Rewrote forum backend.
This commit is contained in:
parent
fb41c71ee9
commit
39c6269cf3
48 changed files with 3798 additions and 3032 deletions
24
composer.lock
generated
24
composer.lock
generated
|
@ -348,7 +348,7 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.flash.moe/flash/index.git",
|
||||
"reference": "553b7c4a14aa7f2403c87ce474933986ac17d040"
|
||||
"reference": "6a38f803f4b3e49296f7472743e7c683c496ec19"
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
|
@ -386,20 +386,20 @@
|
|||
],
|
||||
"description": "Composer package for the common library for my projects.",
|
||||
"homepage": "https://railgun.sh/index",
|
||||
"time": "2023-08-03T01:29:57+00:00"
|
||||
"time": "2023-08-22T00:04:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.1.4",
|
||||
"version": "6.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matomo-org/device-detector.git",
|
||||
"reference": "74f6c4f6732b3ad6cdf25560746841d522969112"
|
||||
"reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/74f6c4f6732b3ad6cdf25560746841d522969112",
|
||||
"reference": "74f6c4f6732b3ad6cdf25560746841d522969112",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/40ca2990dba2c1719e5c62168e822e0b86c167d4",
|
||||
"reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -455,7 +455,7 @@
|
|||
"source": "https://github.com/matomo-org/matomo",
|
||||
"wiki": "https://dev.matomo.org/"
|
||||
},
|
||||
"time": "2023-08-02T08:48:53+00:00"
|
||||
"time": "2023-08-17T16:17:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mustangostang/spyc",
|
||||
|
@ -1616,16 +1616,16 @@
|
|||
"packages-dev": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.26",
|
||||
"version": "1.10.32",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
|
||||
"reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
|
||||
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44",
|
||||
"reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1674,7 +1674,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-19T12:44:37+00:00"
|
||||
"time": "2023-08-24T21:54:50+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
|
|
@ -26,12 +26,6 @@ require_once MSZ_ROOT . '/utility.php';
|
|||
require_once MSZ_SOURCE . '/perms.php';
|
||||
require_once MSZ_SOURCE . '/manage.php';
|
||||
require_once MSZ_SOURCE . '/url.php';
|
||||
require_once MSZ_SOURCE . '/Forum/perms.php';
|
||||
require_once MSZ_SOURCE . '/Forum/forum.php';
|
||||
require_once MSZ_SOURCE . '/Forum/leaderboard.php';
|
||||
require_once MSZ_SOURCE . '/Forum/post.php';
|
||||
require_once MSZ_SOURCE . '/Forum/topic.php';
|
||||
require_once MSZ_SOURCE . '/Forum/validate.php';
|
||||
|
||||
$dbConfig = parse_ini_file(MSZ_CONFIG . '/config.ini', true, INI_SCANNER_TYPED);
|
||||
|
||||
|
|
|
@ -1,77 +1,199 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
$forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
|
||||
$forumId = max($forumId, 0);
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
|
||||
if($forumId === 0) {
|
||||
url_redirect('forum-index');
|
||||
exit;
|
||||
}
|
||||
$forum = $msz->getForum();
|
||||
$users = $msz->getUsers();
|
||||
|
||||
$forum = forum_get($forumId);
|
||||
$forumUser = $msz->getActiveUser();
|
||||
$forumUserId = $forumUser === null ? '0' : $forumUser->getId();
|
||||
$categoryId = (int)filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if(empty($forum) || ($forum['forum_type'] == MSZ_FORUM_TYPE_LINK && empty($forum['forum_link']))) {
|
||||
try {
|
||||
$categoryInfo = $forum->getCategory(categoryId: $categoryId);
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$perms = forum_perms_get_user($forum['forum_id'], $forumUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
$currentUser = $msz->getActiveUser();
|
||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||
|
||||
$perms = forum_perms_get_user($categoryInfo->getId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($forumUser) && $msz->hasActiveBan($forumUser))
|
||||
$perms &= ~MSZ_FORUM_PERM_SET_WRITE;
|
||||
if(isset($currentUser) && $msz->hasActiveBan($currentUser))
|
||||
$perms &= MSZ_FORUM_PERM_LIST_FORUM | MSZ_FORUM_PERM_VIEW_FORUM;
|
||||
|
||||
Template::set('forum_perms', $perms);
|
||||
|
||||
if($forum['forum_type'] == MSZ_FORUM_TYPE_LINK) {
|
||||
forum_increment_clicks($forum['forum_id']);
|
||||
redirect($forum['forum_link']);
|
||||
if($categoryInfo->isLink()) {
|
||||
if($categoryInfo->hasLinkTarget()) {
|
||||
$forum->incrementCategoryClicks($categoryInfo);
|
||||
redirect($categoryInfo->getLinkTarget());
|
||||
} else render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$forumPagination = new Pagination($forum['forum_topic_count'], 20);
|
||||
$forumPagination = new Pagination($forum->countTopics(
|
||||
categoryInfo: $categoryInfo,
|
||||
global: true,
|
||||
deleted: perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ? null : false
|
||||
), 20);
|
||||
|
||||
if(!$forumPagination->hasValidOffset() && $forum['forum_topic_count'] > 0) {
|
||||
if(!$forumPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$forumMayHaveTopics = forum_may_have_topics($forum['forum_type']);
|
||||
$topics = $forumMayHaveTopics
|
||||
? forum_topic_listing(
|
||||
$forum['forum_id'],
|
||||
$forumUserId,
|
||||
$forumPagination->getOffset(),
|
||||
$forumPagination->getRange(),
|
||||
perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST)
|
||||
)
|
||||
: [];
|
||||
$userInfos = [];
|
||||
$userColours = [];
|
||||
$children = [];
|
||||
$topics = [];
|
||||
|
||||
$forumMayHaveChildren = forum_may_have_children($forum['forum_type']);
|
||||
if($categoryInfo->mayHaveChildren()) {
|
||||
$children = $forum->getCategoryChildren($categoryInfo, hidden: false, asTree: true);
|
||||
|
||||
if($forumMayHaveChildren) {
|
||||
$forum['forum_subforums'] = forum_get_children($forum['forum_id'], $forumUserId);
|
||||
foreach($children as $child) {
|
||||
$childPerms = forum_perms_get_user($child->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(!perms_check($childPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
|
||||
unset($category->children[$childId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($forum['forum_subforums'] as $skey => $subforum) {
|
||||
$forum['forum_subforums'][$skey]['forum_subforums']
|
||||
= forum_get_children($subforum['forum_id'], $forumUserId);
|
||||
$childUnread = false;
|
||||
|
||||
if($child->info->mayHaveChildren()) {
|
||||
foreach($child->children as $grandChildId => $grandChild) {
|
||||
$grandChildPerms = forum_perms_get_user($grandChild->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(!perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
|
||||
unset($child->children[$grandChildId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$grandChildUnread = false;
|
||||
|
||||
if($grandChild->info->mayHaveTopics()) {
|
||||
$catIds = [$grandChild->info->getId()];
|
||||
foreach($grandChild->childIds as $greatGrandChildId) {
|
||||
$greatGrandChildPerms = forum_perms_get_user($greatGrandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(perms_check($greatGrandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
|
||||
$catIds[] = $greatGrandChildId;
|
||||
}
|
||||
|
||||
$grandChildUnread = $forum->checkCategoryUnread($catIds, $currentUser);
|
||||
if($grandChildUnread)
|
||||
$childUnread = true;
|
||||
}
|
||||
|
||||
$grandChild->perms = $grandChildPerms;
|
||||
$grandChild->unread = $grandChildUnread;
|
||||
}
|
||||
}
|
||||
|
||||
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
|
||||
$catIds = [$child->info->getId()];
|
||||
foreach($child->childIds as $grandChildId) {
|
||||
$grandChildPerms = forum_perms_get_user($grandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
|
||||
$catIds[] = $grandChildId;
|
||||
}
|
||||
|
||||
try {
|
||||
$lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
||||
} catch(RuntimeException $ex) {
|
||||
$lastPostInfo = null;
|
||||
}
|
||||
|
||||
if($lastPostInfo !== null) {
|
||||
$child->lastPost = new stdClass;
|
||||
$child->lastPost->info = $lastPostInfo;
|
||||
$child->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo);
|
||||
|
||||
if($lastPostInfo->hasUserId()) {
|
||||
$lastPostUserId = $lastPostInfo->getUserId();
|
||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
||||
$userInfos[$lastPostUserId] = $userInfo;
|
||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$child->lastPost->user = $userInfos[$lastPostUserId];
|
||||
$child->lastPost->colour = $userColours[$lastPostUserId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($child->info->mayHaveTopics() && !$childUnread)
|
||||
$childUnread = $forum->checkCategoryUnread($child->info, $currentUser);
|
||||
|
||||
$child->perms = $childPerms;
|
||||
$child->unread = $childUnread;
|
||||
}
|
||||
}
|
||||
|
||||
if($categoryInfo->mayHaveTopics()) {
|
||||
$topicInfos = $forum->getTopics(
|
||||
categoryInfo: $categoryInfo,
|
||||
global: true,
|
||||
deleted: perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ? null : false,
|
||||
pagination: $forumPagination,
|
||||
);
|
||||
|
||||
foreach($topicInfos as $topicInfo) {
|
||||
$topics[] = $topic = new stdClass;
|
||||
$topic->info = $topicInfo;
|
||||
$topic->unread = $forum->checkTopicUnread($topicInfo, $currentUser);
|
||||
$topic->participated = $forum->checkTopicParticipated($topicInfo, $currentUser);
|
||||
$topic->lastPost = new stdClass;
|
||||
|
||||
if($topicInfo->hasUserId()) {
|
||||
$lastTopicUserId = $topicInfo->getUserId();
|
||||
if(!array_key_exists($lastTopicUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastTopicUserId, 'id');
|
||||
$userInfos[$lastTopicUserId] = $userInfo;
|
||||
$userColours[$lastTopicUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$topic->user = $userInfos[$lastTopicUserId];
|
||||
$topic->colour = $userColours[$lastTopicUserId];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic->lastPost->info = $lastPostInfo = $forum->getPost(
|
||||
topicInfo: $topicInfo,
|
||||
getLast: true,
|
||||
deleted: $topicInfo->isDeleted() ? null : false,
|
||||
);
|
||||
|
||||
if($lastPostInfo->hasUserId()) {
|
||||
$lastPostUserId = $lastPostInfo->getUserId();
|
||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
||||
$userInfos[$lastPostUserId] = $userInfo;
|
||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$topic->lastPost->user = $userInfos[$lastPostUserId];
|
||||
$topic->lastPost->colour = $userColours[$lastPostUserId];
|
||||
}
|
||||
} catch(RuntimeException $ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
$perms = perms_check_bulk($perms, [
|
||||
'can_create_topic' => MSZ_FORUM_PERM_CREATE_TOPIC,
|
||||
]);
|
||||
|
||||
Template::render('forum.forum', [
|
||||
'forum_breadcrumbs' => forum_get_breadcrumbs($forum['forum_id']),
|
||||
'global_accent_colour' => forum_get_colour($forum['forum_id']),
|
||||
'forum_may_have_topics' => $forumMayHaveTopics,
|
||||
'forum_may_have_children' => $forumMayHaveChildren,
|
||||
'forum_info' => $forum,
|
||||
'forum_breadcrumbs' => $forum->getCategoryAncestry($categoryInfo),
|
||||
'global_accent_colour' => $forum->getCategoryColour($categoryInfo),
|
||||
'forum_info' => $categoryInfo,
|
||||
'forum_children' => $children,
|
||||
'forum_topics' => $topics,
|
||||
'forum_pagination' => $forumPagination,
|
||||
'forum_show_mark_as_read' => $forumUser !== null,
|
||||
'forum_show_mark_as_read' => $currentUser !== null,
|
||||
'forum_perms' => $perms,
|
||||
]);
|
||||
|
|
|
@ -1,57 +1,207 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
$indexMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||
$forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
|
||||
$forum = $msz->getForum();
|
||||
$users = $msz->getUsers();
|
||||
$mode = (string)filter_input(INPUT_GET, 'm');
|
||||
|
||||
$currentUser = $msz->getActiveUser();
|
||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||
|
||||
switch($indexMode) {
|
||||
case 'mark':
|
||||
if($mode === 'mark') {
|
||||
if(!$msz->isLoggedIn()) {
|
||||
echo render_error(403);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
$categoryId = filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT);
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
forum_mark_read($forumId, (int)$msz->getAuthInfo()->getUserId());
|
||||
$redirect = url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]);
|
||||
redirect($redirect);
|
||||
break;
|
||||
$categoryInfos = $categoryId === null
|
||||
? $forum->getCategories()
|
||||
: $forum->getCategoryChildren(parentInfo: $categoryId, includeSelf: true);
|
||||
|
||||
foreach($categoryInfos as $categoryInfo) {
|
||||
$perms = forum_perms_get_user($categoryInfo->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_LIST_FORUM))
|
||||
$forum->updateUserReadCategory($userInfo, $categoryInfo);
|
||||
}
|
||||
|
||||
url_redirect($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]);
|
||||
return;
|
||||
}
|
||||
|
||||
Template::render('confirm', [
|
||||
'title' => 'Mark forum as read',
|
||||
'message' => 'Are you sure you want to mark ' . ($forumId === 0 ? 'the entire' : 'this') . ' forum as read?',
|
||||
'return' => url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]),
|
||||
'message' => 'Are you sure you want to mark ' . ($categoryId < 1 ? 'the entire' : 'this') . ' forum as read?',
|
||||
'return' => url($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]),
|
||||
'params' => [
|
||||
'forum' => $forumId,
|
||||
'forum' => $categoryId,
|
||||
]
|
||||
]);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
$categories = forum_get_root_categories($currentUserId);
|
||||
$blankForum = count($categories) < 1;
|
||||
if($mode !== '') {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($categories as $key => $category) {
|
||||
$categories[$key]['forum_subforums'] = forum_get_children($category['forum_id'], $currentUserId);
|
||||
$userInfos = [];
|
||||
$userColours = [];
|
||||
$categories = $forum->getCategories(hidden: false, asTree: true);
|
||||
|
||||
foreach($categories[$key]['forum_subforums'] as $skey => $sub) {
|
||||
if(!forum_may_have_children($sub['forum_type'])) {
|
||||
foreach($categories as $categoryId => $category) {
|
||||
$perms = forum_perms_get_user($category->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_LIST_FORUM)) {
|
||||
unset($categories[$categoryId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories[$key]['forum_subforums'][$skey]['forum_subforums']
|
||||
= forum_get_children($sub['forum_id'], $currentUserId);
|
||||
$unread = false;
|
||||
|
||||
if($category->info->mayHaveChildren())
|
||||
foreach($category->children as $childId => $child) {
|
||||
$childPerms = forum_perms_get_user($child->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(!perms_check($childPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
|
||||
unset($category->children[$childId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$childUnread = false;
|
||||
|
||||
if($category->info->isListing()) {
|
||||
if($child->info->mayHaveChildren()) {
|
||||
foreach($child->children as $grandChildId => $grandChild) {
|
||||
$grandChildPerms = forum_perms_get_user($grandChild->info->getId(), (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(!perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM)) {
|
||||
unset($child->children[$grandChildId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$grandChildUnread = false;
|
||||
|
||||
if($grandChild->info->mayHaveTopics()) {
|
||||
$catIds = [$grandChild->info->getId()];
|
||||
foreach($grandChild->childIds as $greatGrandChildId) {
|
||||
$greatGrandChildPerms = forum_perms_get_user($greatGrandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(perms_check($greatGrandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
|
||||
$catIds[] = $greatGrandChildId;
|
||||
}
|
||||
|
||||
$grandChildUnread = $forum->checkCategoryUnread($catIds, $currentUser);
|
||||
if($grandChildUnread)
|
||||
$childUnread = true;
|
||||
}
|
||||
|
||||
$grandChild->perms = $grandChildPerms;
|
||||
$grandChild->unread = $grandChildUnread;
|
||||
}
|
||||
}
|
||||
|
||||
if($child->info->mayHaveChildren() || $child->info->mayHaveTopics()) {
|
||||
$catIds = [$child->info->getId()];
|
||||
foreach($child->childIds as $grandChildId) {
|
||||
$grandChildPerms = forum_perms_get_user($grandChildId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(perms_check($grandChildPerms, MSZ_FORUM_PERM_LIST_FORUM))
|
||||
$catIds[] = $grandChildId;
|
||||
}
|
||||
|
||||
try {
|
||||
$lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
||||
} catch(RuntimeException $ex) {
|
||||
$lastPostInfo = null;
|
||||
}
|
||||
|
||||
if($lastPostInfo !== null) {
|
||||
$child->lastPost = new stdClass;
|
||||
$child->lastPost->info = $lastPostInfo;
|
||||
$child->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo);
|
||||
|
||||
if($lastPostInfo->hasUserId()) {
|
||||
$lastPostUserId = $lastPostInfo->getUserId();
|
||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
||||
$userInfos[$lastPostUserId] = $userInfo;
|
||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$child->lastPost->user = $userInfos[$lastPostUserId];
|
||||
$child->lastPost->colour = $userColours[$lastPostUserId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($child->info->mayHaveTopics() && !$childUnread) {
|
||||
$childUnread = $forum->checkCategoryUnread($child->info, $currentUser);
|
||||
if($childUnread)
|
||||
$unread = true;
|
||||
}
|
||||
|
||||
$child->perms = $childPerms;
|
||||
$child->unread = $childUnread;
|
||||
}
|
||||
|
||||
if($category->info->mayHaveTopics() && !$unread)
|
||||
$unread = $forum->checkCategoryUnread($category->info, $currentUser);
|
||||
|
||||
if(!$category->info->isListing()) {
|
||||
if(!array_key_exists('0', $categories)) {
|
||||
$categories['0'] = $root = new stdClass;
|
||||
$root->info = null;
|
||||
$root->perms = 0;
|
||||
$root->unread = false;
|
||||
$root->colour = null;
|
||||
$root->children = [];
|
||||
}
|
||||
|
||||
$categories['0']->children[$categoryId] = $category;
|
||||
unset($categories[$categoryId]);
|
||||
|
||||
if($category->info->mayHaveChildren() || $category->info->mayHaveTopics()) {
|
||||
$catIds = [$category->info->getId()];
|
||||
foreach($category->childIds as $childId) {
|
||||
$childPerms = forum_perms_get_user($childId, (int)$currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if(perms_check($childPerms, MSZ_FORUM_PERM_LIST_FORUM))
|
||||
$catIds[] = $childId;
|
||||
}
|
||||
|
||||
try {
|
||||
$lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false);
|
||||
} catch(RuntimeException $ex) {
|
||||
$lastPostInfo = null;
|
||||
}
|
||||
|
||||
if($lastPostInfo !== null) {
|
||||
$category->lastPost = new stdClass;
|
||||
$category->lastPost->info = $lastPostInfo;
|
||||
$category->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo);
|
||||
|
||||
if($lastPostInfo->hasUserId()) {
|
||||
$lastPostUserId = $lastPostInfo->getUserId();
|
||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastPostInfo->getUserId(), 'id');
|
||||
$userInfos[$lastPostUserId] = $userInfo;
|
||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$category->lastPost->user = $userInfos[$lastPostUserId];
|
||||
$category->lastPost->colour = $userColours[$lastPostUserId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$category->perms = $perms;
|
||||
$category->unread = $unread;
|
||||
}
|
||||
|
||||
Template::render('forum.index', [
|
||||
'forum_categories' => $categories,
|
||||
'forum_empty' => $blankForum,
|
||||
'forum_empty' => empty($categories),
|
||||
'forum_show_mark_as_read' => $currentUser !== null,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,63 +1,116 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_FORUM, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$leaderboardMode = !empty($_GET['mode']) && is_string($_GET['mode']) && ctype_lower($_GET['mode']) ? $_GET['mode'] : '';
|
||||
$leaderboardId = !empty($_GET['id']) && is_string($_GET['id'])
|
||||
&& ctype_digit($_GET['id'])
|
||||
? $_GET['id']
|
||||
: MSZ_FORUM_LEADERBOARD_CATEGORY_ALL;
|
||||
$leaderboardIdLength = strlen($leaderboardId);
|
||||
|
||||
$leaderboardYear = $leaderboardIdLength === 4 || $leaderboardIdLength === 6 ? substr($leaderboardId, 0, 4) : null;
|
||||
$leaderboardMonth = $leaderboardIdLength === 6 ? substr($leaderboardId, 4, 2) : null;
|
||||
|
||||
if(empty($_GET['allow_unranked'])) {
|
||||
[
|
||||
'forum_leader.unranked.forum' => $unrankedForums,
|
||||
'forum_leader.unranked.topic' => $unrankedTopics,
|
||||
] = $cfg->getValues([
|
||||
$forum = $msz->getForum();
|
||||
$users = $msz->getUsers();
|
||||
$config = $cfg->getValues([
|
||||
['forum_leader.first_year:i', 2018],
|
||||
['forum_leader.first_month:i', 12],
|
||||
'forum_leader.unranked.forum:a',
|
||||
'forum_leader.unranked.topic:a',
|
||||
]);
|
||||
} else $unrankedForums = $unrankedTopics = [];
|
||||
|
||||
$leaderboards = forum_leaderboard_categories();
|
||||
$leaderboard = forum_leaderboard_listing($leaderboardYear, $leaderboardMonth, $unrankedForums, $unrankedTopics);
|
||||
$mode = (string)filter_input(INPUT_GET, 'mode');
|
||||
$yearMonth = (string)filter_input(INPUT_GET, 'id');
|
||||
$year = $month = 0;
|
||||
|
||||
$leaderboardName = 'All Time';
|
||||
$currentYear = (int)date('Y');
|
||||
$currentMonth = (int)date('m');
|
||||
|
||||
if($leaderboardYear) {
|
||||
$leaderboardName = "Leaderboard {$leaderboardYear}";
|
||||
|
||||
if($leaderboardMonth)
|
||||
$leaderboardName .= "-{$leaderboardMonth}";
|
||||
if(!empty($yearMonth)) {
|
||||
$yearMonthLength = strlen($yearMonth);
|
||||
if(($yearMonthLength !== 4 && $yearMonthLength !== 6) || !ctype_digit($yearMonth)) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($leaderboardMode === 'markdown') {
|
||||
$year = (int)substr($yearMonth, 0, 4);
|
||||
if($year < $config['forum_leader.first_year'] || $year > $currentYear) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($yearMonthLength === 6) {
|
||||
$month = (int)substr($yearMonth, 4, 2);
|
||||
if($month < 1 || $month > 12 || ($year === $config['forum_leader.first_year'] && $month < $config['forum_leader.first_month'])) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(filter_has_var(INPUT_GET, 'allow_unranked')) {
|
||||
$unrankedForums = $unrankedTopics = [];
|
||||
} else {
|
||||
$unrankedForums = $config['forum_leader.unranked.forum'];
|
||||
$unrankedTopics = $config['forum_leader.unranked.topic'];
|
||||
}
|
||||
|
||||
$years = $months = [];
|
||||
|
||||
for($i = $currentYear; $i >= $config['forum_leader.first_year']; $i--)
|
||||
$years[(string)$i] = sprintf('Leaderboard %d', $i);
|
||||
|
||||
for($i = $currentYear, $j = $currentMonth;;) {
|
||||
$months[sprintf('%d%02d', $i, $j)] = sprintf('Leaderboard %d-%02d', $i, $j);
|
||||
|
||||
if($j <= 1) {
|
||||
$i--; $j = 12;
|
||||
} else $j--;
|
||||
|
||||
if($i <= $config['forum_leader.first_year'] && $j < $config['forum_leader.first_month'])
|
||||
break;
|
||||
}
|
||||
|
||||
$rankings = $forum->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics);
|
||||
foreach($rankings as $ranking) {
|
||||
$ranking->user = $ranking->colour = null;
|
||||
|
||||
if($ranking->userId !== '')
|
||||
try {
|
||||
$ranking->user = $users->getUser($ranking->userId);
|
||||
$ranking->colour = $users->getUserColour($ranking->user);
|
||||
} catch(RuntimeException $ex) {}
|
||||
}
|
||||
|
||||
$name = 'All Time';
|
||||
|
||||
if($year > 0) {
|
||||
$name = "Leaderboard {$year}";
|
||||
|
||||
if($month > 0)
|
||||
$name .= "-{$month}";
|
||||
}
|
||||
|
||||
if($mode === 'markdown') {
|
||||
$markdown = <<<MD
|
||||
# {$leaderboardName}
|
||||
# {$name}
|
||||
|
||||
| Rank | Usename | Post count |
|
||||
| ----:|:------- | ----------:|
|
||||
|
||||
MD;
|
||||
|
||||
foreach($leaderboard as $user) {
|
||||
$markdown .= sprintf("| %s | [%s](%s%s) | %s |\r\n", $user['rank'], $user['username'], url_prefix(false), url('user-profile', ['user' => $user['user_id']]), $user['posts']);
|
||||
}
|
||||
foreach($rankings as $ranking)
|
||||
$markdown .= sprintf("| %s | [%s](%s%s) | %s |\r\n", $ranking->position,
|
||||
$ranking->user?->getName() ?? 'Deleted User',
|
||||
url_prefix(false), url('user-profile', ['user' => $ranking->userId]), $ranking->postsCount);
|
||||
|
||||
Template::set('leaderboard_markdown', $markdown);
|
||||
}
|
||||
|
||||
Template::render('forum.leaderboard', [
|
||||
'leaderboard_id' => $leaderboardId,
|
||||
'leaderboard_name' => $leaderboardName,
|
||||
'leaderboard_categories' => $leaderboards,
|
||||
'leaderboard_data' => $leaderboard,
|
||||
'leaderboard_mode' => $leaderboardMode,
|
||||
'leaderboard_id' => $yearMonth,
|
||||
'leaderboard_name' => $name,
|
||||
'leaderboard_years' => $years,
|
||||
'leaderboard_months' => $months,
|
||||
'leaderboard_data' => $rankings,
|
||||
'leaderboard_mode' => $mode,
|
||||
]);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
$forum = $msz->getForum();
|
||||
|
||||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
||||
$postMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
|
||||
|
@ -20,169 +24,150 @@ if($postMode !== '' && $msz->hasActiveBan()) {
|
|||
return;
|
||||
}
|
||||
|
||||
$postInfo = forum_post_get($postId, true);
|
||||
$perms = empty($postInfo)
|
||||
? 0
|
||||
: forum_perms_get_user($postInfo['forum_id'], $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
try {
|
||||
$postInfo = $forum->getPost(postId: $postId);
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$perms = forum_perms_get_user($postInfo->getCategoryId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
|
||||
|
||||
switch($postMode) {
|
||||
case 'delete':
|
||||
$canDelete = forum_post_can_delete($postInfo, $currentUserId);
|
||||
$canDeleteMsg = '';
|
||||
$responseCode = 200;
|
||||
|
||||
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($canDeleteAny) {
|
||||
if($postInfo->isDeleted()) {
|
||||
echo render_info('This post has already been marked as deleted.', 404);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if($postInfo->isDeleted()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($canDelete !== MSZ_E_FORUM_POST_DELETE_OK) {
|
||||
echo render_info($canDeleteMsg, $responseCode);
|
||||
break;
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_POST)) {
|
||||
echo render_info('You are not allowed to delete posts.', 403);
|
||||
return;
|
||||
}
|
||||
|
||||
if($postInfo->getUserId() !== $currentUser->getId()) {
|
||||
echo render_info('You can only delete your own posts.', 403);
|
||||
return;
|
||||
}
|
||||
|
||||
// posts may only be deleted within a week of creation, this should be a config value
|
||||
$deleteTimeFrame = 60 * 60 * 24 * 7;
|
||||
if($postInfo->getCreatedTime() < time() - $deleteTimeFrame) {
|
||||
echo render_info('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$originalPostInfo = $forum->getPost(topicInfo: $postInfo->getTopicId());
|
||||
if($originalPostInfo->getId() === $postInfo->getId()) {
|
||||
echo render_info('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403);
|
||||
return;
|
||||
}
|
||||
|
||||
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',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$deletePost = forum_post_delete($postInfo['post_id']);
|
||||
$forum->deletePost($postInfo);
|
||||
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->getId()]);
|
||||
|
||||
if($deletePost) {
|
||||
$msz->createAuditLog('FORUM_POST_DELETE', [$postInfo['post_id']]);
|
||||
}
|
||||
|
||||
if(!$deletePost) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
||||
break;
|
||||
|
||||
case 'nuke':
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST)) {
|
||||
if(!$canDeleteAny) {
|
||||
echo render_error(403);
|
||||
break;
|
||||
}
|
||||
|
||||
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',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$nukePost = forum_post_nuke($postInfo['post_id']);
|
||||
$forum->nukePost($postInfo->getId());
|
||||
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->getId()]);
|
||||
|
||||
if(!$nukePost) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo['post_id']]);
|
||||
|
||||
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
|
||||
url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]);
|
||||
break;
|
||||
|
||||
case 'restore':
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST)) {
|
||||
if(!$canDeleteAny) {
|
||||
echo render_error(403);
|
||||
break;
|
||||
}
|
||||
|
||||
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',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$restorePost = forum_post_restore($postInfo['post_id']);
|
||||
$forum->restorePost($postInfo->getId());
|
||||
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->getId()]);
|
||||
|
||||
if(!$restorePost) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo['post_id']]);
|
||||
|
||||
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
|
||||
url_redirect('forum-post', [
|
||||
'post' => $postInfo['post_id'],
|
||||
'post_fragment' => 'p' . $postInfo['post_id'],
|
||||
'post' => $postInfo->getId(),
|
||||
'post_fragment' => 'p' . $postInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
use Index\DateTime;
|
||||
use Misuzu\Forum\ForumTopicInfo;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
if(!$msz->isLoggedIn()) {
|
||||
|
@ -15,6 +19,13 @@ if($msz->hasActiveBan()) {
|
|||
return;
|
||||
}
|
||||
|
||||
$forum = $msz->getForum();
|
||||
$users = $msz->getUsers();
|
||||
|
||||
$userInfos = [];
|
||||
$userColours = [];
|
||||
$userPostsCounts = [];
|
||||
|
||||
$forumPostingModes = [
|
||||
'create', 'edit', 'quote', 'preview',
|
||||
];
|
||||
|
@ -57,42 +68,70 @@ 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($topicId)) {
|
||||
$topic = forum_topic_get($topicId);
|
||||
|
||||
if(isset($topic['forum_id'])) {
|
||||
$forumId = (int)$topic['forum_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($forumId)) {
|
||||
$forum = forum_get($forumId);
|
||||
}
|
||||
|
||||
if(empty($forum)) {
|
||||
if(empty($postId)) {
|
||||
$hasPostInfo = false;
|
||||
} else {
|
||||
try {
|
||||
$postInfo = $forum->getPost(postId: $postId);
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$perms = forum_perms_get_user($forum['forum_id'], $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
if($postInfo->isDeleted()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($forum['forum_archived']
|
||||
|| (!empty($topic['topic_locked']) && !perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC))
|
||||
// should automatic cross-quoting be a thing? if so, check if $topicId is < 1 first <-- what did i mean by this?
|
||||
$topicId = $postInfo->getTopicId();
|
||||
$hasPostInfo = true;
|
||||
}
|
||||
|
||||
if(empty($topicId)) {
|
||||
$hasTopicInfo = false;
|
||||
} else {
|
||||
try {
|
||||
$topicInfo = $forum->getTopic(topicId: $topicId);
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($topicInfo->isDeleted()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$forumId = $topicInfo->getCategoryId();
|
||||
$originalPostInfo = $forum->getPost(topicInfo: $topicInfo);
|
||||
$hasTopicInfo = true;
|
||||
}
|
||||
|
||||
if(empty($forumId)) {
|
||||
$hasCategoryInfo = false;
|
||||
} else {
|
||||
try {
|
||||
$categoryInfo = $forum->getCategory(categoryId: $forumId);
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$hasCategoryInfo = true;
|
||||
}
|
||||
|
||||
$perms = forum_perms_get_user($categoryInfo->getId(), $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
|
||||
if($categoryInfo->isArchived()
|
||||
|| (isset($topicInfo) && $topicInfo->isLocked() && !perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC))
|
||||
|| !perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM | MSZ_FORUM_PERM_CREATE_POST)
|
||||
|| (empty($topic) && !perms_check($perms, MSZ_FORUM_PERM_CREATE_TOPIC))) {
|
||||
|| (!isset($topicInfo) && !perms_check($perms, MSZ_FORUM_PERM_CREATE_TOPIC))) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!forum_may_have_topics($forum['forum_type'])) {
|
||||
if(!$categoryInfo->mayHaveTopics()) {
|
||||
echo render_error(400);
|
||||
return;
|
||||
}
|
||||
|
@ -100,31 +139,21 @@ if(!forum_may_have_topics($forum['forum_type'])) {
|
|||
$topicTypes = [];
|
||||
|
||||
if($mode === 'create' || $mode === 'edit') {
|
||||
$topicTypes[MSZ_TOPIC_TYPE_DISCUSSION] = 'Normal discussion';
|
||||
$topicTypes['discussion'] = 'Normal discussion';
|
||||
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_STICKY_TOPIC)) {
|
||||
$topicTypes[MSZ_TOPIC_TYPE_STICKY] = 'Sticky topic';
|
||||
}
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_ANNOUNCE_TOPIC)) {
|
||||
$topicTypes[MSZ_TOPIC_TYPE_ANNOUNCEMENT] = 'Announcement';
|
||||
}
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC)) {
|
||||
$topicTypes[MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT] = 'Global Announcement';
|
||||
}
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_STICKY_TOPIC))
|
||||
$topicTypes['sticky'] = 'Sticky topic';
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_ANNOUNCE_TOPIC))
|
||||
$topicTypes['announce'] = 'Announcement';
|
||||
if(perms_check($perms, MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC))
|
||||
$topicTypes['global'] = 'Global Announcement';
|
||||
}
|
||||
|
||||
// edit mode stuff
|
||||
if($mode === 'edit') {
|
||||
if(empty($post)) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!perms_check($perms, (string)$post['poster_id'] === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
|
||||
if($mode === 'edit' && !perms_check($perms, $postInfo->getUserId() === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$notices = [];
|
||||
|
||||
|
@ -132,38 +161,50 @@ 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']) ? $_POST['post']['type'] : null;
|
||||
$postSignature = isset($_POST['post']['signature']);
|
||||
|
||||
if(!CSRF::validateRequest()) {
|
||||
$notices[] = 'Could not verify request.';
|
||||
} else {
|
||||
$isEditingTopic = empty($topic) || ($mode === 'edit' && $post['is_opening_post']);
|
||||
$isEditingTopic = empty($topicInfo) || ($mode === 'edit' && $originalPostInfo->getId() == $postInfo->getId());
|
||||
|
||||
if(is_string($topicType))
|
||||
$topicType = ForumTopicInfo::TYPE_ALIASES[$topicType] ?? ForumTopicInfo::TYPE_DISCUSSION;
|
||||
else
|
||||
$topicType = (int)$topicType;
|
||||
|
||||
if($mode === 'create') {
|
||||
$timeoutCheck = max(1, forum_timeout($forumId, $currentUserId));
|
||||
$postTimeout = $cfg->getInteger('forum.posting.timeout', 5);
|
||||
if($postTimeout > 0) {
|
||||
$postTimeoutThreshold = DateTime::now()->modify(sprintf('-%d seconds', $postTimeout));
|
||||
$lastPostCreatedAt = $forum->getUserLastPostCreatedAt($currentUser);
|
||||
|
||||
if($timeoutCheck < 5) {
|
||||
$notices[] = sprintf("You're posting too quickly! Please wait %s seconds before posting again.", number_format($timeoutCheck));
|
||||
if($lastPostCreatedAt->isMoreThan($postTimeoutThreshold)) {
|
||||
$waitSeconds = $postTimeout + ($lastPostCreatedAt->getUnixTimeSeconds() - time());
|
||||
|
||||
$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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($isEditingTopic) {
|
||||
$originalTopicTitle = $topic['topic_title'] ?? null;
|
||||
$originalTopicTitle = $topicInfo?->getTitle() ?? null;
|
||||
$topicTitleChanged = $topicTitle !== $originalTopicTitle;
|
||||
$originalTopicType = (int)($topic['topic_type'] ?? MSZ_TOPIC_TYPE_DISCUSSION);
|
||||
$originalTopicType = (int)($topicInfo?->getType() ?? 0);
|
||||
$topicTypeChanged = $topicType !== null && $topicType !== $originalTopicType;
|
||||
|
||||
switch(forum_validate_title($topicTitle)) {
|
||||
case 'too-short':
|
||||
$notices[] = 'Topic title was too short.';
|
||||
break;
|
||||
$topicTitleLengths = $cfg->getValues([
|
||||
['forum.topic.minLength:i', 3],
|
||||
['forum.topic.maxLength:i', 100],
|
||||
]);
|
||||
|
||||
case 'too-long':
|
||||
$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.';
|
||||
break;
|
||||
}
|
||||
|
||||
if($mode === 'create' && $topicType === null) {
|
||||
$topicType = array_key_first($topicTypes);
|
||||
|
@ -172,66 +213,76 @@ if(!empty($_POST)) {
|
|||
}
|
||||
}
|
||||
|
||||
if(!Parser::isValid($postParser)) {
|
||||
if(!Parser::isValid($postParser))
|
||||
$notices[] = 'Invalid parser selected.';
|
||||
}
|
||||
|
||||
switch(forum_validate_post($postText)) {
|
||||
case 'too-short':
|
||||
$postTextLengths = $cfg->getValues([
|
||||
['forum.post.minLength:i', 1],
|
||||
['forum.post.maxLength:i', 60000],
|
||||
]);
|
||||
|
||||
$postTextLength = mb_strlen(trim($postText));
|
||||
if($postTextLength < $postTextLengths['forum.post.minLength'])
|
||||
$notices[] = 'Post content was too short.';
|
||||
break;
|
||||
|
||||
case 'too-long':
|
||||
elseif($postTextLength > $postTextLengths['forum.post.maxLength'])
|
||||
$notices[] = 'Post content was too long.';
|
||||
break;
|
||||
}
|
||||
|
||||
if(empty($notices)) {
|
||||
switch($mode) {
|
||||
case 'create':
|
||||
if(!empty($topic)) {
|
||||
forum_topic_bump($topic['topic_id']);
|
||||
} else {
|
||||
$topicId = forum_topic_create(
|
||||
$forum['forum_id'],
|
||||
$currentUserId,
|
||||
if(empty($topicInfo)) {
|
||||
$topicInfo = $forum->createTopic(
|
||||
$categoryInfo,
|
||||
$currentUser,
|
||||
$topicTitle,
|
||||
$topicType
|
||||
);
|
||||
}
|
||||
|
||||
$postId = forum_post_create(
|
||||
$topicId = $topicInfo->getId();
|
||||
$forum->incrementCategoryTopics($categoryInfo);
|
||||
} else
|
||||
$forum->bumpTopic($topicInfo);
|
||||
|
||||
$postInfo = $forum->createPost(
|
||||
$topicId,
|
||||
$forum['forum_id'],
|
||||
$currentUserId,
|
||||
$currentUser,
|
||||
$_SERVER['REMOTE_ADDR'],
|
||||
$postText,
|
||||
$postParser,
|
||||
$postSignature
|
||||
$postSignature,
|
||||
$categoryInfo
|
||||
);
|
||||
forum_topic_mark_read($currentUserId, $topicId, $forum['forum_id']);
|
||||
forum_count_increase($forum['forum_id'], empty($topic));
|
||||
|
||||
$postId = $postInfo->getId();
|
||||
$forum->incrementCategoryPosts($categoryInfo);
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
$markUpdated = $post['poster_id'] === $currentUserId
|
||||
&& $post['post_created_unix'] < strtotime('-1 minutes')
|
||||
&& $postText !== $post['post_text'];
|
||||
$markUpdated = $postInfo->getUserId() === $currentUserId
|
||||
&& $postInfo->shouldMarkAsEdited()
|
||||
&& $postText !== $postInfo->getBody();
|
||||
|
||||
if(!forum_post_update($postId, $_SERVER['REMOTE_ADDR'], $postText, $postParser, $postSignature, $markUpdated)) {
|
||||
$notices[] = 'Post edit failed.';
|
||||
}
|
||||
$forum->updatePost(
|
||||
$postId,
|
||||
remoteAddr: $_SERVER['REMOTE_ADDR'],
|
||||
body: $postText,
|
||||
bodyParser: $postParser,
|
||||
displaySignature: $postSignature,
|
||||
bumpEdited: $markUpdated
|
||||
);
|
||||
|
||||
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged)) {
|
||||
if(!forum_topic_update($topicId, $topicTitle, $topicType)) {
|
||||
$notices[] = 'Topic update failed.';
|
||||
}
|
||||
}
|
||||
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged))
|
||||
$forum->updateTopic(
|
||||
$topicId,
|
||||
title: $topicTitle,
|
||||
type: $topicType
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if(empty($notices)) {
|
||||
$redirect = url(empty($topic) ? 'forum-topic' : 'forum-post', [
|
||||
// does this ternary ever return forum-topic?
|
||||
$redirect = url(empty($topicInfo) ? 'forum-topic' : 'forum-post', [
|
||||
'topic' => $topicId ?? 0,
|
||||
'post' => $postId ?? 0,
|
||||
'post_fragment' => 'p' . ($postId ?? 0),
|
||||
|
@ -243,24 +294,61 @@ if(!empty($_POST)) {
|
|||
}
|
||||
}
|
||||
|
||||
if(!empty($topic)) {
|
||||
Template::set('posting_topic', $topic);
|
||||
}
|
||||
if(!empty($topicInfo))
|
||||
Template::set('posting_topic', $topicInfo);
|
||||
|
||||
if($mode === 'edit') { // $post is pretty much sure to be populated at this point
|
||||
$post = new stdClass;
|
||||
$post->info = $postInfo;
|
||||
|
||||
if($postInfo->hasUserId()) {
|
||||
$postUserId = $postInfo->getUserId();
|
||||
if(!array_key_exists($postUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($postUserId, 'id');
|
||||
$userInfos[$postUserId] = $userInfo;
|
||||
$userColours[$postUserId] = $users->getUserColour($userInfo);
|
||||
$userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
||||
}
|
||||
|
||||
$post->user = $userInfos[$postUserId];
|
||||
$post->colour = $userColours[$postUserId];
|
||||
$post->postsCount = $userPostsCounts[$postUserId];
|
||||
}
|
||||
|
||||
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId();
|
||||
$post->isOriginalPoster = $originalPostInfo->hasUserId() && $postInfo->hasUserId()
|
||||
&& $originalPostInfo->getUserId() === $postInfo->getUserId();
|
||||
|
||||
Template::set('posting_post', $post);
|
||||
}
|
||||
|
||||
$displayInfo = forum_posting_info($currentUserId);
|
||||
try {
|
||||
$lastPostInfo = $forum->getPost(userInfo: $currentUser, getLast: true, deleted: false);
|
||||
$selectedParser = $lastPostInfo->getParser();
|
||||
} catch(RuntimeException $ex) {
|
||||
$selectedParser = Parser::BBCODE;
|
||||
}
|
||||
|
||||
// this sucks, fix it!
|
||||
$topicTypeName = match($topicType ?? $topicInfo?->getType() ?? null) {
|
||||
default => 'discussion',
|
||||
ForumTopicInfo::TYPE_STICKY => 'sticky',
|
||||
ForumTopicInfo::TYPE_ANNOUNCE => 'announce',
|
||||
ForumTopicInfo::TYPE_GLOBAL => 'global',
|
||||
};
|
||||
|
||||
Template::render('forum.posting', [
|
||||
'posting_breadcrumbs' => forum_get_breadcrumbs($forumId),
|
||||
'global_accent_colour' => forum_get_colour($forumId),
|
||||
'posting_forum' => $forum,
|
||||
'posting_info' => $displayInfo,
|
||||
'posting_breadcrumbs' => $forum->getCategoryAncestry($categoryInfo),
|
||||
'global_accent_colour' => $forum->getCategoryColour($categoryInfo),
|
||||
'posting_user' => $currentUser,
|
||||
'posting_user_colour' => $userColours[$currentUser->getId()] ?? $users->getUserColour($currentUser),
|
||||
'posting_user_posts_count' => $userPostsCounts[$currentUser->getId()] ?? $forum->countPosts(userInfo: $currentUser, deleted: false),
|
||||
'posting_user_preferred_parser' => $selectedParser,
|
||||
'posting_forum' => $categoryInfo,
|
||||
'posting_notices' => $notices,
|
||||
'posting_mode' => $mode,
|
||||
'posting_types' => $topicTypes,
|
||||
'posting_type_selected' => $topicTypeName,
|
||||
'posting_defaults' => [
|
||||
'title' => $topicTitle ?? null,
|
||||
'type' => $topicType ?? null,
|
||||
|
|
|
@ -1,43 +1,76 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
|
||||
$forum = $msz->getForum();
|
||||
$users = $msz->getUsers();
|
||||
|
||||
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
|
||||
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
|
||||
$categoryId = null;
|
||||
$moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
|
||||
|
||||
$topicUser = $msz->getActiveUser();
|
||||
$topicUserId = $topicUser === null ? '0' : $topicUser->getId();
|
||||
$currentUser = $msz->getActiveUser();
|
||||
$currentUserId = $currentUser === null ? '0' : $currentUser->getId();
|
||||
|
||||
if($topicId < 1 && $postId > 0) {
|
||||
$postInfo = forum_post_find($postId, $topicUserId);
|
||||
|
||||
if(!empty($postInfo['topic_id'])) {
|
||||
$topicId = (int)$postInfo['topic_id'];
|
||||
}
|
||||
try {
|
||||
$postInfo = $forum->getPost(postId: $postId);
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$topic = forum_topic_get($topicId, true);
|
||||
$perms = $topic
|
||||
? forum_perms_get_user($topic['forum_id'], $topicUserId)[MSZ_FORUM_PERMS_GENERAL]
|
||||
: 0;
|
||||
$categoryId = $postInfo->getCategoryId();
|
||||
$perms = forum_perms_get_user($categoryId, $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
$canDeleteAny = !perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
|
||||
|
||||
if(isset($topicUser) && $msz->hasActiveBan($topicUser))
|
||||
$perms &= ~MSZ_FORUM_PERM_SET_WRITE;
|
||||
if($postInfo->isDeleted() && !$canDeleteAny) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$topicId = $postInfo->getTopicId();
|
||||
$preceedingPostCount = $forum->countPosts(
|
||||
topicInfo: $topicId,
|
||||
upToPostInfo: $postInfo,
|
||||
deleted: $canDeleteAny ? null : false
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$topicIsNuked = $topicIsDeleted = $canDeleteAny = false;
|
||||
$topicInfo = $forum->getTopic(topicId: $topicId);
|
||||
} catch(RuntimeException $ex) {
|
||||
$topicIsNuked = true;
|
||||
}
|
||||
|
||||
if(!$topicIsNuked) {
|
||||
$topicIsDeleted = $topicInfo->isDeleted();
|
||||
|
||||
if($categoryId !== (int)$topicInfo->getCategoryId()) {
|
||||
$categoryId = (int)$topicInfo->getCategoryId();
|
||||
$perms = forum_perms_get_user($categoryId, $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
}
|
||||
|
||||
if(isset($currentUser) && $msz->hasActiveBan($currentUser))
|
||||
$perms &= MSZ_FORUM_PERM_LIST_FORUM | MSZ_FORUM_PERM_VIEW_FORUM;
|
||||
|
||||
$topicIsNuked = empty($topic['topic_id']);
|
||||
$topicIsDeleted = !empty($topic['topic_deleted']);
|
||||
$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
|
||||
}
|
||||
|
||||
if($topicIsNuked || $topicIsDeleted) {
|
||||
$topicRedirectInfo = forum_topic_redir_info($topicId);
|
||||
if(($topicIsNuked || $topicIsDeleted) && $forum->hasTopicRedirect($topicId)) {
|
||||
$topicRedirectInfo = $forum->getTopicRedirect($topicId);
|
||||
Template::set('topic_redir_info', $topicRedirectInfo);
|
||||
|
||||
if($topicIsNuked || !$canDeleteAny) {
|
||||
if(empty($topicRedirectInfo))
|
||||
echo render_error(404);
|
||||
else
|
||||
header('Location: ' . $topicRedirectInfo->topic_redir_url);
|
||||
header('Location: ' . $topicRedirectInfo->getLinkTarget());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -47,9 +80,14 @@ if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
|
|||
return;
|
||||
}
|
||||
|
||||
$topicIsLocked = !empty($topic['topic_locked']);
|
||||
$topicIsArchived = !empty($topic['topic_archived']);
|
||||
$topicPostsTotal = (int)($topic['topic_count_posts'] + $topic['topic_count_posts_deleted']);
|
||||
// Maximum amount of posts a topic may contain to still be deletable by the author
|
||||
// this should be in the config
|
||||
$deletePostThreshold = 1;
|
||||
|
||||
$categoryInfo = $forum->getCategory(topicInfo: $topicInfo);
|
||||
$topicIsLocked = $topicInfo->isLocked();
|
||||
$topicIsArchived = $categoryInfo->isArchived();
|
||||
$topicPostsTotal = $topicInfo->getTotalPostsCount();
|
||||
$topicIsFrozen = $topicIsArchived || $topicIsDeleted;
|
||||
$canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && perms_check($perms, MSZ_FORUM_PERM_DELETE_POST);
|
||||
$canBumpTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_BUMP_TOPIC);
|
||||
|
@ -58,9 +96,9 @@ $canNukeOrRestore = $canDeleteAny && $topicIsDeleted;
|
|||
$canDelete = !$topicIsDeleted && (
|
||||
$canDeleteAny || (
|
||||
$topicPostsTotal > 0
|
||||
&& $topicPostsTotal <= MSZ_FORUM_TOPIC_DELETE_POST_LIMIT
|
||||
&& $topicPostsTotal <= $deletePostThreshold
|
||||
&& $canDeleteOwn
|
||||
&& $topic['author_user_id'] === $topicUserId
|
||||
&& $topicInfo->getUserId() === (string)$currentUserId
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -87,58 +125,49 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
|
||||
switch($moderationMode) {
|
||||
case 'delete':
|
||||
$canDeleteCode = forum_topic_can_delete($topic, $topicUserId);
|
||||
$canDeleteMsg = '';
|
||||
$responseCode = 200;
|
||||
|
||||
switch($canDeleteCode) {
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_USER:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'You must be logged in to delete topics.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_TOPIC:
|
||||
$responseCode = 404;
|
||||
$canDeleteMsg = "This topic doesn't exist.";
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_DELETED:
|
||||
$responseCode = 404;
|
||||
$canDeleteMsg = 'This topic has already been marked as deleted.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_OWNER:
|
||||
$responseCode = 403;
|
||||
$canDeleteMsg = 'You can only delete your own topics.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_OLD:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_PERM:
|
||||
$responseCode = 401;
|
||||
$canDeleteMsg = 'You are not allowed to delete topics.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_POSTS:
|
||||
$responseCode = 403;
|
||||
$canDeleteMsg = 'This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.';
|
||||
break;
|
||||
case MSZ_E_FORUM_TOPIC_DELETE_OK:
|
||||
break;
|
||||
default:
|
||||
$responseCode = 500;
|
||||
$canDeleteMsg = sprintf('Unknown error \'%d\'', $canDelete);
|
||||
if($canDeleteAny) {
|
||||
if($topicInfo->isDeleted()) {
|
||||
echo render_info('This topic has already been marked as deleted.', 404);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if($topicInfo->isDeleted()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if($canDeleteCode !== MSZ_E_FORUM_TOPIC_DELETE_OK) {
|
||||
echo render_info($canDeleteMsg, $responseCode);
|
||||
break;
|
||||
if(!$canDeleteOwn) {
|
||||
echo render_info("You aren't allowed to delete topics.", 403);
|
||||
return;
|
||||
}
|
||||
|
||||
if($topicInfo->getUserId() !== $currentUser->getId()) {
|
||||
echo render_info('You can only delete your own topics.', 403);
|
||||
return;
|
||||
}
|
||||
|
||||
// topics may only be deleted within a day of creation, this should be a config value
|
||||
$deleteTimeFrame = 60 * 60 * 24;
|
||||
if($topicInfo->getCreatedTime() < time() - $deleteTimeFrame) {
|
||||
echo render_info('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||
return;
|
||||
}
|
||||
|
||||
// deleted posts are intentionally included
|
||||
$topicPostCount = $forum->countPosts(topicInfo: $topicInfo);
|
||||
if($topicPostCount > $deletePostThreshold) {
|
||||
echo render_info('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($_GET['confirm'])) {
|
||||
Template::render('forum.confirm', [
|
||||
'title' => 'Confirm topic deletion',
|
||||
'class' => 'far fa-trash-alt',
|
||||
'message' => sprintf('You are about to delete topic #%d. Are you sure about that?', $topic['topic_id']),
|
||||
'message' => sprintf('You are about to delete topic #%d. Are you sure about that?', $topicInfo->getId()),
|
||||
'params' => [
|
||||
't' => $topic['topic_id'],
|
||||
't' => $topicInfo->getId(),
|
||||
'm' => 'delete',
|
||||
],
|
||||
]);
|
||||
|
@ -146,23 +175,16 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
} elseif(!$submissionConfirmed) {
|
||||
url_redirect(
|
||||
'forum-topic',
|
||||
['topic' => $topic['topic_id']]
|
||||
['topic' => $topicInfo->getId()]
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$deleteTopic = forum_topic_delete($topic['topic_id']);
|
||||
|
||||
if($deleteTopic)
|
||||
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topic['topic_id']]);
|
||||
|
||||
if(!$deleteTopic) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
$forum->deleteTopic($topicInfo->getId());
|
||||
$msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->getId()]);
|
||||
|
||||
url_redirect('forum-category', [
|
||||
'forum' => $topic['forum_id'],
|
||||
'forum' => $categoryInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
|
||||
|
@ -176,31 +198,25 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
Template::render('forum.confirm', [
|
||||
'title' => 'Confirm topic restore',
|
||||
'class' => 'fas fa-magic',
|
||||
'message' => sprintf('You are about to restore topic #%d. Are you sure about that?', $topic['topic_id']),
|
||||
'message' => sprintf('You are about to restore topic #%d. Are you sure about that?', $topicInfo->getId()),
|
||||
'params' => [
|
||||
't' => $topic['topic_id'],
|
||||
't' => $topicInfo->getId(),
|
||||
'm' => 'restore',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
} elseif(!$submissionConfirmed) {
|
||||
url_redirect('forum-topic', [
|
||||
'topic' => $topic['topic_id'],
|
||||
'topic' => $topicInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$restoreTopic = forum_topic_restore($topic['topic_id']);
|
||||
|
||||
if(!$restoreTopic) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topic['topic_id']]);
|
||||
$forum->restoreTopic($topicInfo->getId());
|
||||
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->getId()]);
|
||||
|
||||
url_redirect('forum-category', [
|
||||
'forum' => $topic['forum_id'],
|
||||
'forum' => $categoryInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
|
||||
|
@ -214,112 +230,139 @@ if(in_array($moderationMode, $validModerationModes, true)) {
|
|||
Template::render('forum.confirm', [
|
||||
'title' => 'Confirm topic nuke',
|
||||
'class' => 'fas fa-radiation',
|
||||
'message' => sprintf('You are about to PERMANENTLY DELETE topic #%d. Are you sure about that?', $topic['topic_id']),
|
||||
'message' => sprintf('You are about to PERMANENTLY DELETE topic #%d. Are you sure about that?', $topicInfo->getId()),
|
||||
'params' => [
|
||||
't' => $topic['topic_id'],
|
||||
't' => $topicInfo->getId(),
|
||||
'm' => 'nuke',
|
||||
],
|
||||
]);
|
||||
break;
|
||||
} elseif(!$submissionConfirmed) {
|
||||
url_redirect('forum-topic', [
|
||||
'topic' => $topic['topic_id'],
|
||||
'topic' => $topicInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$nukeTopic = forum_topic_nuke($topic['topic_id']);
|
||||
|
||||
if(!$nukeTopic) {
|
||||
echo render_error(500);
|
||||
break;
|
||||
}
|
||||
|
||||
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topic['topic_id']]);
|
||||
$forum->nukeTopic($topicInfo->getId());
|
||||
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->getId()]);
|
||||
|
||||
url_redirect('forum-category', [
|
||||
'forum' => $topic['forum_id'],
|
||||
'forum' => $categoryInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'bump':
|
||||
if($canBumpTopic && forum_topic_bump($topic['topic_id'])) {
|
||||
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topic['topic_id']]);
|
||||
if($canBumpTopic) {
|
||||
$forum->bumpTopic($topicInfo->getId());
|
||||
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->getId()]);
|
||||
}
|
||||
|
||||
url_redirect('forum-topic', [
|
||||
'topic' => $topic['topic_id'],
|
||||
'topic' => $topicInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'lock':
|
||||
if($canLockTopic && !$topicIsLocked && forum_topic_lock($topic['topic_id'])) {
|
||||
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topic['topic_id']]);
|
||||
if($canLockTopic && !$topicIsLocked) {
|
||||
$forum->lockTopic($topicInfo->getId());
|
||||
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->getId()]);
|
||||
}
|
||||
|
||||
url_redirect('forum-topic', [
|
||||
'topic' => $topic['topic_id'],
|
||||
'topic' => $topicInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'unlock':
|
||||
if($canLockTopic && $topicIsLocked && forum_topic_unlock($topic['topic_id'])) {
|
||||
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topic['topic_id']]);
|
||||
if($canLockTopic && $topicIsLocked) {
|
||||
$forum->unlockTopic($topicInfo->getId());
|
||||
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->getId()]);
|
||||
}
|
||||
|
||||
url_redirect('forum-topic', [
|
||||
'topic' => $topic['topic_id'],
|
||||
'topic' => $topicInfo->getId(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$topicPosts = $topic['topic_count_posts'];
|
||||
$topicPosts = $topicInfo->getPostsCount();
|
||||
if($canDeleteAny)
|
||||
$topicPosts += $topicInfo->getDeletedPostsCount();
|
||||
|
||||
if($canDeleteAny) {
|
||||
$topicPosts += $topic['topic_count_posts_deleted'];
|
||||
}
|
||||
$topicPagination = new Pagination($topicPosts, 10, 'page');
|
||||
|
||||
$topicPagination = new Pagination($topicPosts, MSZ_FORUM_POSTS_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($preceedingPostCount))
|
||||
$topicPagination->setPage(floor($preceedingPostCount / $topicPagination->getRange()), true);
|
||||
|
||||
if(!$topicPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
Template::set('topic_perms', $perms);
|
||||
|
||||
$posts = forum_post_listing(
|
||||
$topic['topic_id'],
|
||||
$topicPagination->getOffset(),
|
||||
$topicPagination->getRange(),
|
||||
perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST)
|
||||
$postInfos = $forum->getPosts(
|
||||
topicInfo: $topicInfo,
|
||||
deleted: perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST) ? null : false,
|
||||
pagination: $topicPagination,
|
||||
);
|
||||
|
||||
if(!$posts) {
|
||||
if(empty($postInfos)) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$originalPostInfo = $forum->getPost(topicInfo: $topicInfo);
|
||||
|
||||
$userInfos = [];
|
||||
$userColours = [];
|
||||
$userPostsCounts = [];
|
||||
$posts = [];
|
||||
|
||||
foreach($postInfos as $postInfo) {
|
||||
$posts[] = $post = new stdClass;
|
||||
$post->info = $postInfo;
|
||||
|
||||
if($postInfo->hasUserId()) {
|
||||
$postUserId = $postInfo->getUserId();
|
||||
if(!array_key_exists($postUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($postUserId, 'id');
|
||||
$userInfos[$postUserId] = $userInfo;
|
||||
$userColours[$postUserId] = $users->getUserColour($userInfo);
|
||||
$userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
||||
}
|
||||
|
||||
$post->user = $userInfos[$postUserId];
|
||||
$post->colour = $userColours[$postUserId];
|
||||
$post->postsCount = $userPostsCounts[$postUserId];
|
||||
}
|
||||
|
||||
$post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId();
|
||||
$post->isOriginalPoster = $originalPostInfo->hasUserId() && $postInfo->hasUserId()
|
||||
&& $originalPostInfo->getUserId() === $postInfo->getUserId();
|
||||
}
|
||||
|
||||
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && perms_check($perms, MSZ_FORUM_PERM_CREATE_POST);
|
||||
|
||||
forum_topic_mark_read($topicUserId, $topic['topic_id'], $topic['forum_id']);
|
||||
if(!$forum->checkUserHasReadTopic($userInfo, $topicInfo))
|
||||
$forum->incrementTopicView($topicInfo);
|
||||
|
||||
$forum->updateUserReadTopic($currentUser, $topicInfo);
|
||||
|
||||
$perms = perms_check_bulk($perms, [
|
||||
'can_create_post' => MSZ_FORUM_PERM_CREATE_POST,
|
||||
'can_edit_post' => MSZ_FORUM_PERM_EDIT_POST,
|
||||
'can_edit_any_post' => MSZ_FORUM_PERM_EDIT_ANY_POST,
|
||||
'can_delete_post' => MSZ_FORUM_PERM_DELETE_POST,
|
||||
'can_delete_any_post' => MSZ_FORUM_PERM_DELETE_ANY_POST,
|
||||
]);
|
||||
|
||||
Template::render('forum.topic', [
|
||||
'topic_breadcrumbs' => forum_get_breadcrumbs($topic['forum_id']),
|
||||
'global_accent_colour' => forum_get_colour($topic['forum_id']),
|
||||
'topic_info' => $topic,
|
||||
'topic_breadcrumbs' => $forum->getCategoryAncestry($topicInfo),
|
||||
'global_accent_colour' => $forum->getCategoryColour($topicInfo),
|
||||
'topic_info' => $topicInfo,
|
||||
'category_info' => $categoryInfo,
|
||||
'topic_posts' => $posts,
|
||||
'can_reply' => $canReply,
|
||||
'topic_pagination' => $topicPagination,
|
||||
|
@ -327,5 +370,6 @@ Template::render('forum.topic', [
|
|||
'topic_can_nuke_or_restore' => $canNukeOrRestore,
|
||||
'topic_can_bump' => $canBumpTopic,
|
||||
'topic_can_lock' => $canLockTopic,
|
||||
'topic_user_id' => $topicUserId,
|
||||
'topic_user_id' => $currentUserId,
|
||||
'topic_perms' => $perms,
|
||||
]);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$getForum = DB::prepare('
|
||||
SELECT *
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
$getForum->bind('forum_id', (int)($_GET['f'] ?? 0));
|
||||
$forum = $getForum->fetch();
|
||||
|
||||
if(!$forum) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
Template::render('manage.forum.forum', compact('forum'));
|
|
@ -6,7 +6,6 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUs
|
|||
return;
|
||||
}
|
||||
|
||||
$forums = DB::query('SELECT * FROM `msz_forum_categories`')->fetchAll();
|
||||
$rawPerms = perms_create(MSZ_FORUM_PERM_MODES);
|
||||
$perms = manage_forum_perms_list($rawPerms);
|
||||
|
||||
|
@ -16,4 +15,4 @@ if(!empty($_POST['perms']) && is_array($_POST['perms'])) {
|
|||
Template::set('calculated_perms', $finalPerms);
|
||||
}
|
||||
|
||||
Template::render('manage.forum.listing', compact('forums', 'perms'));
|
||||
Template::render('manage.forum.listing', compact('perms'));
|
||||
|
|
|
@ -6,18 +6,17 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUs
|
|||
return;
|
||||
}
|
||||
|
||||
$forum = $msz->getForum();
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if(!CSRF::validateRequest())
|
||||
throw new \Exception("Request verification failed.");
|
||||
|
||||
$rTopicId = (int)filter_input(INPUT_POST, 'topic_redir_id');
|
||||
$rTopicId = (string)filter_input(INPUT_POST, 'topic_redir_id');
|
||||
$rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url'));
|
||||
|
||||
if($rTopicId < 1)
|
||||
throw new \Exception("Invalid topic id.");
|
||||
|
||||
$msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]);
|
||||
forum_topic_redir_create($rTopicId, $msz->getActiveUser()->getId(), $rTopicURL);
|
||||
$forum->createTopicRedirect($rTopicId, $msz->getActiveUser(), $rTopicURL);
|
||||
url_redirect('manage-forum-topic-redirs');
|
||||
return;
|
||||
}
|
||||
|
@ -26,20 +25,20 @@ if(filter_input(INPUT_GET, 'm') === 'explode') {
|
|||
if(!CSRF::validateRequest())
|
||||
throw new \Exception("Request verification failed.");
|
||||
|
||||
$rTopicId = (int)filter_input(INPUT_GET, 't');
|
||||
$rTopicId = (string)filter_input(INPUT_GET, 't');
|
||||
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
|
||||
forum_topic_redir_remove($rTopicId);
|
||||
$forum->deleteTopicRedirect($rTopicId);
|
||||
url_redirect('manage-forum-topic-redirs');
|
||||
return;
|
||||
}
|
||||
|
||||
$pagination = new Pagination(forum_topic_redir_count(), 20);
|
||||
$pagination = new Pagination($forum->countTopicRedirects(), 20);
|
||||
if(!$pagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$redirs = forum_topic_redir_all($pagination->getOffset(), $pagination->getRange());
|
||||
$redirs = $forum->getTopicRedirects(pagination: $pagination);
|
||||
|
||||
Template::render('manage.forum.redirs', [
|
||||
'manage_redirs' => $redirs,
|
||||
|
|
|
@ -12,6 +12,7 @@ if(!$msz->isLoggedIn()) {
|
|||
|
||||
$users = $msz->getUsers();
|
||||
$roles = $msz->getRoles();
|
||||
$forum = $msz->getForum();
|
||||
|
||||
$roleId = filter_has_var(INPUT_GET, 'r') ? (string)filter_input(INPUT_GET, 'r') : null;
|
||||
$orderBy = strtolower((string)filter_input(INPUT_GET, 'ss'));
|
||||
|
@ -97,8 +98,8 @@ foreach($userInfos as $userInfo)
|
|||
$userList[] = [
|
||||
'info' => $userInfo,
|
||||
'colour' => $users->getUserColour($userInfo),
|
||||
'ftopics' => forum_get_user_topic_count($userInfo),
|
||||
'fposts' => forum_get_user_post_count($userInfo),
|
||||
'ftopics' => $forum->countTopics(userInfo: $userInfo, deleted: false),
|
||||
'fposts' => $forum->countPosts(userInfo: $userInfo, deleted: false),
|
||||
];
|
||||
|
||||
if(empty($userList))
|
||||
|
|
|
@ -15,6 +15,7 @@ $profileMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m']
|
|||
$isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['edit'] : !empty($_POST) && is_array($_POST);
|
||||
|
||||
$users = $msz->getUsers();
|
||||
$forum = $msz->getForum();
|
||||
|
||||
$viewerInfo = $msz->getActiveUser();
|
||||
$viewingAsGuest = $viewerInfo === null;
|
||||
|
@ -42,6 +43,23 @@ if($userInfo->isDeleted()) {
|
|||
return;
|
||||
}
|
||||
|
||||
switch($profileMode) {
|
||||
default:
|
||||
echo render_error(404);
|
||||
return;
|
||||
|
||||
case 'forum-topics':
|
||||
url_redirect('search-query', ['query' => sprintf('type:forum:topic author:%s', $userInfo->getName()), 'section' => 'topics']);
|
||||
return;
|
||||
|
||||
case 'forum-posts':
|
||||
url_redirect('search-query', ['query' => sprintf('type:forum:post author:%s', $userInfo->getName()), 'section' => 'posts']);
|
||||
return;
|
||||
|
||||
case '':
|
||||
break;
|
||||
}
|
||||
|
||||
$notices = [];
|
||||
|
||||
$userRank = $users->getUserRank($userInfo);
|
||||
|
@ -324,72 +342,30 @@ $profileStats = DB::prepare('
|
|||
WHERE `user_id` = :user_id
|
||||
')->bind('user_id', $userInfo->getId())->fetch();
|
||||
|
||||
switch($profileMode) {
|
||||
default:
|
||||
echo render_error(404);
|
||||
return;
|
||||
|
||||
case 'forum-topics':
|
||||
$template = 'profile.topics';
|
||||
$topicsCount = forum_topic_count_user($userInfo->getId(), $viewerId);
|
||||
$topicsPagination = new Pagination($topicsCount, 20);
|
||||
|
||||
if(!$topicsPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$topics = forum_topic_listing_user(
|
||||
$userInfo->getId(), $viewerId,
|
||||
$topicsPagination->getOffset(), $topicsPagination->getRange()
|
||||
);
|
||||
|
||||
Template::set([
|
||||
'title' => $userInfo->getName() . ' / topics',
|
||||
'canonical_url' => url('user-profile-forum-topics', ['user' => $userInfo->getId(), 'page' => Pagination::param()]),
|
||||
'profile_topics' => $topics,
|
||||
'profile_topics_pagination' => $topicsPagination,
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'forum-posts':
|
||||
$template = 'profile.posts';
|
||||
$postsCount = forum_post_count_user($userInfo->getId());
|
||||
$postsPagination = new Pagination($postsCount, 20);
|
||||
|
||||
if(!$postsPagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$posts = forum_post_listing(
|
||||
$userInfo->getId(),
|
||||
$postsPagination->getOffset(),
|
||||
$postsPagination->getRange(),
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
Template::set([
|
||||
'title' => $userInfo->getName() . ' / posts',
|
||||
'canonical_url' => url('user-profile-forum-posts', ['user' => $userInfo->getId(), 'page' => Pagination::param()]),
|
||||
'profile_posts' => $posts,
|
||||
'profile_posts_pagination' => $postsPagination,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '':
|
||||
$template = 'profile.index';
|
||||
|
||||
if(!$viewingAsGuest) {
|
||||
Template::set('profile_warnings', $msz->getWarnings()->getWarningsWithDefaultBacklog($userInfo));
|
||||
|
||||
if((!$isBanned || $canEdit)) {
|
||||
$activeCategoryStats = forum_get_user_most_active_category_info($userInfo->getId());
|
||||
$activeCategoryInfo = empty($activeCategoryStats->forum_id) ? null : forum_get($activeCategoryStats->forum_id);
|
||||
$unranked = $cfg->getValues([
|
||||
'forum_leader.unranked.forum:a',
|
||||
'forum_leader.unranked.topic:a',
|
||||
]);
|
||||
|
||||
$activeTopicStats = forum_get_user_most_active_topic_info($userInfo->getId());
|
||||
$activeTopicInfo = empty($activeTopicStats->topic_id) ? null : forum_topic_get($activeTopicStats->topic_id);
|
||||
$activeCategoryStats = $forum->getMostActiveCategoryInfo(
|
||||
$userInfo,
|
||||
$unranked['forum_leader.unranked.forum'],
|
||||
$unranked['forum_leader.unranked.topic'],
|
||||
deleted: false
|
||||
);
|
||||
$activeCategoryInfo = $activeCategoryStats->success ? $forum->getCategory(categoryId: $activeCategoryStats->categoryId) : null;
|
||||
|
||||
$activeTopicStats = $forum->getMostActiveTopicInfo(
|
||||
$userInfo,
|
||||
$unranked['forum_leader.unranked.forum'],
|
||||
$unranked['forum_leader.unranked.topic'],
|
||||
deleted: false
|
||||
);
|
||||
$activeTopicInfo = $activeTopicStats->success ? $forum->getTopic(topicId: $activeTopicStats->topicId) : null;
|
||||
|
||||
$profileFieldValues = $profileFields->getFieldValues($userInfo);
|
||||
$profileFieldInfos = $profileFieldInfos ?? $profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues);
|
||||
|
@ -437,11 +413,8 @@ switch($profileMode) {
|
|||
]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(!empty($template)) {
|
||||
Template::render($template, [
|
||||
Template::render('profile.index', [
|
||||
'profile_viewer' => $viewerInfo,
|
||||
'profile_user' => $userInfo,
|
||||
'profile_colour' => $users->getUserColour($userInfo),
|
||||
|
@ -457,4 +430,3 @@ if(!empty($template)) {
|
|||
'profile_avatar_info' => $avatarInfo,
|
||||
'profile_background_info' => $backgroundInfo,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use stdClass;
|
||||
use RuntimeException;
|
||||
use Index\XArray;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
|
||||
if(!$msz->isLoggedIn()) {
|
||||
|
@ -11,35 +13,168 @@ if(!$msz->isLoggedIn()) {
|
|||
|
||||
$searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
|
||||
|
||||
if(!empty($searchQuery)) {
|
||||
$forumTopics = forum_topic_listing_search($searchQuery, $msz->getActiveUser()->getId());
|
||||
$forumPosts = forum_post_search($searchQuery);
|
||||
$searchQueryEvaluated = ['query' => []];
|
||||
Template::addFunction('search_merge_query', function($attrs) use (&$searchQueryEvaluated) {
|
||||
$existing = [];
|
||||
|
||||
// this sure is an expansion
|
||||
$news = $msz->getNews();
|
||||
if(!empty($attrs['type']))
|
||||
$existing[] = 'type:' . $attrs['type'];
|
||||
elseif(!empty($searchQueryEvaluated['type']))
|
||||
$existing[] = 'type:' . $searchQueryEvaluated['type'];
|
||||
|
||||
if(!empty($attrs['author']))
|
||||
$existing[] = 'author:' . $attrs['author'];
|
||||
elseif(!empty($searchQueryEvaluated['author']))
|
||||
$existing[] = 'author:' . $searchQueryEvaluated['author']->getName();
|
||||
|
||||
if(!empty($attrs['after']))
|
||||
$existing[] = 'after:' . $attrs['after'];
|
||||
elseif(!empty($searchQueryEvaluated['after']))
|
||||
$existing[] = 'after:' . $searchQueryEvaluated['after'];
|
||||
|
||||
$existing = array_merge($existing, array_unique(array_merge(
|
||||
$searchQueryEvaluated['query'],
|
||||
empty($attrs['query']) ? [] : explode(' ', $attrs['query'])
|
||||
)));
|
||||
|
||||
return rawurlencode(implode(' ', $existing));
|
||||
});
|
||||
if(!empty($searchQuery)) {
|
||||
$users = $msz->getUsers();
|
||||
$forum = $msz->getForum();
|
||||
$news = $msz->getNews();
|
||||
$comments = $msz->getComments();
|
||||
|
||||
$userInfos = [];
|
||||
$userColours = [];
|
||||
|
||||
$searchQueryAttributes = ['type', 'author', 'after'];
|
||||
$searchQueryParts = explode(' ', $searchQuery);
|
||||
foreach($searchQueryParts as $queryPart) {
|
||||
$queryPart = trim($queryPart);
|
||||
if($queryPart === '')
|
||||
continue;
|
||||
|
||||
$colonIndex = strpos($queryPart, ':');
|
||||
if($colonIndex !== false && $colonIndex > 0) {
|
||||
$attrName = substr($queryPart, 0, $colonIndex);
|
||||
if(in_array($attrName, $searchQueryAttributes)) {
|
||||
$attrValue = substr($queryPart, $colonIndex + 1);
|
||||
$searchQueryEvaluated[$attrName] = $attrValue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$searchQueryEvaluated['query'][] = $queryPart;
|
||||
}
|
||||
|
||||
$searchQueryEvaluated['query_string'] = implode(' ', $searchQueryEvaluated['query']);
|
||||
|
||||
if(!empty($searchQueryEvaluated['author']))
|
||||
try {
|
||||
$searchQueryEvaluated['author'] = $users->getUser($searchQueryEvaluated['author'], 'search');
|
||||
} catch(RuntimeException $ex) {
|
||||
unset($searchQueryEvaluated['author']);
|
||||
}
|
||||
|
||||
if(empty($searchQueryEvaluated['type']) || str_starts_with($searchQueryEvaluated['type'], 'forum')) {
|
||||
$currentUser = $msz->getActiveUser();
|
||||
$currentUserId = $currentUser === null ? 0 : (int)$currentUser->getId();
|
||||
|
||||
$forumCategoryIds = XArray::where(
|
||||
$forum->getCategories(hidden: false),
|
||||
fn($categoryInfo) => $categoryInfo->mayHaveTopics() && forum_perms_check_user(MSZ_FORUM_PERMS_GENERAL, $categoryInfo->getId(), $currentUserId, MSZ_FORUM_PERM_VIEW_FORUM)
|
||||
);
|
||||
|
||||
$forumTopicInfos = $forum->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated);
|
||||
$forumTopics = [];
|
||||
|
||||
foreach($forumTopicInfos as $topicInfo) {
|
||||
$forumTopics[] = $topic = new stdClass;
|
||||
$topic->info = $topicInfo;
|
||||
$topic->unread = $forum->checkTopicUnread($topicInfo, $currentUser);
|
||||
$topic->participated = $forum->checkTopicParticipated($topicInfo, $currentUser);
|
||||
$topic->lastPost = new stdClass;
|
||||
|
||||
if($topicInfo->hasUserId()) {
|
||||
$lastTopicUserId = $topicInfo->getUserId();
|
||||
if(!array_key_exists($lastTopicUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastTopicUserId, 'id');
|
||||
$userInfos[$lastTopicUserId] = $userInfo;
|
||||
$userColours[$lastTopicUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$topic->user = $userInfos[$lastTopicUserId];
|
||||
$topic->colour = $userColours[$lastTopicUserId];
|
||||
}
|
||||
|
||||
try {
|
||||
$topic->lastPost->info = $lastPostInfo = $forum->getPost(
|
||||
topicInfo: $topicInfo,
|
||||
getLast: true,
|
||||
deleted: $topicInfo->isDeleted() ? null : false,
|
||||
);
|
||||
|
||||
if($lastPostInfo->hasUserId()) {
|
||||
$lastPostUserId = $lastPostInfo->getUserId();
|
||||
if(!array_key_exists($lastPostUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($lastPostUserId, 'id');
|
||||
$userInfos[$lastPostUserId] = $userInfo;
|
||||
$userColours[$lastPostUserId] = $users->getUserColour($userInfo);
|
||||
}
|
||||
|
||||
$topic->lastPost->user = $userInfos[$lastPostUserId];
|
||||
$topic->lastPost->colour = $userColours[$lastPostUserId];
|
||||
}
|
||||
} catch(RuntimeException $ex) {}
|
||||
}
|
||||
|
||||
$forumPostInfos = $forum->getPosts(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated);
|
||||
$forumPosts = [];
|
||||
|
||||
foreach($forumPostInfos as $postInfo) {
|
||||
$forumPosts[] = $post = new stdClass;
|
||||
$post->info = $postInfo;
|
||||
|
||||
if($postInfo->hasUserId()) {
|
||||
$postUserId = $postInfo->getUserId();
|
||||
if(!array_key_exists($postUserId, $userInfos)) {
|
||||
$userInfo = $users->getUser($postUserId, 'id');
|
||||
$userInfos[$postUserId] = $userInfo;
|
||||
$userColours[$postUserId] = $users->getUserColour($userInfo);
|
||||
$userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $userInfo, deleted: false);
|
||||
}
|
||||
|
||||
$post->user = $userInfos[$postUserId];
|
||||
$post->colour = $userColours[$postUserId];
|
||||
$post->postsCount = null;
|
||||
}
|
||||
|
||||
// can't be bothered sorry
|
||||
$post->isOriginalPost = false;
|
||||
$post->isOriginalPoster = false;
|
||||
}
|
||||
}
|
||||
|
||||
$newsPosts = [];
|
||||
$newsPostInfos = $news->getPosts(searchQuery: $searchQuery);
|
||||
$newsUserInfos = [];
|
||||
$newsUserColours = [];
|
||||
$newsPostInfos = empty($searchQueryEvaluated['type']) || $searchQueryEvaluated['type'] === 'news' ? $news->getPosts(searchQuery: $searchQuery) : [];
|
||||
$newsCategoryInfos = [];
|
||||
|
||||
foreach($newsPostInfos as $postInfo) {
|
||||
$userId = $postInfo->getUserId();
|
||||
$categoryId = $postInfo->getCategoryId();
|
||||
|
||||
if(array_key_exists($userId, $newsUserInfos)) {
|
||||
$userInfo = $newsUserInfos[$userId];
|
||||
if(array_key_exists($userId, $userInfos)) {
|
||||
$userInfo = $userInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = $users->getUser($userId, 'id');
|
||||
$newsUserColours[$userId] = $users->getUserColour($userInfo);
|
||||
$userColours[$userId] = $users->getUserColour($userInfo);
|
||||
} catch(RuntimeException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$newsUserInfos[$userId] = $userInfo;
|
||||
$userInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
if(array_key_exists($categoryId, $newsCategoryInfos))
|
||||
|
@ -54,11 +189,12 @@ if(!empty($searchQuery)) {
|
|||
'post' => $postInfo,
|
||||
'category' => $categoryInfo,
|
||||
'user' => $userInfo,
|
||||
'user_colour' => $newsUserColours[$userId] ?? \Index\Colour\Colour::none(),
|
||||
'user_colour' => $userColours[$userId] ?? \Index\Colour\Colour::none(),
|
||||
'comments_count' => $commentsCount,
|
||||
];
|
||||
}
|
||||
|
||||
if(empty($searchQueryEvaluated['type']) || $searchQueryEvaluated['type'] === 'member') {
|
||||
$findUsers = DB::prepare('
|
||||
SELECT u.`user_id`, u.`username`, u.`user_country`,
|
||||
u.`user_created`, u.`user_active`, r.`role_id`,
|
||||
|
@ -87,6 +223,7 @@ if(!empty($searchQuery)) {
|
|||
$findUsers->bind('query', $searchQuery);
|
||||
$userList = $findUsers->fetchAll();
|
||||
}
|
||||
}
|
||||
|
||||
Template::render('home.search', [
|
||||
'search_query' => $searchQuery,
|
||||
|
|
1558
src/Forum/Forum.php
Normal file
1558
src/Forum/Forum.php
Normal file
File diff suppressed because it is too large
Load diff
194
src/Forum/ForumCategoryInfo.php
Normal file
194
src/Forum/ForumCategoryInfo.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Colour\Colour;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class ForumCategoryInfo {
|
||||
// should the types just be replaced with flags indicating what's allowed?
|
||||
public const TYPE_DISCUSSION = 0;
|
||||
public const TYPE_LISTING = 1;
|
||||
public const TYPE_LINK = 2;
|
||||
|
||||
public const TYPE_ALIASES = [
|
||||
'discussion' => self::TYPE_DISCUSSION,
|
||||
'listing' => self::TYPE_LISTING,
|
||||
'link' => self::TYPE_LINK,
|
||||
];
|
||||
|
||||
public const MAY_HAVE_CHILDREN = [
|
||||
self::TYPE_DISCUSSION,
|
||||
self::TYPE_LISTING,
|
||||
];
|
||||
|
||||
public const MAY_HAVE_TOPICS = [
|
||||
self::TYPE_DISCUSSION,
|
||||
];
|
||||
|
||||
private string $id;
|
||||
private int $order;
|
||||
private ?string $parentId;
|
||||
private string $name;
|
||||
private int $type;
|
||||
private ?string $desc;
|
||||
private ?string $icon;
|
||||
private ?int $colour;
|
||||
private ?string $link;
|
||||
private ?int $clicks;
|
||||
private int $created;
|
||||
private bool $archived;
|
||||
private bool $hidden;
|
||||
private int $topicsCount;
|
||||
private int $postsCount;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->order = $result->getInteger(1);
|
||||
$this->parentId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->name = $result->getString(3);
|
||||
$this->type = $result->getInteger(4);
|
||||
$this->desc = $result->isNull(5) ? null : $result->getString(5);
|
||||
$this->icon = $result->isNull(6) ? null : $result->getString(6);
|
||||
$this->colour = $result->isNull(7) ? null : $result->getInteger(7);
|
||||
$this->link = $result->isNull(8) ? null : $result->getString(8);
|
||||
$this->clicks = $result->isNull(9) ? null : $result->getInteger(9);
|
||||
$this->created = $result->getInteger(10);
|
||||
$this->archived = $result->getInteger(11) !== 0;
|
||||
$this->hidden = $result->getInteger(12) !== 0;
|
||||
$this->topicsCount = $result->getInteger(13);
|
||||
$this->postsCount = $result->getInteger(14);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getOrder(): int {
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
public function hasParent(): bool {
|
||||
return $this->parentId !== null && $this->parentId !== '0';
|
||||
}
|
||||
|
||||
public function getParentId(): ?string {
|
||||
return $this->parentId;
|
||||
}
|
||||
|
||||
public function isDirectChildOf(ForumCategoryInfo|string $parentInfo): bool {
|
||||
if($parentInfo instanceof ForumCategoryInfo)
|
||||
$parentInfo = $parentInfo->getId();
|
||||
return $this->hasParent() && $this->getParentId() === $parentInfo;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getType(): int {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isDiscussion(): bool {
|
||||
return $this->type === self::TYPE_DISCUSSION;
|
||||
}
|
||||
|
||||
public function isListing(): bool {
|
||||
return $this->type === self::TYPE_LISTING;
|
||||
}
|
||||
|
||||
public function isLink(): bool {
|
||||
return $this->type === self::TYPE_LINK;
|
||||
}
|
||||
|
||||
public function mayHaveChildren(): bool {
|
||||
return in_array($this->type, self::MAY_HAVE_CHILDREN);
|
||||
}
|
||||
|
||||
public function mayHaveTopics(): bool {
|
||||
return in_array($this->type, self::MAY_HAVE_TOPICS);
|
||||
}
|
||||
|
||||
public function hasDescription(): bool {
|
||||
return $this->desc !== null && $this->desc !== '';
|
||||
}
|
||||
|
||||
public function getDescription(): ?string {
|
||||
return $this->desc;
|
||||
}
|
||||
|
||||
public function hasIcon(): bool {
|
||||
return $this->icon !== null && $this->icon !== '';
|
||||
}
|
||||
|
||||
public function getIcon(): ?string {
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function getIconForDisplay(): string {
|
||||
if($this->hasIcon())
|
||||
return $this->getIcon();
|
||||
|
||||
if($this->isArchived())
|
||||
return 'fas fa-archive fa-fw';
|
||||
|
||||
return match($this->type) {
|
||||
self::TYPE_LISTING => 'fas fa-folder fa-fw',
|
||||
self::TYPE_LINK => 'fas fa-link fa-fw',
|
||||
default => 'fas fa-comments fa-fw',
|
||||
};
|
||||
}
|
||||
|
||||
public function hasColour(): bool {
|
||||
return $this->colour !== null && ($this->colour & 0x40000000) === 0;
|
||||
}
|
||||
|
||||
public function getColourRaw(): ?int {
|
||||
return $this->colour;
|
||||
}
|
||||
|
||||
public function getColour(): Colour {
|
||||
return $this->colour === null ? Colour::none() : Colour::fromMisuzu($this->colour);
|
||||
}
|
||||
|
||||
public function hasLinkTarget(): bool {
|
||||
return $this->link !== null && $this->link !== '';
|
||||
}
|
||||
|
||||
public function getLinkTarget(): ?string {
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
public function hasLinkClicks(): bool {
|
||||
return $this->clicks !== null;
|
||||
}
|
||||
|
||||
public function getLinkClicks(): ?int {
|
||||
return $this->clicks;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function isArchived(): bool {
|
||||
return $this->archived;
|
||||
}
|
||||
|
||||
public function isHidden(): bool {
|
||||
return $this->hidden;
|
||||
}
|
||||
|
||||
public function getTopicsCount(): int {
|
||||
return $this->topicsCount;
|
||||
}
|
||||
|
||||
public function getPostsCount(): int {
|
||||
return $this->postsCount;
|
||||
}
|
||||
}
|
135
src/Forum/ForumPostInfo.php
Normal file
135
src/Forum/ForumPostInfo.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Net\IPAddress;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
class ForumPostInfo {
|
||||
private string $id;
|
||||
private string $topicId;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private string $remoteAddr;
|
||||
private string $body;
|
||||
private int $parser;
|
||||
private bool $displaySignature;
|
||||
private int $created;
|
||||
private ?int $edited;
|
||||
private ?int $deleted;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->topicId = (string)$result->getInteger(1);
|
||||
$this->categoryId = (string)$result->getInteger(2);
|
||||
$this->userId = $result->isNull(3) ? null : (string)$result->getInteger(3);
|
||||
$this->remoteAddr = $result->getString(4);
|
||||
$this->body = $result->getString(5);
|
||||
$this->parser = $result->getInteger(6);
|
||||
$this->displaySignature = $result->getInteger(7) !== 0;
|
||||
$this->created = $result->getInteger(8);
|
||||
$this->edited = $result->isNull(9) ? null : $result->getInteger(9);
|
||||
$this->deleted = $result->isNull(10) ? null : $result->getInteger(10);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getTopicId(): string {
|
||||
return $this->topicId;
|
||||
}
|
||||
|
||||
public function getCategoryId(): string {
|
||||
return $this->categoryId;
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
return $this->userId !== null;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string {
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getRemoteAddressRaw(): string {
|
||||
return $this->remoteAddr;
|
||||
}
|
||||
|
||||
public function getRemoteAddress(): IPAddress {
|
||||
return IPAddress::parse($this->remoteAddr);
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getParser(): int {
|
||||
return $this->parser;
|
||||
}
|
||||
|
||||
public function isBodyPlain(): bool {
|
||||
return $this->parser === Parser::PLAIN;
|
||||
}
|
||||
|
||||
public function isBodyBBCode(): bool {
|
||||
return $this->parser === Parser::BBCODE;
|
||||
}
|
||||
|
||||
public function isBodyMarkdown(): bool {
|
||||
return $this->parser === Parser::MARKDOWN;
|
||||
}
|
||||
|
||||
public function shouldDisplaySignature(): bool {
|
||||
return $this->displaySignature;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
private static ?DateTime $markAsEditedThreshold = null;
|
||||
public function shouldMarkAsEdited(): bool {
|
||||
if(self::$markAsEditedThreshold === null)
|
||||
self::$markAsEditedThreshold = DateTime::now()->modify('-5 minutes');
|
||||
|
||||
return $this->getCreatedAt()->isMoreThanOrEqual(self::$markAsEditedThreshold);
|
||||
}
|
||||
|
||||
public function isEdited(): bool {
|
||||
return $this->edited !== null;
|
||||
}
|
||||
|
||||
public function getEditedTime(): ?int {
|
||||
return $this->edited;
|
||||
}
|
||||
|
||||
public function getEditedAt(): ?DateTime {
|
||||
return $this->edited === null ? null : DateTime::fromUnixTimeSeconds($this->edited);
|
||||
}
|
||||
|
||||
private static ?DateTime $canBeDeletedThreshold = null;
|
||||
public function canBeDeleted(): bool {
|
||||
if(self::$canBeDeletedThreshold === null)
|
||||
self::$canBeDeletedThreshold = DateTime::now()->modify('-1 week');
|
||||
|
||||
return $this->getCreatedAt()->isMoreThanOrEqual(self::$canBeDeletedThreshold);
|
||||
}
|
||||
|
||||
public function isDeleted(): bool {
|
||||
return $this->deleted !== null;
|
||||
}
|
||||
|
||||
public function getDeletedTime(): ?int {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): ?DateTime {
|
||||
return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted);
|
||||
}
|
||||
}
|
170
src/Forum/ForumTopicInfo.php
Normal file
170
src/Forum/ForumTopicInfo.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class ForumTopicInfo {
|
||||
public const TYPE_DISCUSSION = 0;
|
||||
public const TYPE_STICKY = 1;
|
||||
public const TYPE_ANNOUNCE = 2;
|
||||
public const TYPE_GLOBAL = 3;
|
||||
|
||||
public const TYPE_ALIASES = [
|
||||
'discussion' => self::TYPE_DISCUSSION,
|
||||
'sticky' => self::TYPE_STICKY,
|
||||
'announce' => self::TYPE_ANNOUNCE,
|
||||
'global' => self::TYPE_GLOBAL,
|
||||
];
|
||||
|
||||
private string $id;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private int $type;
|
||||
private string $title;
|
||||
private int $postsCount;
|
||||
private int $deletedPostsCount;
|
||||
private int $viewsCount;
|
||||
private int $created;
|
||||
private int $bumped;
|
||||
private ?int $deleted;
|
||||
private ?int $locked;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->categoryId = (string)$result->getInteger(1);
|
||||
$this->userId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->type = $result->getInteger(3);
|
||||
$this->title = $result->getString(4);
|
||||
$this->viewsCount = $result->getInteger(5);
|
||||
$this->created = $result->getInteger(6);
|
||||
$this->bumped = $result->getInteger(7);
|
||||
$this->deleted = $result->isNull(8) ? null : $result->getInteger(8);
|
||||
$this->locked = $result->isNull(9) ? null : $result->getInteger(9);
|
||||
$this->postsCount = $result->getInteger(10);
|
||||
$this->deletedPostsCount = $result->getInteger(11);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getCategoryId(): string {
|
||||
return $this->categoryId;
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
return $this->userId !== null;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string {
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getType(): int {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isDiscussion(): bool {
|
||||
return $this->type === self::TYPE_DISCUSSION;
|
||||
}
|
||||
|
||||
public function isSticky(): bool {
|
||||
return $this->type === self::TYPE_STICKY;
|
||||
}
|
||||
|
||||
public function isAnnouncement(): bool {
|
||||
return $this->type === self::TYPE_ANNOUNCE;
|
||||
}
|
||||
|
||||
public function isGlobalAnnouncement(): bool {
|
||||
return $this->type === self::TYPE_GLOBAL;
|
||||
}
|
||||
|
||||
public function isImportant(): bool {
|
||||
return $this->isSticky()
|
||||
|| $this->isAnnouncement()
|
||||
|| $this->isGlobalAnnouncement();
|
||||
}
|
||||
|
||||
public function getIconForDisplay(bool $unread = false): string {
|
||||
if($this->isDeleted())
|
||||
return 'fas fa-trash-alt fa-fw';
|
||||
if($this->isAnnouncement() || $this->isGlobalAnnouncement())
|
||||
return 'fas fa-bullhorn fa-fw';
|
||||
if($this->isSticky())
|
||||
return 'fas fa-thumbtack fa-fw';
|
||||
if($this->isLocked())
|
||||
return 'fas fa-lock fa-fw';
|
||||
return sprintf('%s fa-comment fa-fw', $unread ? 'fas' : 'far');
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getPostsCount(): int {
|
||||
return $this->postsCount;
|
||||
}
|
||||
|
||||
public function getDeletedPostsCount(): int {
|
||||
return $this->deletedPostsCount;
|
||||
}
|
||||
|
||||
public function getTotalPostsCount(): int {
|
||||
return $this->postsCount + $this->deletedPostsCount;
|
||||
}
|
||||
|
||||
public function getViewsCount(): int {
|
||||
return $this->viewsCount;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
private static ?DateTime $lastActiveAt = null;
|
||||
|
||||
public function isActive(): bool {
|
||||
if(self::$lastActiveAt === null)
|
||||
self::$lastActiveAt = DateTime::now()->modify('-1 month');
|
||||
|
||||
return $this->getBumpedAt()->isMoreThanOrEqual(self::$lastActiveAt);
|
||||
}
|
||||
|
||||
public function getBumpedTime(): int {
|
||||
return $this->bumped;
|
||||
}
|
||||
|
||||
public function getBumpedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->bumped);
|
||||
}
|
||||
|
||||
public function isDeleted(): bool {
|
||||
return $this->deleted !== null;
|
||||
}
|
||||
|
||||
public function getDeletedTime(): ?int {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): ?DateTime {
|
||||
return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted);
|
||||
}
|
||||
|
||||
public function isLocked(): bool {
|
||||
return $this->locked !== null;
|
||||
}
|
||||
|
||||
public function getLockedTime(): ?int {
|
||||
return $this->locked;
|
||||
}
|
||||
|
||||
public function getLockedAt(): ?DateTime {
|
||||
return $this->locked === null ? null : DateTime::fromUnixTimeSeconds($this->locked);
|
||||
}
|
||||
}
|
43
src/Forum/ForumTopicRedirectInfo.php
Normal file
43
src/Forum/ForumTopicRedirectInfo.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
namespace Misuzu\Forum;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class ForumTopicRedirectInfo {
|
||||
private string $topicId;
|
||||
private ?string $userId;
|
||||
private string $link;
|
||||
private string $created;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->topicId = (string)$result->getInteger(0);
|
||||
$this->userId = $result->isNull(1) ? null : (string)$result->getInteger(1);
|
||||
$this->link = $result->getString(2);
|
||||
$this->created = $result->getInteger(3);
|
||||
}
|
||||
|
||||
public function getTopicId(): string {
|
||||
return $this->topicId;
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
return $this->userId !== null;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string {
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getLinkTarget(): string {
|
||||
return $this->link;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
}
|
|
@ -1,586 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_FORUM_TYPE_DISCUSSION', 0);
|
||||
define('MSZ_FORUM_TYPE_CATEGORY', 1);
|
||||
define('MSZ_FORUM_TYPE_LINK', 2);
|
||||
define('MSZ_FORUM_TYPES', [
|
||||
MSZ_FORUM_TYPE_DISCUSSION,
|
||||
MSZ_FORUM_TYPE_CATEGORY,
|
||||
MSZ_FORUM_TYPE_LINK,
|
||||
]);
|
||||
|
||||
define('MSZ_FORUM_MAY_HAVE_CHILDREN', [
|
||||
MSZ_FORUM_TYPE_DISCUSSION,
|
||||
MSZ_FORUM_TYPE_CATEGORY,
|
||||
]);
|
||||
|
||||
define('MSZ_FORUM_MAY_HAVE_TOPICS', [
|
||||
MSZ_FORUM_TYPE_DISCUSSION,
|
||||
]);
|
||||
|
||||
define('MSZ_FORUM_ROOT', 0);
|
||||
define('MSZ_FORUM_ROOT_DATA', [ // should be compatible with the data fetched in forum_get_root_categories
|
||||
'forum_id' => MSZ_FORUM_ROOT,
|
||||
'forum_name' => 'Forums',
|
||||
'forum_children' => 0,
|
||||
'forum_type' => MSZ_FORUM_TYPE_CATEGORY,
|
||||
'forum_colour' => null,
|
||||
'forum_permissions' => MSZ_FORUM_PERM_SET_READ,
|
||||
]);
|
||||
|
||||
function forum_is_valid_type(int $type): bool {
|
||||
return in_array($type, MSZ_FORUM_TYPES, true);
|
||||
}
|
||||
|
||||
function forum_may_have_children(int $forumType): bool {
|
||||
return in_array($forumType, MSZ_FORUM_MAY_HAVE_CHILDREN);
|
||||
}
|
||||
|
||||
function forum_may_have_topics(int $forumType): bool {
|
||||
return in_array($forumType, MSZ_FORUM_MAY_HAVE_TOPICS);
|
||||
}
|
||||
|
||||
function forum_get(int $forumId, bool $showDeleted = false): array {
|
||||
$getForum = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
`forum_id`, `forum_name`, `forum_type`, `forum_link`, `forum_archived`,
|
||||
`forum_link_clicks`, `forum_parent`, `forum_colour`, `forum_icon`,
|
||||
(
|
||||
SELECT COUNT(`topic_id`)
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `forum_id` = f.`forum_id`
|
||||
%1$s
|
||||
) as `forum_topic_count`
|
||||
FROM `msz_forum_categories` as f
|
||||
WHERE `forum_id` = :forum_id
|
||||
',
|
||||
$showDeleted ? '' : 'AND `topic_deleted` IS NULL'
|
||||
));
|
||||
$getForum->bind('forum_id', $forumId);
|
||||
return $getForum->fetch();
|
||||
}
|
||||
|
||||
function forum_get_root_categories(int $userId): array {
|
||||
$getCategories = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
f.`forum_id`, f.`forum_name`, f.`forum_type`, f.`forum_colour`, f.`forum_icon`,
|
||||
(
|
||||
SELECT COUNT(`forum_id`)
|
||||
FROM `msz_forum_categories` AS sf
|
||||
WHERE sf.`forum_parent` = f.`forum_id`
|
||||
) AS `forum_children`
|
||||
FROM `msz_forum_categories` AS f
|
||||
WHERE f.`forum_parent` = 0
|
||||
AND f.`forum_type` = %1$d
|
||||
AND f.`forum_hidden` = 0
|
||||
GROUP BY f.`forum_id`
|
||||
ORDER BY f.`forum_order`
|
||||
',
|
||||
MSZ_FORUM_TYPE_CATEGORY
|
||||
));
|
||||
$categories = array_merge([MSZ_FORUM_ROOT_DATA], $getCategories->fetchAll());
|
||||
|
||||
$getRootForumCount = \Misuzu\DB::prepare(sprintf(
|
||||
"
|
||||
SELECT COUNT(`forum_id`)
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_parent` = %d
|
||||
AND `forum_type` != %d
|
||||
",
|
||||
MSZ_FORUM_ROOT,
|
||||
MSZ_FORUM_TYPE_CATEGORY
|
||||
));
|
||||
$categories[0]['forum_children'] = (int)$getRootForumCount->fetchColumn();
|
||||
|
||||
foreach($categories as $key => $category) {
|
||||
$categories[$key]['forum_permissions'] = $perms = forum_perms_get_user($category['forum_id'], $userId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_SET_READ)) {
|
||||
unset($categories[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$categories[$key] = array_merge(
|
||||
$category,
|
||||
['forum_unread' => forum_topics_unread($category['forum_id'], $userId)],
|
||||
forum_latest_post($category['forum_id'], $userId)
|
||||
);
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
function forum_get_breadcrumbs(
|
||||
int $forumId,
|
||||
string $linkFormat = '/forum/forum.php?f=%d',
|
||||
string $rootFormat = '/forum/#f%d',
|
||||
array $indexLink = ['Forums' => '/forum/']
|
||||
): array {
|
||||
$breadcrumbs = [];
|
||||
$getBreadcrumb = \Misuzu\DB::prepare('
|
||||
SELECT `forum_id`, `forum_name`, `forum_type`, `forum_parent`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
|
||||
while($forumId > 0) {
|
||||
$getBreadcrumb->bind('forum_id', $forumId);
|
||||
$breadcrumb = $getBreadcrumb->fetch();
|
||||
|
||||
if(empty($breadcrumb)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$breadcrumbs[$breadcrumb['forum_name']] = sprintf(
|
||||
$breadcrumb['forum_parent'] === MSZ_FORUM_ROOT
|
||||
&& $breadcrumb['forum_type'] === MSZ_FORUM_TYPE_CATEGORY
|
||||
? $rootFormat
|
||||
: $linkFormat,
|
||||
$breadcrumb['forum_id']
|
||||
);
|
||||
$forumId = $breadcrumb['forum_parent'];
|
||||
}
|
||||
|
||||
return array_reverse($breadcrumbs + $indexLink);
|
||||
}
|
||||
|
||||
function forum_get_colour(int $forumId): int {
|
||||
$getColours = \Misuzu\DB::prepare('
|
||||
SELECT `forum_id`, `forum_parent`, `forum_colour`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
|
||||
while($forumId > 0) {
|
||||
$getColours->bind('forum_id', $forumId);
|
||||
$colourInfo = $getColours->fetch();
|
||||
|
||||
if(empty($colourInfo)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!empty($colourInfo['forum_colour'])) {
|
||||
return $colourInfo['forum_colour'];
|
||||
}
|
||||
|
||||
$forumId = $colourInfo['forum_parent'];
|
||||
}
|
||||
|
||||
return 0x40000000;
|
||||
}
|
||||
|
||||
function forum_increment_clicks(int $forumId): void {
|
||||
$incrementLinkClicks = \Misuzu\DB::prepare(sprintf('
|
||||
UPDATE `msz_forum_categories`
|
||||
SET `forum_link_clicks` = `forum_link_clicks` + 1
|
||||
WHERE `forum_id` = :forum_id
|
||||
AND `forum_type` = %d
|
||||
AND `forum_link_clicks` IS NOT NULL
|
||||
', MSZ_FORUM_TYPE_LINK));
|
||||
$incrementLinkClicks->bind('forum_id', $forumId);
|
||||
$incrementLinkClicks->execute();
|
||||
}
|
||||
|
||||
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 0;
|
||||
|
||||
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_latest_post(int $forumId, int $userId): array {
|
||||
if($forumId < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
static $memoized = [];
|
||||
$memoId = "{$forumId}-{$userId}";
|
||||
|
||||
if(array_key_exists($memoId, $memoized)) {
|
||||
return $memoized[$memoId];
|
||||
}
|
||||
|
||||
if(!forum_perms_check_user(MSZ_FORUM_PERMS_GENERAL, $forumId, $userId, MSZ_FORUM_PERM_SET_READ)) {
|
||||
return $memoized[$memoId] = [];
|
||||
}
|
||||
|
||||
$getLastPost = \Misuzu\DB::prepare('
|
||||
SELECT
|
||||
p.`post_id` AS `recent_post_id`, t.`topic_id` AS `recent_topic_id`,
|
||||
t.`topic_title` AS `recent_topic_title`, t.`topic_bumped` AS `recent_topic_bumped`,
|
||||
p.`post_created` AS `recent_post_created`,
|
||||
u.`user_id` AS `recent_post_user_id`,
|
||||
u.`username` AS `recent_post_username`,
|
||||
COALESCE(u.`user_colour`, r.`role_colour`) AS `recent_post_user_colour`,
|
||||
UNIX_TIMESTAMP(p.`post_created`) AS `post_created_unix`
|
||||
FROM `msz_forum_posts` AS p
|
||||
LEFT JOIN `msz_forum_topics` AS t
|
||||
ON t.`topic_id` = p.`topic_id`
|
||||
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 p.`forum_id` = :forum_id
|
||||
AND p.`post_deleted` IS NULL
|
||||
ORDER BY p.`post_id` DESC
|
||||
');
|
||||
$getLastPost->bind('forum_id', $forumId);
|
||||
$currentLast = $getLastPost->fetch();
|
||||
|
||||
$children = forum_get_child_ids($forumId);
|
||||
|
||||
foreach($children as $child) {
|
||||
$lastPost = forum_latest_post($child, $userId);
|
||||
|
||||
if(($currentLast['post_created_unix'] ?? 0) < ($lastPost['post_created_unix'] ?? 0)) {
|
||||
$currentLast = $lastPost;
|
||||
}
|
||||
}
|
||||
|
||||
return $memoized[$memoId] = $currentLast;
|
||||
}
|
||||
|
||||
function forum_get_children(int $parentId, int $userId): array {
|
||||
$getListing = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
:user_id AS `target_user_id`,
|
||||
f.`forum_id`, f.`forum_name`, f.`forum_description`, f.`forum_type`, f.`forum_icon`,
|
||||
f.`forum_link`, f.`forum_link_clicks`, f.`forum_archived`, f.`forum_colour`,
|
||||
f.`forum_count_topics`, f.`forum_count_posts`
|
||||
FROM `msz_forum_categories` AS f
|
||||
WHERE f.`forum_parent` = :parent_id
|
||||
AND f.`forum_hidden` = 0
|
||||
AND (
|
||||
(f.`forum_parent` = %1$d AND f.`forum_type` != %2$d)
|
||||
OR f.`forum_parent` != %1$d
|
||||
)
|
||||
GROUP BY f.`forum_id`
|
||||
ORDER BY f.`forum_order`
|
||||
',
|
||||
MSZ_FORUM_ROOT,
|
||||
MSZ_FORUM_TYPE_CATEGORY
|
||||
));
|
||||
|
||||
$getListing->bind('user_id', $userId);
|
||||
$getListing->bind('parent_id', $parentId);
|
||||
|
||||
$listing = $getListing->fetchAll();
|
||||
|
||||
foreach($listing as $key => $forum) {
|
||||
$listing[$key]['forum_permissions'] = $perms = forum_perms_get_user($forum['forum_id'], $userId)[MSZ_FORUM_PERMS_GENERAL];
|
||||
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_SET_READ)) {
|
||||
unset($listing[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$listing[$key] = array_merge(
|
||||
$forum,
|
||||
['forum_unread' => forum_topics_unread($forum['forum_id'], $userId)],
|
||||
forum_latest_post($forum['forum_id'], $userId)
|
||||
);
|
||||
}
|
||||
|
||||
return $listing;
|
||||
}
|
||||
|
||||
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 {
|
||||
// shitty fix for dumb-ass function signature
|
||||
if($forumId === 0)
|
||||
$forumId = null;
|
||||
|
||||
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_posting_info(int $userId): array {
|
||||
$getPostingInfo = \Misuzu\DB::prepare('
|
||||
SELECT
|
||||
u.`user_id`, u.`username`, u.`user_country`, u.`user_created`,
|
||||
COALESCE(u.`user_colour`, r.`role_colour`) AS `colour`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `user_id` = u.`user_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `user_forum_posts`,
|
||||
(
|
||||
SELECT `post_parse`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `user_id` = u.`user_id`
|
||||
AND `post_deleted` IS NULL
|
||||
ORDER BY `post_id` DESC
|
||||
LIMIT 1
|
||||
) AS `user_post_parse`
|
||||
FROM `msz_users` as u
|
||||
LEFT JOIN `msz_roles` AS r
|
||||
ON r.`role_id` = u.`display_role`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getPostingInfo->bind('user_id', $userId);
|
||||
return $getPostingInfo->fetch();
|
||||
}
|
||||
|
||||
function forum_count_increase(int $forumId, bool $topic = false): void {
|
||||
$increaseCount = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
UPDATE `msz_forum_categories`
|
||||
SET `forum_count_posts` = `forum_count_posts` + 1
|
||||
%s
|
||||
WHERE `forum_id` = :forum
|
||||
',
|
||||
$topic ? ',`forum_count_topics` = `forum_count_topics` + 1' : ''
|
||||
));
|
||||
$increaseCount->bind('forum', $forumId);
|
||||
$increaseCount->execute();
|
||||
}
|
||||
|
||||
function forum_count_synchronise(int $forumId = MSZ_FORUM_ROOT, bool $save = true): array {
|
||||
static $getChildren = null;
|
||||
static $getCounts = null;
|
||||
static $setCounts = null;
|
||||
|
||||
if(is_null($getChildren)) {
|
||||
$getChildren = \Misuzu\DB::prepare('
|
||||
SELECT `forum_id`, `forum_parent`
|
||||
FROM `msz_forum_categories`
|
||||
WHERE `forum_parent` = :parent
|
||||
');
|
||||
}
|
||||
|
||||
if(is_null($getCounts)) {
|
||||
$getCounts = \Misuzu\DB::prepare('
|
||||
SELECT :forum as `target_forum_id`,
|
||||
(
|
||||
SELECT COUNT(`topic_id`)
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `forum_id` = `target_forum_id`
|
||||
AND `topic_deleted` IS NULL
|
||||
) AS `count_topics`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `forum_id` = `target_forum_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `count_posts`
|
||||
');
|
||||
}
|
||||
|
||||
if($save && is_null($setCounts)) {
|
||||
$setCounts = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_categories`
|
||||
SET `forum_count_topics` = :topics,
|
||||
`forum_count_posts` = :posts
|
||||
WHERE `forum_id` = :forum_id
|
||||
');
|
||||
}
|
||||
|
||||
$getChildren->bind('parent', $forumId);
|
||||
$children = $getChildren->fetchAll();
|
||||
|
||||
$topics = 0;
|
||||
$posts = 0;
|
||||
|
||||
foreach($children as $child) {
|
||||
$childCount = forum_count_synchronise($child['forum_id'], $save);
|
||||
$topics += $childCount['topics'];
|
||||
$posts += $childCount['posts'];
|
||||
}
|
||||
|
||||
$getCounts->bind('forum', $forumId);
|
||||
$counts = $getCounts->fetch();
|
||||
$topics += $counts['count_topics'];
|
||||
$posts += $counts['count_posts'];
|
||||
|
||||
if($forumId > 0 && $save) {
|
||||
$setCounts->bind('forum_id', $forumId);
|
||||
$setCounts->bind('topics', $topics);
|
||||
$setCounts->bind('posts', $posts);
|
||||
$setCounts->execute();
|
||||
}
|
||||
|
||||
return compact('topics', 'posts');
|
||||
}
|
||||
|
||||
function forum_get_user_most_active_category_info(string|int $userId): ?object {
|
||||
if(is_string($userId))
|
||||
$userId = (int)$userId;
|
||||
if($userId < 1)
|
||||
return null;
|
||||
|
||||
global $cfg;
|
||||
|
||||
$getActiveForum = \Misuzu\DB::prepare(sprintf(
|
||||
'SELECT forum_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = :user AND post_deleted IS NULL AND forum_id NOT IN (%s) GROUP BY forum_id ORDER BY post_count DESC LIMIT 1',
|
||||
implode(',', $cfg->getArray('forum_leader.unranked.forum'))
|
||||
));
|
||||
$getActiveForum->bind('user', $userId);
|
||||
|
||||
return $getActiveForum->fetchObject();
|
||||
}
|
||||
|
||||
function forum_get_user_topic_count(\Misuzu\Users\UserInfo|string|int $userId): int {
|
||||
if(is_int($userId))
|
||||
$userId = (string)$userId;
|
||||
elseif($userId instanceof \Misuzu\Users\UserInfo)
|
||||
$userId = $userId->getId();
|
||||
|
||||
global $db;
|
||||
static $stmt = null;
|
||||
if($stmt === null)
|
||||
$stmt = $db->prepare('SELECT COUNT(*) FROM msz_forum_topics WHERE user_id = ? AND topic_deleted IS NULL');
|
||||
else
|
||||
$stmt->reset();
|
||||
$stmt->addParameter(1, $userId);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
return $result->next() ? $result->getInteger(0) : 0;
|
||||
}
|
||||
|
||||
function forum_get_user_post_count(\Misuzu\Users\UserInfo|string|int $userId): int {
|
||||
if(is_int($userId))
|
||||
$userId = (string)$userId;
|
||||
elseif($userId instanceof \Misuzu\Users\UserInfo)
|
||||
$userId = $userId->getId();
|
||||
|
||||
global $db;
|
||||
static $stmt = null;
|
||||
if($stmt === null)
|
||||
$stmt = $db->prepare('SELECT COUNT(*) FROM msz_forum_posts WHERE user_id = ? AND post_deleted IS NULL');
|
||||
else
|
||||
$stmt->reset();
|
||||
$stmt->addParameter(1, $userId);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
return $result->next() ? $result->getInteger(0) : 0;
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_FORUM_LEADERBOARD_START_YEAR', 2018);
|
||||
define('MSZ_FORUM_LEADERBOARD_START_MONTH', 12);
|
||||
define('MSZ_FORUM_LEADERBOARD_CATEGORY_ALL', 0);
|
||||
|
||||
function forum_leaderboard_year_valid(?int $year): bool {
|
||||
return !is_null($year) && $year >= MSZ_FORUM_LEADERBOARD_START_YEAR && $year <= date('Y');
|
||||
}
|
||||
|
||||
function forum_leaderboard_month_valid(?int $year, ?int $month): bool {
|
||||
if(is_null($month) || !forum_leaderboard_year_valid($year) || $month < 1 || $month > 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$combo = sprintf('%04d%02d', $year, $month);
|
||||
$start = sprintf('%04d%02d', MSZ_FORUM_LEADERBOARD_START_YEAR, MSZ_FORUM_LEADERBOARD_START_MONTH);
|
||||
$current = date('Ym');
|
||||
|
||||
return $combo >= $start && $combo <= $current;
|
||||
}
|
||||
|
||||
function forum_leaderboard_categories(): array {
|
||||
$categories = [
|
||||
MSZ_FORUM_LEADERBOARD_CATEGORY_ALL => 'All Time',
|
||||
];
|
||||
|
||||
$currentYear = date('Y');
|
||||
$currentMonth = date('m');
|
||||
|
||||
for($i = $currentYear; $i >= MSZ_FORUM_LEADERBOARD_START_YEAR; $i--) {
|
||||
$categories[$i] = sprintf('Leaderboard %d', $i);
|
||||
}
|
||||
|
||||
for($i = $currentYear, $j = $currentMonth;;) {
|
||||
$categories[sprintf('%d%02d', $i, $j)] = sprintf('Leaderboard %d-%02d', $i, $j);
|
||||
|
||||
if($j <= 1) {
|
||||
$i--; $j = 12;
|
||||
} else $j--;
|
||||
|
||||
if($i <= MSZ_FORUM_LEADERBOARD_START_YEAR && $j < MSZ_FORUM_LEADERBOARD_START_MONTH)
|
||||
break;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
function forum_leaderboard_listing(
|
||||
?int $year = null,
|
||||
?int $month = null,
|
||||
array $unrankedForums = [],
|
||||
array $unrankedTopics = []
|
||||
): array {
|
||||
$hasYear = forum_leaderboard_year_valid($year);
|
||||
$hasMonth = $hasYear && forum_leaderboard_month_valid($year, $month);
|
||||
$unrankedForums = implode(',', $unrankedForums);
|
||||
$unrankedTopics = implode(',', $unrankedTopics);
|
||||
|
||||
$rawLeaderboard = \Misuzu\DB::query(sprintf(
|
||||
'
|
||||
SELECT
|
||||
u.`user_id`, u.`username`,
|
||||
COUNT(fp.`post_id`) as `posts`
|
||||
FROM `msz_users` AS u
|
||||
INNER JOIN `msz_forum_posts` AS fp
|
||||
ON fp.`user_id` = u.`user_id`
|
||||
WHERE fp.`post_deleted` IS NULL
|
||||
%s %s %s
|
||||
GROUP BY u.`user_id`
|
||||
HAVING `posts` > 0
|
||||
ORDER BY `posts` DESC
|
||||
',
|
||||
$unrankedForums ? sprintf('AND fp.`forum_id` NOT IN (%s)', $unrankedForums) : '',
|
||||
$unrankedTopics ? sprintf('AND fp.`topic_id` NOT IN (%s)', $unrankedTopics) : '',
|
||||
!$hasYear ? '' : sprintf(
|
||||
'AND DATE(fp.`post_created`) BETWEEN \'%1$04d-%2$02d-01\' AND \'%1$04d-%3$02d-31\'',
|
||||
$year,
|
||||
$hasMonth ? $month : 1,
|
||||
$hasMonth ? $month : 12
|
||||
)
|
||||
))->fetchAll();
|
||||
|
||||
$leaderboard = [];
|
||||
$ranking = 0;
|
||||
$lastPosts = null;
|
||||
|
||||
foreach($rawLeaderboard as $entry) {
|
||||
if(is_null($lastPosts) || $lastPosts > $entry['posts']) {
|
||||
$ranking++;
|
||||
$lastPosts = $entry['posts'];
|
||||
}
|
||||
|
||||
$entry['rank'] = $ranking;
|
||||
$leaderboard[] = $entry;
|
||||
}
|
||||
|
||||
return $leaderboard;
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_FORUM_PERMS_GENERAL', 'forum');
|
||||
|
||||
define('MSZ_FORUM_PERM_LIST_FORUM', 0x00000001); // can see stats, but will get error when trying to view
|
||||
define('MSZ_FORUM_PERM_VIEW_FORUM', 0x00000002);
|
||||
|
||||
define('MSZ_FORUM_PERM_CREATE_TOPIC', 0x00000400);
|
||||
//define('MSZ_FORUM_PERM_DELETE_TOPIC', 0x00000800); // use MSZ_FORUM_PERM_DELETE_ANY_POST instead
|
||||
define('MSZ_FORUM_PERM_MOVE_TOPIC', 0x00001000);
|
||||
define('MSZ_FORUM_PERM_LOCK_TOPIC', 0x00002000);
|
||||
define('MSZ_FORUM_PERM_STICKY_TOPIC', 0x00004000);
|
||||
define('MSZ_FORUM_PERM_ANNOUNCE_TOPIC', 0x00008000);
|
||||
define('MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC', 0x00010000);
|
||||
define('MSZ_FORUM_PERM_BUMP_TOPIC', 0x00020000);
|
||||
//define('MSZ_FORUM_PERM_PRIORITY_VOTE', 0x00040000); // feature postponed, perhaps reuse if it makes sense to
|
||||
|
||||
define('MSZ_FORUM_PERM_CREATE_POST', 0x00100000);
|
||||
define('MSZ_FORUM_PERM_EDIT_POST', 0x00200000);
|
||||
define('MSZ_FORUM_PERM_EDIT_ANY_POST', 0x00400000);
|
||||
define('MSZ_FORUM_PERM_DELETE_POST', 0x00800000);
|
||||
define('MSZ_FORUM_PERM_DELETE_ANY_POST', 0x01000000);
|
||||
|
||||
// shorthands, never use these to SET!!!!!!!
|
||||
define('MSZ_FORUM_PERM_SET_READ', MSZ_FORUM_PERM_LIST_FORUM | MSZ_FORUM_PERM_VIEW_FORUM);
|
||||
define(
|
||||
'MSZ_FORUM_PERM_SET_WRITE',
|
||||
MSZ_FORUM_PERM_CREATE_TOPIC
|
||||
| MSZ_FORUM_PERM_MOVE_TOPIC
|
||||
| MSZ_FORUM_PERM_LOCK_TOPIC
|
||||
| MSZ_FORUM_PERM_STICKY_TOPIC
|
||||
| MSZ_FORUM_PERM_ANNOUNCE_TOPIC
|
||||
| MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC
|
||||
| MSZ_FORUM_PERM_CREATE_POST
|
||||
| MSZ_FORUM_PERM_EDIT_POST
|
||||
| MSZ_FORUM_PERM_EDIT_ANY_POST
|
||||
| MSZ_FORUM_PERM_DELETE_POST
|
||||
| MSZ_FORUM_PERM_DELETE_ANY_POST
|
||||
| MSZ_FORUM_PERM_BUMP_TOPIC
|
||||
);
|
||||
|
||||
define('MSZ_FORUM_PERM_MODES', [
|
||||
MSZ_FORUM_PERMS_GENERAL,
|
||||
]);
|
||||
|
||||
function forum_perms_get_user(?int $forum, int $user): array {
|
||||
$perms = perms_get_blank(MSZ_FORUM_PERM_MODES);
|
||||
|
||||
if($user < 0 || $forum < 0) {
|
||||
return $perms;
|
||||
}
|
||||
|
||||
static $memo = [];
|
||||
$memoId = "{$forum}-{$user}";
|
||||
|
||||
if(array_key_exists($memoId, $memo)) {
|
||||
return $memo[$memoId];
|
||||
}
|
||||
|
||||
if($forum > 0) {
|
||||
$perms = forum_perms_get_user(
|
||||
forum_get_parent_id($forum),
|
||||
$user
|
||||
);
|
||||
}
|
||||
|
||||
$getPerms = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT %s
|
||||
FROM `msz_forum_permissions`
|
||||
WHERE (`forum_id` = :forum_id OR `forum_id` IS NULL)
|
||||
AND (
|
||||
(`user_id` IS NULL AND `role_id` IS NULL)
|
||||
OR (`user_id` = :user_id_1 AND `role_id` IS NULL)
|
||||
OR (
|
||||
`user_id` IS NULL
|
||||
AND `role_id` IN (
|
||||
SELECT `role_id`
|
||||
FROM `msz_users_roles`
|
||||
WHERE `user_id` = :user_id_2
|
||||
)
|
||||
)
|
||||
)
|
||||
',
|
||||
perms_get_select(MSZ_FORUM_PERM_MODES)
|
||||
));
|
||||
$getPerms->bind('forum_id', $forum);
|
||||
$getPerms->bind('user_id_1', $user);
|
||||
$getPerms->bind('user_id_2', $user);
|
||||
|
||||
$userPerms = $getPerms->fetch();
|
||||
foreach($perms as $key => $value)
|
||||
$perms[$key] |= $userPerms[$key] ?? 0;
|
||||
|
||||
return $memo[$memoId] = $perms;
|
||||
}
|
||||
|
||||
function forum_perms_get_role(?int $forum, int $role): array {
|
||||
$perms = perms_get_blank(MSZ_FORUM_PERM_MODES);
|
||||
|
||||
if($role < 1 || $forum < 0) {
|
||||
return $perms;
|
||||
}
|
||||
|
||||
static $memo = [];
|
||||
$memoId = "{$forum}-{$role}";
|
||||
|
||||
if(array_key_exists($memoId, $memo)) {
|
||||
return $memo[$memoId];
|
||||
}
|
||||
|
||||
if($forum > 0) {
|
||||
$perms = forum_perms_get_role(
|
||||
forum_get_parent_id($forum),
|
||||
$role
|
||||
);
|
||||
}
|
||||
|
||||
$getPerms = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT %s
|
||||
FROM `msz_forum_permissions`
|
||||
WHERE (`forum_id` = :forum_id OR `forum_id` IS NULL)
|
||||
AND `role_id` = :role_id
|
||||
AND `user_id` IS NULL
|
||||
',
|
||||
perms_get_select(MSZ_FORUM_PERM_MODES)
|
||||
));
|
||||
$getPerms->bind('forum_id', $forum);
|
||||
$getPerms->bind('role_id', $role);
|
||||
|
||||
$userPerms = $getPerms->fetch();
|
||||
foreach($perms as $key => $value)
|
||||
$perms[$key] |= $userPerms[$key] ?? 0;
|
||||
|
||||
return $memo[$memoId] = $perms;
|
||||
}
|
||||
|
||||
function forum_perms_get_user_raw(?int $forum, int $user): array {
|
||||
if($user < 1) {
|
||||
return perms_create(MSZ_FORUM_PERM_MODES);
|
||||
}
|
||||
|
||||
$getPerms = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT `%s`
|
||||
FROM `msz_forum_permissions`
|
||||
WHERE `forum_id` %s
|
||||
AND `user_id` = :user_id
|
||||
AND `role_id` IS NULL
|
||||
',
|
||||
implode('`, `', perms_get_keys(MSZ_FORUM_PERM_MODES)),
|
||||
$forum === null ? 'IS NULL' : '= :forum_id'
|
||||
));
|
||||
|
||||
if($forum !== null) {
|
||||
$getPerms->bind('forum_id', $forum);
|
||||
}
|
||||
|
||||
$getPerms->bind('user_id', $user);
|
||||
$perms = $getPerms->fetch();
|
||||
|
||||
if(empty($perms)) {
|
||||
return perms_create(MSZ_FORUM_PERM_MODES);
|
||||
}
|
||||
|
||||
return $perms;
|
||||
}
|
||||
|
||||
function forum_perms_get_role_raw(?int $forum, ?int $role): array {
|
||||
if($role < 1 && $role !== null) {
|
||||
return perms_create(MSZ_FORUM_PERM_MODES);
|
||||
}
|
||||
|
||||
$getPerms = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT `%s`
|
||||
FROM `msz_forum_permissions`
|
||||
WHERE `forum_id` %s
|
||||
AND `user_id` IS NULL
|
||||
AND `role_id` %s
|
||||
',
|
||||
implode('`, `', perms_get_keys(MSZ_FORUM_PERM_MODES)),
|
||||
$forum === null ? 'IS NULL' : '= :forum_id',
|
||||
$role === null ? 'IS NULL' : '= :role_id'
|
||||
));
|
||||
|
||||
if($forum !== null) {
|
||||
$getPerms->bind('forum_id', $forum);
|
||||
}
|
||||
|
||||
if($role !== null) {
|
||||
$getPerms->bind('role_id', $role);
|
||||
}
|
||||
|
||||
$perms = $getPerms->fetch();
|
||||
|
||||
if(empty($perms)) {
|
||||
return perms_create(MSZ_FORUM_PERM_MODES);
|
||||
}
|
||||
|
||||
return $perms;
|
||||
}
|
||||
|
||||
function forum_perms_check_user(
|
||||
string $prefix,
|
||||
?int $forumId,
|
||||
?int $userId,
|
||||
int $perm,
|
||||
bool $strict = false
|
||||
): bool {
|
||||
return perms_check(forum_perms_get_user($forumId, $userId)[$prefix] ?? 0, $perm, $strict);
|
||||
}
|
|
@ -1,363 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_FORUM_POSTS_PER_PAGE', 10);
|
||||
|
||||
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 {
|
||||
// i have no idea if the post_created field depend on not being parsed, so post_created_unix it is!
|
||||
// not even the first time i've done this either (see forum_latest_post) lol, what a mess
|
||||
$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`,
|
||||
UNIX_TIMESTAMP(p.`post_created`) AS `post_created_unix`,
|
||||
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,719 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_TOPIC_TYPE_DISCUSSION', 0);
|
||||
define('MSZ_TOPIC_TYPE_STICKY', 1);
|
||||
define('MSZ_TOPIC_TYPE_ANNOUNCEMENT', 2);
|
||||
define('MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT', 3);
|
||||
define('MSZ_TOPIC_TYPES', [
|
||||
MSZ_TOPIC_TYPE_DISCUSSION,
|
||||
MSZ_TOPIC_TYPE_STICKY,
|
||||
MSZ_TOPIC_TYPE_ANNOUNCEMENT,
|
||||
MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT,
|
||||
]);
|
||||
|
||||
define('MSZ_TOPIC_TYPE_ORDER', [ // in which order to display topics, only add types here that should appear above others
|
||||
MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT,
|
||||
MSZ_TOPIC_TYPE_ANNOUNCEMENT,
|
||||
MSZ_TOPIC_TYPE_STICKY,
|
||||
]);
|
||||
|
||||
function forum_topic_is_valid_type(int $type): bool {
|
||||
return in_array($type, MSZ_TOPIC_TYPES, true);
|
||||
}
|
||||
|
||||
function forum_topic_create(
|
||||
int $forumId,
|
||||
int $userId,
|
||||
string $title,
|
||||
int $type = MSZ_TOPIC_TYPE_DISCUSSION
|
||||
): int {
|
||||
if(empty($title) || !forum_topic_is_valid_type($type)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$createTopic = \Misuzu\DB::prepare('
|
||||
INSERT INTO `msz_forum_topics`
|
||||
(`forum_id`, `user_id`, `topic_title`, `topic_type`)
|
||||
VALUES
|
||||
(:forum_id, :user_id, :topic_title, :topic_type)
|
||||
');
|
||||
$createTopic->bind('forum_id', $forumId);
|
||||
$createTopic->bind('user_id', $userId);
|
||||
$createTopic->bind('topic_title', $title);
|
||||
$createTopic->bind('topic_type', $type);
|
||||
|
||||
return $createTopic->execute() ? \Misuzu\DB::lastId() : 0;
|
||||
}
|
||||
|
||||
function forum_topic_update(int $topicId, ?string $title, ?int $type = null): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure it's null and not some other kinda empty
|
||||
if(empty($title)) {
|
||||
$title = null;
|
||||
}
|
||||
|
||||
if($type !== null && !forum_topic_is_valid_type($type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$updateTopic = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_title` = COALESCE(:topic_title, `topic_title`),
|
||||
`topic_type` = COALESCE(:topic_type, `topic_type`)
|
||||
WHERE `topic_id` = :topic_id
|
||||
');
|
||||
$updateTopic->bind('topic_id', $topicId);
|
||||
$updateTopic->bind('topic_title', $title);
|
||||
$updateTopic->bind('topic_type', $type);
|
||||
|
||||
return $updateTopic->execute();
|
||||
}
|
||||
|
||||
function forum_topic_get(int $topicId, bool $allowDeleted = false): array {
|
||||
$getTopic = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
t.`topic_id`, t.`forum_id`, t.`topic_title`, t.`topic_type`, t.`topic_locked`, t.`topic_created`,
|
||||
f.`forum_archived` AS `topic_archived`, t.`topic_deleted`, t.`topic_bumped`, f.`forum_type`,
|
||||
fp.`topic_id` AS `author_post_id`, fp.`user_id` AS `author_user_id`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `topic_count_posts`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `post_deleted` IS NOT NULL
|
||||
) AS `topic_count_posts_deleted`
|
||||
FROM `msz_forum_topics` AS t
|
||||
LEFT JOIN `msz_forum_categories` AS f
|
||||
ON f.`forum_id` = t.`forum_id`
|
||||
LEFT JOIN `msz_forum_posts` AS fp
|
||||
ON fp.`post_id` = (
|
||||
SELECT MIN(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
)
|
||||
WHERE t.`topic_id` = :topic_id
|
||||
%s
|
||||
',
|
||||
$allowDeleted ? '' : 'AND t.`topic_deleted` IS NULL'
|
||||
));
|
||||
$getTopic->bind('topic_id', $topicId);
|
||||
return $getTopic->fetch();
|
||||
}
|
||||
|
||||
function forum_topic_redir_info(int $topicId): ?object {
|
||||
$getTopicRedir = \Misuzu\DB::prepare('
|
||||
SELECT topic_id, user_id, topic_redir_url,
|
||||
UNIX_TIMESTAMP(topic_redir_created) AS topic_redir_created
|
||||
FROM msz_forum_topics_redirects
|
||||
WHERE topic_id = :topic_id
|
||||
');
|
||||
$getTopicRedir->bind('topic_id', $topicId);
|
||||
return $getTopicRedir->fetchObject();
|
||||
}
|
||||
|
||||
function forum_topic_redir_count(): int {
|
||||
return \Misuzu\DB::query('SELECT COUNT(*) FROM msz_forum_topics_redirects')->fetchColumn() ?? 0;
|
||||
}
|
||||
|
||||
function forum_topic_redir_all(int $offset, int $take): array {
|
||||
$getTopicRedirs = \Misuzu\DB::prepare('
|
||||
SELECT topic_id, user_id, topic_redir_url,
|
||||
UNIX_TIMESTAMP(topic_redir_created) AS topic_redir_created
|
||||
FROM msz_forum_topics_redirects
|
||||
LIMIT :offset, :take
|
||||
');
|
||||
$getTopicRedirs->bind('offset', $offset);
|
||||
$getTopicRedirs->bind('take', $take);
|
||||
return $getTopicRedirs->fetchObjects();
|
||||
}
|
||||
|
||||
function forum_topic_redir_create(int $topicId, int $userId, string $url): void {
|
||||
if($topicId < 1 || empty($url)) return;
|
||||
if($userId < 1) $userId = null;
|
||||
|
||||
$createTopicRedir = \Misuzu\DB::prepare('
|
||||
INSERT INTO msz_forum_topics_redirects (topic_id, user_id, topic_redir_url)
|
||||
VALUES (:topic_id, :user_id, :redir_url)
|
||||
');
|
||||
$createTopicRedir->bind('topic_id', $topicId);
|
||||
$createTopicRedir->bind('user_id', $userId);
|
||||
$createTopicRedir->bind('redir_url', $url);
|
||||
$createTopicRedir->execute();
|
||||
}
|
||||
|
||||
function forum_topic_redir_remove(int $topicId): void {
|
||||
$removeTopicRedir = \Misuzu\DB::prepare('
|
||||
DELETE FROM msz_forum_topics_redirects
|
||||
WHERE topic_id = :topic_id
|
||||
');
|
||||
$removeTopicRedir->bind('topic_id', $topicId);
|
||||
$removeTopicRedir->execute();
|
||||
}
|
||||
|
||||
function forum_topic_bump(int $topicId): bool {
|
||||
$bumpTopic = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_bumped` = NOW()
|
||||
WHERE `topic_id` = :topic_id
|
||||
AND `topic_deleted` IS NULL
|
||||
');
|
||||
$bumpTopic->bind('topic_id', $topicId);
|
||||
return $bumpTopic->execute();
|
||||
}
|
||||
|
||||
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
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function forum_topic_listing(
|
||||
int $forumId, int $userId,
|
||||
int $offset = 0, int $take = 0,
|
||||
bool $showDeleted = false, bool $sortByPriority = false
|
||||
): array {
|
||||
$hasPagination = $offset >= 0 && $take > 0;
|
||||
$getTopics = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
:user_id AS `target_user_id`,
|
||||
t.`topic_id`, t.`topic_title`, t.`topic_locked`, t.`topic_type`, t.`topic_created`,
|
||||
t.`topic_bumped`, t.`topic_deleted`, t.`topic_count_views`, f.`forum_type`,
|
||||
au.`user_id` AS `author_id`, au.`username` AS `author_name`,
|
||||
COALESCE(au.`user_colour`, ar.`role_colour`) AS `author_colour`,
|
||||
lp.`post_id` AS `response_id`,
|
||||
lp.`post_created` AS `response_created`,
|
||||
lu.`user_id` AS `respondent_id`,
|
||||
lu.`username` AS `respondent_name`,
|
||||
COALESCE(lu.`user_colour`, lr.`role_colour`) AS `respondent_colour`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
) AS `topic_count_posts`,
|
||||
(
|
||||
SELECT CEIL(COUNT(`post_id`) / %6$d)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
) AS `topic_pages`,
|
||||
(
|
||||
SELECT
|
||||
`target_user_id` > 0
|
||||
AND
|
||||
t.`topic_bumped` > NOW() - INTERVAL 1 MONTH
|
||||
AND (
|
||||
SELECT COUNT(ti.`topic_id`) < 1
|
||||
FROM `msz_forum_topics_track` AS tt
|
||||
RIGHT JOIN `msz_forum_topics` AS ti
|
||||
ON ti.`topic_id` = tt.`topic_id`
|
||||
WHERE ti.`topic_id` = t.`topic_id`
|
||||
AND tt.`user_id` = `target_user_id`
|
||||
AND `track_last_read` >= `topic_bumped`
|
||||
)
|
||||
) AS `topic_unread`,
|
||||
(
|
||||
SELECT COUNT(`post_id`) > 0
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `user_id` = `target_user_id`
|
||||
LIMIT 1
|
||||
) AS `topic_participated`
|
||||
FROM `msz_forum_topics` AS t
|
||||
LEFT JOIN `msz_forum_categories` AS f
|
||||
ON f.`forum_id` = t.`forum_id`
|
||||
LEFT JOIN `msz_users` AS au
|
||||
ON t.`user_id` = au.`user_id`
|
||||
LEFT JOIN `msz_roles` AS ar
|
||||
ON ar.`role_id` = au.`display_role`
|
||||
LEFT JOIN `msz_forum_posts` AS lp
|
||||
ON lp.`post_id` = (
|
||||
SELECT `post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
ORDER BY `post_id` DESC
|
||||
LIMIT 1
|
||||
)
|
||||
LEFT JOIN `msz_users` AS lu
|
||||
ON lu.`user_id` = lp.`user_id`
|
||||
LEFT JOIN `msz_roles` AS lr
|
||||
ON lr.`role_id` = lu.`display_role`
|
||||
WHERE (
|
||||
t.`forum_id` = :forum_id
|
||||
OR t.`topic_type` = %3$d
|
||||
)
|
||||
%1$s
|
||||
GROUP BY t.`topic_id`
|
||||
ORDER BY FIELD(t.`topic_type`, %4$s) DESC, t.`topic_bumped` DESC
|
||||
%2$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND t.`topic_deleted` IS NULL',
|
||||
$hasPagination ? 'LIMIT :offset, :take' : '',
|
||||
MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT,
|
||||
implode(',', array_reverse(MSZ_TOPIC_TYPE_ORDER)),
|
||||
$showDeleted ? '' : 'AND `post_deleted` IS NULL',
|
||||
MSZ_FORUM_POSTS_PER_PAGE
|
||||
));
|
||||
$getTopics->bind('forum_id', $forumId);
|
||||
$getTopics->bind('user_id', $userId);
|
||||
|
||||
if($hasPagination) {
|
||||
$getTopics->bind('offset', $offset);
|
||||
$getTopics->bind('take', $take);
|
||||
}
|
||||
|
||||
return $getTopics->fetchAll();
|
||||
}
|
||||
|
||||
function forum_topic_count_user(int $authorId, int $userId, bool $showDeleted = false): int {
|
||||
$getTopics = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT COUNT(`topic_id`)
|
||||
FROM `msz_forum_topics` AS t
|
||||
WHERE t.`user_id` = :author_id
|
||||
%1$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND t.`topic_deleted` IS NULL'
|
||||
));
|
||||
$getTopics->bind('author_id', $authorId);
|
||||
//$getTopics->bind('user_id', $userId);
|
||||
|
||||
return (int)$getTopics->fetchColumn();
|
||||
}
|
||||
|
||||
// Remove unneccesary stuff from the sql stmt
|
||||
function forum_topic_listing_user(
|
||||
int $authorId,
|
||||
int $userId,
|
||||
int $offset = 0,
|
||||
int $take = 0,
|
||||
bool $showDeleted = false
|
||||
): array {
|
||||
$hasPagination = $offset >= 0 && $take > 0;
|
||||
$getTopics = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
:user_id AS `target_user_id`,
|
||||
t.`topic_id`, t.`topic_title`, t.`topic_locked`, t.`topic_type`, t.`topic_created`,
|
||||
t.`topic_bumped`, t.`topic_deleted`, t.`topic_count_views`, f.`forum_type`,
|
||||
au.`user_id` AS `author_id`, au.`username` AS `author_name`,
|
||||
COALESCE(au.`user_colour`, ar.`role_colour`) AS `author_colour`,
|
||||
lp.`post_id` AS `response_id`,
|
||||
lp.`post_created` AS `response_created`,
|
||||
lu.`user_id` AS `respondent_id`,
|
||||
lu.`username` AS `respondent_name`,
|
||||
COALESCE(lu.`user_colour`, lr.`role_colour`) AS `respondent_colour`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
) AS `topic_count_posts`,
|
||||
(
|
||||
SELECT CEIL(COUNT(`post_id`) / %6$d)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
) AS `topic_pages`,
|
||||
(
|
||||
SELECT
|
||||
`target_user_id` > 0
|
||||
AND
|
||||
t.`topic_bumped` > NOW() - INTERVAL 1 MONTH
|
||||
AND (
|
||||
SELECT COUNT(ti.`topic_id`) < 1
|
||||
FROM `msz_forum_topics_track` AS tt
|
||||
RIGHT JOIN `msz_forum_topics` AS ti
|
||||
ON ti.`topic_id` = tt.`topic_id`
|
||||
WHERE ti.`topic_id` = t.`topic_id`
|
||||
AND tt.`user_id` = `target_user_id`
|
||||
AND `track_last_read` >= `topic_bumped`
|
||||
)
|
||||
) AS `topic_unread`,
|
||||
(
|
||||
SELECT COUNT(`post_id`) > 0
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `user_id` = `target_user_id`
|
||||
LIMIT 1
|
||||
) AS `topic_participated`
|
||||
FROM `msz_forum_topics` AS t
|
||||
LEFT JOIN `msz_forum_categories` AS f
|
||||
ON f.`forum_id` = t.`forum_id`
|
||||
LEFT JOIN `msz_users` AS au
|
||||
ON t.`user_id` = au.`user_id`
|
||||
LEFT JOIN `msz_roles` AS ar
|
||||
ON ar.`role_id` = au.`display_role`
|
||||
LEFT JOIN `msz_forum_posts` AS lp
|
||||
ON lp.`post_id` = (
|
||||
SELECT `post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
%5$s
|
||||
ORDER BY `post_id` DESC
|
||||
LIMIT 1
|
||||
)
|
||||
LEFT JOIN `msz_users` AS lu
|
||||
ON lu.`user_id` = lp.`user_id`
|
||||
LEFT JOIN `msz_roles` AS lr
|
||||
ON lr.`role_id` = lu.`display_role`
|
||||
WHERE au.`user_id` = :author_id
|
||||
%1$s
|
||||
ORDER BY FIELD(t.`topic_type`, %4$s) DESC, t.`topic_bumped` DESC
|
||||
%2$s
|
||||
',
|
||||
$showDeleted ? '' : 'AND t.`topic_deleted` IS NULL',
|
||||
$hasPagination ? 'LIMIT :offset, :take' : '',
|
||||
MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT,
|
||||
implode(',', array_reverse(MSZ_TOPIC_TYPE_ORDER)),
|
||||
$showDeleted ? '' : 'AND `post_deleted` IS NULL',
|
||||
MSZ_FORUM_POSTS_PER_PAGE
|
||||
));
|
||||
$getTopics->bind('author_id', $authorId);
|
||||
$getTopics->bind('user_id', $userId);
|
||||
|
||||
if($hasPagination) {
|
||||
$getTopics->bind('offset', $offset);
|
||||
$getTopics->bind('take', $take);
|
||||
}
|
||||
|
||||
return $getTopics->fetchAll();
|
||||
}
|
||||
|
||||
function forum_topic_listing_search(string $query, int $userId): array {
|
||||
$getTopics = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT
|
||||
:user_id AS `target_user_id`,
|
||||
t.`topic_id`, t.`topic_title`, t.`topic_locked`, t.`topic_type`, t.`topic_created`,
|
||||
t.`topic_bumped`, t.`topic_deleted`, t.`topic_count_views`, f.`forum_type`,
|
||||
au.`user_id` AS `author_id`, au.`username` AS `author_name`,
|
||||
COALESCE(au.`user_colour`, ar.`role_colour`) AS `author_colour`,
|
||||
lp.`post_id` AS `response_id`,
|
||||
lp.`post_created` AS `response_created`,
|
||||
lu.`user_id` AS `respondent_id`,
|
||||
lu.`username` AS `respondent_name`,
|
||||
COALESCE(lu.`user_colour`, lr.`role_colour`) AS `respondent_colour`,
|
||||
(
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `topic_count_posts`,
|
||||
(
|
||||
SELECT CEIL(COUNT(`post_id`) / %2$d)
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `post_deleted` IS NULL
|
||||
) AS `topic_pages`,
|
||||
(
|
||||
SELECT
|
||||
`target_user_id` > 0
|
||||
AND
|
||||
t.`topic_bumped` > NOW() - INTERVAL 1 MONTH
|
||||
AND (
|
||||
SELECT COUNT(ti.`topic_id`) < 1
|
||||
FROM `msz_forum_topics_track` AS tt
|
||||
RIGHT JOIN `msz_forum_topics` AS ti
|
||||
ON ti.`topic_id` = tt.`topic_id`
|
||||
WHERE ti.`topic_id` = t.`topic_id`
|
||||
AND tt.`user_id` = `target_user_id`
|
||||
AND `track_last_read` >= `topic_bumped`
|
||||
)
|
||||
) AS `topic_unread`,
|
||||
(
|
||||
SELECT COUNT(`post_id`) > 0
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `user_id` = `target_user_id`
|
||||
LIMIT 1
|
||||
) AS `topic_participated`
|
||||
FROM `msz_forum_topics` AS t
|
||||
LEFT JOIN `msz_forum_categories` AS f
|
||||
ON f.`forum_id` = t.`forum_id`
|
||||
LEFT JOIN `msz_users` AS au
|
||||
ON t.`user_id` = au.`user_id`
|
||||
LEFT JOIN `msz_roles` AS ar
|
||||
ON ar.`role_id` = au.`display_role`
|
||||
LEFT JOIN `msz_forum_posts` AS lp
|
||||
ON lp.`post_id` = (
|
||||
SELECT `post_id`
|
||||
FROM `msz_forum_posts`
|
||||
WHERE `topic_id` = t.`topic_id`
|
||||
AND `post_deleted` IS NULL
|
||||
ORDER BY `post_id` DESC
|
||||
LIMIT 1
|
||||
)
|
||||
LEFT JOIN `msz_users` AS lu
|
||||
ON lu.`user_id` = lp.`user_id`
|
||||
LEFT JOIN `msz_roles` AS lr
|
||||
ON lr.`role_id` = lu.`display_role`
|
||||
WHERE MATCH(`topic_title`)
|
||||
AGAINST (:query IN NATURAL LANGUAGE MODE)
|
||||
AND t.`topic_deleted` IS NULL
|
||||
ORDER BY FIELD(t.`topic_type`, %1$s) DESC, t.`topic_bumped` DESC
|
||||
',
|
||||
implode(',', array_reverse(MSZ_TOPIC_TYPE_ORDER)),
|
||||
MSZ_FORUM_POSTS_PER_PAGE
|
||||
));
|
||||
$getTopics->bind('query', $query);
|
||||
$getTopics->bind('user_id', $userId);
|
||||
|
||||
return $getTopics->fetchAll();
|
||||
}
|
||||
|
||||
function forum_topic_lock(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markLocked = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_locked` = NOW()
|
||||
WHERE `topic_id` = :topic
|
||||
AND `topic_locked` IS NULL
|
||||
');
|
||||
$markLocked->bind('topic', $topicId);
|
||||
|
||||
return $markLocked->execute();
|
||||
}
|
||||
|
||||
function forum_topic_unlock(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markUnlocked = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_locked` = NULL
|
||||
WHERE `topic_id` = :topic
|
||||
AND `topic_locked` IS NOT NULL
|
||||
');
|
||||
$markUnlocked->bind('topic', $topicId);
|
||||
|
||||
return $markUnlocked->execute();
|
||||
}
|
||||
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_OK', 0); // deleting is fine
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_USER', 1); // invalid user
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_TOPIC', 2); // topic doesn't exist
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_DELETED', 3); // topic is already marked as deleted
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_OWNER', 4); // you may only delete your own topics
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_OLD', 5); // topic has existed for too long to be deleted
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_PERM', 6); // you aren't allowed to delete topics
|
||||
define('MSZ_E_FORUM_TOPIC_DELETE_POSTS', 7); // the topic already has replies
|
||||
|
||||
// only allow topics made within a day of posting to be deleted by normal users
|
||||
define('MSZ_FORUM_TOPIC_DELETE_TIME_LIMIT', 60 * 60 * 24);
|
||||
|
||||
// only allow topics with a single post to be deleted, includes soft deleted posts
|
||||
define('MSZ_FORUM_TOPIC_DELETE_POST_LIMIT', 1);
|
||||
|
||||
// set $userId to null for system request, make sure this is NEVER EVER null on user request
|
||||
// $topicId can also be a the return value of forum_topic_get if you already grabbed it once before
|
||||
function forum_topic_can_delete($topicId, ?int $userId = null): int {
|
||||
if($userId !== null && $userId < 1) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_USER;
|
||||
}
|
||||
|
||||
if(is_array($topicId)) {
|
||||
$topic = $topicId;
|
||||
} else {
|
||||
$topic = forum_topic_get((int)$topicId, true);
|
||||
}
|
||||
|
||||
if(empty($topic)) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
|
||||
$isSystemReq = $userId === null;
|
||||
$perms = $isSystemReq ? 0 : forum_perms_get_user($topic['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($topic['topic_deleted']);
|
||||
|
||||
if(!$canViewPost) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
|
||||
if($postIsDeleted) {
|
||||
return $canDeleteAny ? MSZ_E_FORUM_TOPIC_DELETE_DELETED : MSZ_E_FORUM_TOPIC_DELETE_TOPIC;
|
||||
}
|
||||
|
||||
if($isSystemReq) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OK;
|
||||
}
|
||||
|
||||
if(!$canDeleteAny) {
|
||||
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_POST)) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_PERM;
|
||||
}
|
||||
|
||||
if($topic['author_user_id'] !== $userId) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OWNER;
|
||||
}
|
||||
|
||||
if(strtotime($topic['topic_created']) <= (time() - MSZ_FORUM_TOPIC_DELETE_TIME_LIMIT)) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OLD;
|
||||
}
|
||||
|
||||
$totalReplies = (int)$topic['topic_count_posts'] + (int)$topic['topic_count_posts_deleted'];
|
||||
|
||||
if($totalReplies > MSZ_E_FORUM_TOPIC_DELETE_POSTS) {
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_POSTS;
|
||||
}
|
||||
}
|
||||
|
||||
return MSZ_E_FORUM_TOPIC_DELETE_OK;
|
||||
}
|
||||
|
||||
function forum_topic_delete(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markTopicDeleted = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_deleted` = NOW()
|
||||
WHERE `topic_id` = :topic
|
||||
AND `topic_deleted` IS NULL
|
||||
');
|
||||
$markTopicDeleted->bind('topic', $topicId);
|
||||
|
||||
if(!$markTopicDeleted->execute()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markPostsDeleted = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts` as p
|
||||
SET p.`post_deleted` = (
|
||||
SELECT `topic_deleted`
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
)
|
||||
WHERE p.`topic_id` = :topic
|
||||
AND p.`post_deleted` IS NULL
|
||||
');
|
||||
$markPostsDeleted->bind('topic', $topicId);
|
||||
|
||||
return $markPostsDeleted->execute();
|
||||
}
|
||||
|
||||
function forum_topic_restore(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markPostsRestored = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_posts` as p
|
||||
SET p.`post_deleted` = NULL
|
||||
WHERE p.`topic_id` = :topic
|
||||
AND p.`post_deleted` = (
|
||||
SELECT `topic_deleted`
|
||||
FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = p.`topic_id`
|
||||
)
|
||||
');
|
||||
$markPostsRestored->bind('topic', $topicId);
|
||||
|
||||
if(!$markPostsRestored->execute()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$markTopicRestored = \Misuzu\DB::prepare('
|
||||
UPDATE `msz_forum_topics`
|
||||
SET `topic_deleted` = NULL
|
||||
WHERE `topic_id` = :topic
|
||||
AND `topic_deleted` IS NOT NULL
|
||||
');
|
||||
$markTopicRestored->bind('topic', $topicId);
|
||||
|
||||
return $markTopicRestored->execute();
|
||||
}
|
||||
|
||||
function forum_topic_nuke(int $topicId): bool {
|
||||
if($topicId < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nukeTopic = \Misuzu\DB::prepare('
|
||||
DELETE FROM `msz_forum_topics`
|
||||
WHERE `topic_id` = :topic
|
||||
');
|
||||
$nukeTopic->bind('topic', $topicId);
|
||||
return $nukeTopic->execute();
|
||||
}
|
||||
|
||||
function forum_get_user_most_active_topic_info(int $userId): ?object {
|
||||
if($userId < 1)
|
||||
return null;
|
||||
|
||||
global $cfg;
|
||||
|
||||
$getActiveForum = \Misuzu\DB::prepare(sprintf(
|
||||
'SELECT topic_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = :user AND post_deleted IS NULL AND forum_id NOT IN (%s) GROUP BY topic_id ORDER BY post_count DESC LIMIT 1',
|
||||
implode(',', $cfg->getArray('forum_leader.unranked.forum'))
|
||||
));
|
||||
$getActiveForum->bind('user', $userId);
|
||||
|
||||
return $getActiveForum->fetchObject();
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
define('MSZ_TOPIC_TITLE_LENGTH_MIN', 3);
|
||||
define('MSZ_TOPIC_TITLE_LENGTH_MAX', 100);
|
||||
define('MSZ_POST_TEXT_LENGTH_MIN', 1);
|
||||
define('MSZ_POST_TEXT_LENGTH_MAX', 60000);
|
||||
|
||||
function forum_validate_title(string $title): string {
|
||||
$length = mb_strlen(trim($title));
|
||||
|
||||
if($length < MSZ_TOPIC_TITLE_LENGTH_MIN) {
|
||||
return 'too-short';
|
||||
}
|
||||
|
||||
if($length > MSZ_TOPIC_TITLE_LENGTH_MAX) {
|
||||
return 'too-long';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function forum_validate_post(string $text): string {
|
||||
$length = mb_strlen(trim($text));
|
||||
|
||||
if($length < MSZ_POST_TEXT_LENGTH_MIN) {
|
||||
return 'too-short';
|
||||
}
|
||||
|
||||
if($length > MSZ_POST_TEXT_LENGTH_MAX) {
|
||||
return 'too-long';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
|
@ -22,6 +22,7 @@ use Misuzu\Comments\Comments;
|
|||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Counters\Counters;
|
||||
use Misuzu\Emoticons\Emotes;
|
||||
use Misuzu\Forum\Forum;
|
||||
use Misuzu\Home\HomeRoutes;
|
||||
use Misuzu\Info\InfoRoutes;
|
||||
use Misuzu\News\News;
|
||||
|
@ -64,28 +65,30 @@ class MisuzuContext {
|
|||
private Sessions $sessions;
|
||||
private Counters $counters;
|
||||
private ProfileFields $profileFields;
|
||||
private Forum $forum;
|
||||
private AuthInfo $authInfo;
|
||||
|
||||
public function __construct(IDbConnection $dbConn, IConfig $config) {
|
||||
$this->dbConn = $dbConn;
|
||||
$this->config = $config;
|
||||
$this->auditLog = new AuditLog($this->dbConn);
|
||||
$this->emotes = new Emotes($this->dbConn);
|
||||
$this->changelog = new Changelog($this->dbConn);
|
||||
$this->news = new News($this->dbConn);
|
||||
$this->comments = new Comments($this->dbConn);
|
||||
$this->loginAttempts = new LoginAttempts($this->dbConn);
|
||||
$this->recoveryTokens = new RecoveryTokens($this->dbConn);
|
||||
$this->modNotes = new ModNotes($this->dbConn);
|
||||
$this->bans = new Bans($this->dbConn);
|
||||
$this->warnings = new Warnings($this->dbConn);
|
||||
$this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
|
||||
$this->roles = new Roles($this->dbConn);
|
||||
$this->users = new Users($this->dbConn);
|
||||
$this->sessions = new Sessions($this->dbConn);
|
||||
$this->counters = new Counters($this->dbConn);
|
||||
$this->profileFields = new ProfileFields($this->dbConn);
|
||||
$this->authInfo = new AuthInfo;
|
||||
$this->bans = new Bans($this->dbConn);
|
||||
$this->changelog = new Changelog($this->dbConn);
|
||||
$this->comments = new Comments($this->dbConn);
|
||||
$this->counters = new Counters($this->dbConn);
|
||||
$this->emotes = new Emotes($this->dbConn);
|
||||
$this->forum = new Forum($this->dbConn);
|
||||
$this->loginAttempts = new LoginAttempts($this->dbConn);
|
||||
$this->modNotes = new ModNotes($this->dbConn);
|
||||
$this->news = new News($this->dbConn);
|
||||
$this->profileFields = new ProfileFields($this->dbConn);
|
||||
$this->recoveryTokens = new RecoveryTokens($this->dbConn);
|
||||
$this->roles = new Roles($this->dbConn);
|
||||
$this->sessions = new Sessions($this->dbConn);
|
||||
$this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
|
||||
$this->users = new Users($this->dbConn);
|
||||
$this->warnings = new Warnings($this->dbConn);
|
||||
}
|
||||
|
||||
public function getDbConn(): IDbConnection {
|
||||
|
@ -177,6 +180,10 @@ class MisuzuContext {
|
|||
return $this->profileFields;
|
||||
}
|
||||
|
||||
public function getForum(): Forum {
|
||||
return $this->forum;
|
||||
}
|
||||
|
||||
public function createAuthTokenPacker(): AuthTokenPacker {
|
||||
return new AuthTokenPacker($this->config->getString('auth.secret', 'meow'));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace Misuzu;
|
|||
|
||||
use InvalidArgumentException;
|
||||
use Twig\Environment as TwigEnvironment;
|
||||
use Twig\TwigFunction;
|
||||
use Twig_Extensions_Extension_Date;
|
||||
use Twig\Loader\FilesystemLoader as TwigLoaderFilesystem;
|
||||
use Misuzu\MisuzuContext;
|
||||
|
@ -29,6 +30,10 @@ final class Template {
|
|||
self::$loader->addPath($path);
|
||||
}
|
||||
|
||||
public static function addFunction(string $name, callable $body): void {
|
||||
self::$env->addFunction(new TwigFunction($name, $body));
|
||||
}
|
||||
|
||||
public static function renderRaw(string $file, array $vars = []): string {
|
||||
if(!defined('MSZ_TPL_RENDER')) {
|
||||
define('MSZ_TPL_RENDER', microtime(true));
|
||||
|
|
|
@ -23,7 +23,6 @@ final class TwigMisuzu extends AbstractExtension {
|
|||
new TwigFilter('html_colour', 'html_colour'),
|
||||
new TwigFilter('country_name', 'get_country_name'),
|
||||
new TwigFilter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)),
|
||||
new TwigFilter('perms_check', 'perms_check'),
|
||||
new TwigFilter('time_format', [$this, 'timeFormat']),
|
||||
];
|
||||
}
|
||||
|
@ -32,8 +31,6 @@ final class TwigMisuzu extends AbstractExtension {
|
|||
return [
|
||||
new TwigFunction('url_construct', 'url_construct'),
|
||||
new TwigFunction('url', 'url'),
|
||||
new TwigFunction('forum_may_have_children', 'forum_may_have_children'),
|
||||
new TwigFunction('forum_may_have_topics', 'forum_may_have_topics'),
|
||||
new TwigFunction('csrf_token', fn() => CSRF::token()),
|
||||
new TwigFunction('git_commit_hash', fn(bool $long = false) => GitInfo::hash($long)),
|
||||
new TwigFunction('git_tag', fn() => GitInfo::tag()),
|
||||
|
@ -52,7 +49,10 @@ final class TwigMisuzu extends AbstractExtension {
|
|||
return compact('ndx', 'pdo', 'total');
|
||||
}
|
||||
|
||||
public function timeFormat(DateTime|string|int $dateTime): string {
|
||||
public function timeFormat(DateTime|string|int|null $dateTime): string {
|
||||
if($dateTime === null)
|
||||
return 'never';
|
||||
|
||||
if(is_string($dateTime))
|
||||
$dateTime = new DateTime($dateTime);
|
||||
elseif(is_int($dateTime))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Misuzu\Users\Assets;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Routing\IRouter;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
|
@ -18,7 +19,9 @@ class AssetsRoutes {
|
|||
$this->bans = $bans;
|
||||
$this->users = $users;
|
||||
|
||||
$router->get('/assets/avatar', [$this, 'getAvatar']);
|
||||
$router->get('/assets/avatar/:filename', [$this, 'getAvatar']);
|
||||
$router->get('/assets/profile-background', [$this, 'getProfileBackground']);
|
||||
$router->get('/assets/profile-background/:filename', [$this, 'getProfileBackground']);
|
||||
$router->get('/user-assets.php', [$this, 'getUserAssets']);
|
||||
}
|
||||
|
@ -32,7 +35,7 @@ class AssetsRoutes {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function getAvatar($response, $request, string $fileName) {
|
||||
public function getAvatar($response, $request, string $fileName = '') {
|
||||
$userId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/no-avatar.png', MSZ_PUBLIC);
|
||||
|
||||
|
@ -46,17 +49,19 @@ class AssetsRoutes {
|
|||
if($userAssetInfo->isPresent())
|
||||
$assetInfo = $userAssetInfo;
|
||||
}
|
||||
} catch(RuntimeException $ex) {}
|
||||
} catch(RuntimeException $ex) {
|
||||
} catch(InvalidArgumentException $ex) {}
|
||||
|
||||
return $this->serveAsset($response, $request, $assetInfo);
|
||||
}
|
||||
|
||||
public function getProfileBackground($response, $request, string $fileName) {
|
||||
public function getProfileBackground($response, $request, string $fileName = '') {
|
||||
$userId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
|
||||
try {
|
||||
$userInfo = $this->users->getUser($userId, 'id');
|
||||
} catch(RuntimeException $ex) {}
|
||||
} catch(RuntimeException $ex) {
|
||||
} catch(InvalidArgumentException $ex) {}
|
||||
|
||||
if(!empty($userInfo)) {
|
||||
$userAssetInfo = new UserBackgroundAsset($userInfo);
|
||||
|
|
|
@ -6,6 +6,7 @@ use Index\TimeZoneInfo;
|
|||
use Index\Colour\Colour;
|
||||
use Index\Data\IDbResult;
|
||||
use Index\Net\IPAddress;
|
||||
use Misuzu\Parsers\Parser;
|
||||
|
||||
class UserInfo {
|
||||
private string $id;
|
||||
|
@ -182,6 +183,18 @@ class UserInfo {
|
|||
return $this->aboutParser;
|
||||
}
|
||||
|
||||
public function isAboutBodyPlain(): bool {
|
||||
return $this->aboutParser === Parser::PLAIN;
|
||||
}
|
||||
|
||||
public function isAboutBodyBBCode(): bool {
|
||||
return $this->aboutParser === Parser::BBCODE;
|
||||
}
|
||||
|
||||
public function isAboutBodyMarkdown(): bool {
|
||||
return $this->aboutParser === Parser::MARKDOWN;
|
||||
}
|
||||
|
||||
public function hasSignatureContent(): bool {
|
||||
return $this->signatureContent !== null && $this->signatureContent !== '';
|
||||
}
|
||||
|
@ -194,6 +207,18 @@ class UserInfo {
|
|||
return $this->signatureParser;
|
||||
}
|
||||
|
||||
public function isSignatureBodyPlain(): bool {
|
||||
return $this->signatureParser === Parser::PLAIN;
|
||||
}
|
||||
|
||||
public function isSignatureBodyBBCode(): bool {
|
||||
return $this->signatureParser === Parser::BBCODE;
|
||||
}
|
||||
|
||||
public function isSignatureBodyMarkdown(): bool {
|
||||
return $this->signatureParser === Parser::MARKDOWN;
|
||||
}
|
||||
|
||||
public function hasBirthdate(): bool {
|
||||
return $this->birthdate !== null;
|
||||
}
|
||||
|
|
|
@ -186,6 +186,7 @@ class Users {
|
|||
'name' => self::GET_USER_NAME,
|
||||
'email' => self::GET_USER_MAIL,
|
||||
'profile' => self::GET_USER_ID | self::GET_USER_NAME,
|
||||
'search' => self::GET_USER_ID | self::GET_USER_NAME,
|
||||
'login' => self::GET_USER_NAME | self::GET_USER_MAIL,
|
||||
'recovery' => self::GET_USER_MAIL,
|
||||
];
|
||||
|
|
|
@ -33,7 +33,7 @@ function manage_get_menu(int $userId): array {
|
|||
$menu['News']['Categories'] = url('manage-news-categories');
|
||||
|
||||
if(perms_check_user(MSZ_PERMS_FORUM, $userId, MSZ_PERM_FORUM_MANAGE_FORUMS))
|
||||
$menu['Forum']['Categories'] = url('manage-forum-categories');
|
||||
$menu['Forum']['Permission Calculator'] = url('manage-forum-categories');
|
||||
if(perms_check_user(MSZ_PERMS_FORUM, $userId, MSZ_PERM_FORUM_TOPIC_REDIRS))
|
||||
$menu['Forum']['Topic Redirects'] = url('manage-forum-topic-redirs');
|
||||
|
||||
|
|
107
src/perms.php
107
src/perms.php
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
define('MSZ_PERMS_ALLOW', 'allow');
|
||||
define('MSZ_PERMS_DENY', 'deny');
|
||||
|
||||
define('MSZ_PERMS_GENERAL', 'general');
|
||||
define('MSZ_PERM_GENERAL_CAN_MANAGE', 0x00000001);
|
||||
define('MSZ_PERM_GENERAL_VIEW_LOGS', 0x00000002);
|
||||
|
@ -54,8 +57,30 @@ define('MSZ_PERM_MODES', [
|
|||
MSZ_PERMS_NEWS, MSZ_PERMS_FORUM, MSZ_PERMS_COMMENTS,
|
||||
]);
|
||||
|
||||
define('MSZ_PERMS_ALLOW', 'allow');
|
||||
define('MSZ_PERMS_DENY', 'deny');
|
||||
define('MSZ_FORUM_PERMS_GENERAL', 'forum');
|
||||
|
||||
define('MSZ_FORUM_PERM_LIST_FORUM', 0x00000001); // can see stats, but will get error when trying to view
|
||||
define('MSZ_FORUM_PERM_VIEW_FORUM', 0x00000002);
|
||||
|
||||
define('MSZ_FORUM_PERM_CREATE_TOPIC', 0x00000400);
|
||||
//define('MSZ_FORUM_PERM_DELETE_TOPIC', 0x00000800); // use MSZ_FORUM_PERM_DELETE_ANY_POST instead
|
||||
define('MSZ_FORUM_PERM_MOVE_TOPIC', 0x00001000);
|
||||
define('MSZ_FORUM_PERM_LOCK_TOPIC', 0x00002000);
|
||||
define('MSZ_FORUM_PERM_STICKY_TOPIC', 0x00004000);
|
||||
define('MSZ_FORUM_PERM_ANNOUNCE_TOPIC', 0x00008000);
|
||||
define('MSZ_FORUM_PERM_GLOBAL_ANNOUNCE_TOPIC', 0x00010000);
|
||||
define('MSZ_FORUM_PERM_BUMP_TOPIC', 0x00020000);
|
||||
//define('MSZ_FORUM_PERM_PRIORITY_VOTE', 0x00040000); // feature postponed, perhaps reuse if it makes sense to
|
||||
|
||||
define('MSZ_FORUM_PERM_CREATE_POST', 0x00100000);
|
||||
define('MSZ_FORUM_PERM_EDIT_POST', 0x00200000);
|
||||
define('MSZ_FORUM_PERM_EDIT_ANY_POST', 0x00400000);
|
||||
define('MSZ_FORUM_PERM_DELETE_POST', 0x00800000);
|
||||
define('MSZ_FORUM_PERM_DELETE_ANY_POST', 0x01000000);
|
||||
|
||||
define('MSZ_FORUM_PERM_MODES', [
|
||||
MSZ_FORUM_PERMS_GENERAL,
|
||||
]);
|
||||
|
||||
function perms_get_keys(array $modes = MSZ_PERM_MODES): array {
|
||||
$perms = [];
|
||||
|
@ -278,3 +303,81 @@ function perms_for_comments(string|int $userId): array {
|
|||
'can_vote' => MSZ_PERM_COMMENTS_VOTE,
|
||||
]);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if($user < 0 || $forum < 0)
|
||||
return $perms;
|
||||
|
||||
static $memo = [];
|
||||
$memoId = "{$forum}-{$user}";
|
||||
|
||||
if(array_key_exists($memoId, $memo))
|
||||
return $memo[$memoId];
|
||||
|
||||
if($forum > 0)
|
||||
$perms = forum_perms_get_user(
|
||||
forum_get_parent_id($forum),
|
||||
$user
|
||||
);
|
||||
|
||||
$getPerms = \Misuzu\DB::prepare(sprintf(
|
||||
'
|
||||
SELECT %s
|
||||
FROM `msz_forum_permissions`
|
||||
WHERE (`forum_id` = :forum_id OR `forum_id` IS NULL)
|
||||
AND (
|
||||
(`user_id` IS NULL AND `role_id` IS NULL)
|
||||
OR (`user_id` = :user_id_1 AND `role_id` IS NULL)
|
||||
OR (
|
||||
`user_id` IS NULL
|
||||
AND `role_id` IN (
|
||||
SELECT `role_id`
|
||||
FROM `msz_users_roles`
|
||||
WHERE `user_id` = :user_id_2
|
||||
)
|
||||
)
|
||||
)
|
||||
',
|
||||
perms_get_select(MSZ_FORUM_PERM_MODES)
|
||||
));
|
||||
$getPerms->bind('forum_id', $forum);
|
||||
$getPerms->bind('user_id_1', $user);
|
||||
$getPerms->bind('user_id_2', $user);
|
||||
|
||||
$userPerms = $getPerms->fetch();
|
||||
foreach($perms as $key => $value)
|
||||
$perms[$key] |= $userPerms[$key] ?? 0;
|
||||
|
||||
return $memo[$memoId] = $perms;
|
||||
}
|
||||
|
||||
function forum_perms_check_user(
|
||||
string $prefix,
|
||||
?int $forumId,
|
||||
?int $userId,
|
||||
int $perm,
|
||||
bool $strict = false
|
||||
): bool {
|
||||
return perms_check(forum_perms_get_user($forumId, $userId)[$prefix] ?? 0, $perm, $strict);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ define('MSZ_URLS', [
|
|||
'info' => ['/info/<title>'],
|
||||
|
||||
'search-index' => ['/search.php'],
|
||||
'search-query' => ['/search.php', ['q' => '<query>']],
|
||||
'search-query' => ['/search.php', ['q' => '<query>'], '<section>'],
|
||||
|
||||
'auth-login' => ['/auth/login.php', ['username' => '<username>', 'redirect' => '<redirect>']],
|
||||
'auth-login-welcome' => ['/auth/login.php', ['welcome' => '1', 'username' => '<username>']],
|
||||
|
@ -46,6 +46,7 @@ define('MSZ_URLS', [
|
|||
'forum-topic-new' => ['/forum/posting.php', ['f' => '<forum>']],
|
||||
'forum-reply-new' => ['/forum/posting.php', ['t' => '<topic>']],
|
||||
'forum-category' => ['/forum/forum.php', ['f' => '<forum>', 'p' => '<page>']],
|
||||
'forum-category-root' => ['/forum/index.php', [], '<forum>'],
|
||||
'forum-topic' => ['/forum/topic.php', ['t' => '<topic>', 'page' => '<page>']],
|
||||
'forum-topic-create' => ['/forum/posting.php', ['f' => '<forum>']],
|
||||
'forum-topic-bump' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'bump', 'csrf' => '{csrf}']],
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
{% extends 'forum/master.twig' %}
|
||||
{% from 'forum/macros.twig' import forum_category_listing, forum_topic_listing, forum_category_buttons, forum_header, forum_category_tools %}
|
||||
|
||||
{% set title = forum_info.forum_name %}
|
||||
{% set title = forum_info.name %}
|
||||
{% set canonical_url = url('forum-category', {
|
||||
'forum': forum_info.forum_id,
|
||||
'forum': forum_info.id,
|
||||
'page': forum_pagination.page|default(0) > 1 ? forum_pagination.page : 0,
|
||||
}) %}
|
||||
|
||||
{% block content %}
|
||||
{{ forum_header(forum_info.forum_name, forum_breadcrumbs, true, canonical_url, [
|
||||
{{ forum_header(forum_info.name, forum_breadcrumbs, true, canonical_url, [
|
||||
{
|
||||
'html': '<i class="far fa-check-circle"></i> Mark as Read',
|
||||
'url': url('forum-mark-single', {'forum': forum_info.forum_id}),
|
||||
'url': url('forum-mark-single', {'forum': forum_info.id}),
|
||||
'display': forum_show_mark_as_read,
|
||||
'method': 'POST',
|
||||
}
|
||||
]) }}
|
||||
|
||||
{% if forum_may_have_children and forum_info.forum_subforums|length > 0 %}
|
||||
{{ forum_category_listing(forum_info.forum_subforums, 'Forums') }}
|
||||
{% if forum_children|length > 0 %}
|
||||
{{ forum_category_listing(forum_children, 'Forums') }}
|
||||
{% endif %}
|
||||
|
||||
{% if forum_may_have_topics %}
|
||||
{% if forum_info.mayHaveTopics %}
|
||||
{% set category_tools = forum_category_tools(forum_info, forum_perms, forum_pagination) %}
|
||||
{{ category_tools }}
|
||||
{{ forum_topic_listing(forum_topics) }}
|
||||
|
|
|
@ -3,22 +3,12 @@
|
|||
{% from 'forum/macros.twig' import forum_category_listing %}
|
||||
|
||||
{% set title = 'Forum Listing' %}
|
||||
{% set canonical_url = '/forum/' %}
|
||||
{% set canonical_url = url('forum-index') %}
|
||||
|
||||
{% block content %}
|
||||
{% if not forum_empty %}
|
||||
{% for category in forum_categories %}
|
||||
{% if category.forum_children > 0 %}
|
||||
{{ forum_category_listing(
|
||||
category.forum_subforums,
|
||||
category.forum_name,
|
||||
category.forum_colour,
|
||||
category.forum_id == constant('MSZ_FORUM_ROOT')
|
||||
? ''
|
||||
: 'f' ~ category.forum_id,
|
||||
category.forum_icon|default('')
|
||||
) }}
|
||||
{% endif %}
|
||||
{{ forum_category_listing(category) }}
|
||||
{% endfor %}
|
||||
|
||||
{% if forum_show_mark_as_read %}
|
||||
|
|
|
@ -23,7 +23,17 @@
|
|||
]) }}
|
||||
|
||||
<div class="container forum__leaderboard__categories">
|
||||
{% for id, name in leaderboard_categories %}
|
||||
<a href="{{ url('forum-leaderboard', {'mode': leaderboard_mode}) }}" class="forum__leaderboard__category{% if leaderboard_id == '' %} forum__leaderboard__category--active{% endif %}">All Time</a>
|
||||
</div>
|
||||
|
||||
<div class="container forum__leaderboard__categories">
|
||||
{% for id, name in leaderboard_years %}
|
||||
<a href="{{ url('forum-leaderboard', {'id': id, 'mode': leaderboard_mode}) }}" class="forum__leaderboard__category{% if leaderboard_id == id %} forum__leaderboard__category--active{% endif %}">{{ name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="container forum__leaderboard__categories">
|
||||
{% for id, name in leaderboard_months %}
|
||||
<a href="{{ url('forum-leaderboard', {'id': id, 'mode': leaderboard_mode}) }}" class="forum__leaderboard__category{% if leaderboard_id == id %} forum__leaderboard__category--active{% endif %}">{{ name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -31,14 +41,14 @@
|
|||
{% if leaderboard_mode == 'markdown' %}
|
||||
<textarea class="input__textarea forum__leaderboard__markdown">{{ leaderboard_markdown }}</textarea>
|
||||
{% else %}
|
||||
{% for user in leaderboard_data %}
|
||||
<div class="container forum__leaderboard__user forum__leaderboard__user--rank-{{ user.rank }}">
|
||||
<a href="{{ url('user-profile', {'user': user.user_id}) }}" class="forum__leaderboard__user__background"></a>
|
||||
{% for ranking in leaderboard_data %}
|
||||
<div class="container forum__leaderboard__user forum__leaderboard__user--rank-{{ ranking.position }}">
|
||||
<a href="{{ url('user-profile', {'user': ranking.user.id|default()}) }}" class="forum__leaderboard__user__background"></a>
|
||||
<div class="forum__leaderboard__user__content">
|
||||
<div class="forum__leaderboard__user__rank">{{ user.rank|number_format }}</div>
|
||||
<div class="forum__leaderboard__user__avatar">{{ avatar(user.user_id, user.rank == 1 ? 50 : 40, user.username) }}</div>
|
||||
<div class="forum__leaderboard__user__username">{{ user.username }}</div>
|
||||
<div class="forum__leaderboard__user__posts">{{ user.posts|number_format }} posts</div>
|
||||
<div class="forum__leaderboard__user__rank">{{ ranking.position|number_format }}</div>
|
||||
<div class="forum__leaderboard__user__avatar">{{ avatar(ranking.user.id|default(), ranking.position == 1 ? 50 : 40, ranking.user.name|default('Deleted User')) }}</div>
|
||||
<div class="forum__leaderboard__user__username">{{ ranking.user.name|default('Deleted User') }}</div>
|
||||
<div class="forum__leaderboard__user__posts">{{ ranking.postsCount|number_format }} post{{ ranking.postsCount == 1 ? '' : 's' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
|
|
@ -2,10 +2,29 @@
|
|||
{% from _self import forum_category_entry %}
|
||||
{% from 'macros.twig' import container_title %}
|
||||
|
||||
{% if forums.info is defined %}
|
||||
{% set title = forums.info.name|default('Forums') %}
|
||||
{% set icon = forums.info.iconForDisplay|default('fas fa-folder fa-fw') %}
|
||||
{% set colour = forums.colour|default(null) %}
|
||||
{% set id = forums.info.id is defined ? 'f' ~ forums.info.id : '' %}
|
||||
{% set forums = forums.children %}
|
||||
{% elseif forums.children is defined %}
|
||||
{% set title = 'Forums' %}
|
||||
{% set icon = 'fas fa-folder fa-fw' %}
|
||||
{% set colour = null %}
|
||||
{% set id = '' %}
|
||||
{% set forums = forums.children %}
|
||||
{% else %}
|
||||
{% set title = title|default('Forums') %}
|
||||
{% set icon = icon|default('fas fa-folder fa-fw') %}
|
||||
{% set colour = colour|default(null) %}
|
||||
{% set id = id|default('') %}
|
||||
{% endif %}
|
||||
|
||||
<div class="container forum__categories"
|
||||
{% if colour is not null %}style="{{ colour|html_colour('--accent-colour') }}"{% endif %}
|
||||
{% if id|length > 0 %}id="{{ id }}"{% endif %}>
|
||||
{{ container_title('<span class="' ~ icon|default('fas fa-folder fa-fw') ~ '"></span> ' ~ title) }}
|
||||
{{ container_title('<span class="' ~ icon ~ '"></span> ' ~ title) }}
|
||||
|
||||
{% if forums|length > 0 %}
|
||||
<div class="forum__categories__list">
|
||||
|
@ -25,15 +44,13 @@
|
|||
<div class="container forum__header">
|
||||
{% if breadcrumbs is iterable and breadcrumbs|length > 0 %}
|
||||
<div class="forum__header__breadcrumbs">
|
||||
{% for name, url in breadcrumbs %}
|
||||
{% if url != breadcrumbs|first %}
|
||||
<a href="{{ url('forum-index') }}" class="forum__header__breadcrumb">Forums</a>
|
||||
{% for category in breadcrumbs|reverse %}
|
||||
<div class="forum__header__breadcrumb__separator">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not (omit_last_breadcrumb|default(false) and url == breadcrumbs|last) %}
|
||||
<a href="{{ url }}" class="forum__header__breadcrumb">{{ name }}</a>
|
||||
{% if not (omit_last_breadcrumb|default(false) and category == breadcrumbs|first) %}
|
||||
<a href="{{ category.hasParent ? url('forum-category', {'forum': category.id}) : url('forum-category-root', {'forum': 'f' ~ category.id}) }}" class="forum__header__breadcrumb">{{ category.name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -68,15 +85,23 @@
|
|||
{% macro forum_category_tools(info, perms, pagination_info) %}
|
||||
{% from 'macros.twig' import pagination %}
|
||||
|
||||
{% set is_locked = info.forum_archived != 0 %}
|
||||
{% set can_topic = not is_locked and perms|perms_check(constant('MSZ_FORUM_PERM_CREATE_TOPIC')) %}
|
||||
{% set pag = pagination(pagination_info, url('forum-category'), null, {'f': info.forum_id}) %}
|
||||
{% if info.forum_id is defined %}
|
||||
{% set forum_id = info.forum_id %}
|
||||
{% set is_archived = info.forum_archived != 0 %}
|
||||
{% else %}
|
||||
{% set forum_id = info.id %}
|
||||
{% set is_archived = info.isArchived %}
|
||||
{% endif %}
|
||||
|
||||
{% set is_locked = is_archived %}
|
||||
{% set can_topic = not is_locked and perms.can_create_topic %}
|
||||
{% set pag = pagination(pagination_info, url('forum-category'), null, {'f': forum_id}) %}
|
||||
|
||||
{% if can_topic or pag|trim|length > 0 %}
|
||||
<div class="container forum__actions">
|
||||
<div class="forum__actions__buttons">
|
||||
{% if can_topic %}
|
||||
<a href="{{ url('forum-topic-new', {'forum': info.forum_id}) }}" class="input__button forum__actions__button">New Topic</a>
|
||||
<a href="{{ url('forum-topic-new', {'forum': forum_id}) }}" class="input__button forum__actions__button">New Topic</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -90,13 +115,13 @@
|
|||
{% macro forum_topic_tools(info, pagination_info, can_reply) %}
|
||||
{% from 'macros.twig' import pagination %}
|
||||
|
||||
{% set pag = pagination(pagination_info, url('forum-topic'), null, {'t': info.topic_id}, 'page') %}
|
||||
{% set pag = pagination(pagination_info, url('forum-topic'), null, {'t': info.id}, 'page') %}
|
||||
|
||||
{% if can_reply or pag|trim|length > 0 %}
|
||||
<div class="container forum__actions">
|
||||
<div class="forum__actions__buttons">
|
||||
{% if can_reply %}
|
||||
<a href="{{ url('forum-reply-new', {'topic': info.topic_id}) }}" class="input__button">Reply</a>
|
||||
<a href="{{ url('forum-reply-new', {'topic': info.id}) }}" class="input__button">Reply</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -109,94 +134,136 @@
|
|||
|
||||
{% macro forum_category_entry(forum, forum_unread, forum_icon) %}
|
||||
{% from 'macros.twig' import avatar %}
|
||||
{% set forum_unread = forum_unread|default(forum.forum_unread|default(false)) ? 'unread' : 'read' %}
|
||||
|
||||
{% if forum.info is defined %}
|
||||
{% set forum_id = forum.info.id %}
|
||||
{% set forum_name = forum.info.name %}
|
||||
{% set forum_desc = forum.info.description|default('') %}
|
||||
{% set forum_is_link = forum.info.isLink %}
|
||||
{% set forum_may_have_children = forum.info.mayHaveChildren %}
|
||||
{% set forum_link_clicks = forum.info.linkClicks %}
|
||||
{% set forum_count_topics = forum.info.topicsCount %}
|
||||
{% set forum_count_posts = forum.info.postsCount %}
|
||||
{% set forum_show_activity = forum.info.mayHaveTopics or forum.info.hasLinkClicks %}
|
||||
{% set forum_unread = forum.unread %}
|
||||
{% set forum_colour = forum.colour %}
|
||||
|
||||
{% set forum_has_recent_post = forum.lastPost is defined %}
|
||||
{% set children = forum.children %}
|
||||
|
||||
{% if forum_has_recent_post %}
|
||||
{% set forum_recent_post_id = forum.lastPost.info.id %}
|
||||
{% set forum_recent_topic_title = forum.lastPost.topicInfo.title %}
|
||||
{% set forum_recent_post_created = forum.lastPost.info.createdTime %}
|
||||
|
||||
{% set forum_has_recent_post_user = forum_has_recent_post and forum.lastPost.user is defined %}
|
||||
{% if forum_has_recent_post_user %}
|
||||
{% set forum_recent_post_user_id = forum.lastPost.user.id %}
|
||||
{% set forum_recent_post_user_name = forum.lastPost.user.name %}
|
||||
{% set forum_recent_post_user_colour = '--user-colour: ' ~ forum.lastPost.colour %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if forum_icon is empty %}
|
||||
{% if forum.forum_icon is defined and forum.forum_icon is not empty %}
|
||||
{% set forum_icon = forum.forum_icon %}
|
||||
{% elseif forum.forum_archived is defined and forum.forum_archived %}
|
||||
{% set forum_icon = 'fas fa-archive fa-fw' %}
|
||||
{% elseif forum.forum_type is defined and forum.forum_type != constant('MSZ_FORUM_TYPE_DISCUSSION') %}
|
||||
{% if forum.forum_type == constant('MSZ_FORUM_TYPE_LINK') %}
|
||||
{% set forum_icon = 'fas fa-link fa-fw' %}
|
||||
{% elseif forum.forum_type == constant('MSZ_FORUM_TYPE_CATEGORY') %}
|
||||
{% set forum_icon = 'fas fa-folder fa-fw' %}
|
||||
{% set forum_icon = forum.info.iconForDisplay %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set forum_icon = 'fas fa-comments fa-fw' %}
|
||||
{% endif %}
|
||||
{% set forum_id = null %}
|
||||
{% set forum_name = 'Forums' %}
|
||||
{% set forum_desc = null %}
|
||||
{% set forum_is_link = false %}
|
||||
{% set forum_may_have_children = true %}
|
||||
{% set forum_count_topics = 0 %}
|
||||
{% set forum_count_posts = 0 %}
|
||||
{% set forum_show_activity = false %}
|
||||
{% set forum_unread = false %}
|
||||
{% set forum_colour = null %}
|
||||
{% set forum_has_recent_post = false %}
|
||||
{% set children = forum %}
|
||||
{% endif %}
|
||||
|
||||
<div class="forum__category">
|
||||
<a href="{{ url('forum-category', {'forum': forum.forum_id}) }}" class="forum__category__link"></a>
|
||||
<div class="forum__category"{% if forum_colour is not null %} style="--accent-colour: {{ forum_colour }}"{% endif %}>
|
||||
<a href="{{ url('forum-category', {'forum': forum_id}) }}" class="forum__category__link"></a>
|
||||
|
||||
<div class="forum__category__container">
|
||||
<div class="forum__category__icon forum__category__icon--{{ forum_unread }}">
|
||||
<div class="forum__category__icon forum__category__icon--{{ forum_unread ? 'unread' : 'read' }}">
|
||||
<span class="{{ forum_icon }}"></span>
|
||||
</div>
|
||||
|
||||
<div class="forum__category__details">
|
||||
<div class="forum__category__title">
|
||||
{{ forum.forum_name }}
|
||||
{{ forum_name }}
|
||||
</div>
|
||||
|
||||
{% if forum_desc is not null %}
|
||||
<div class="forum__category__description">
|
||||
{{ forum.forum_description|nl2br }}
|
||||
{{ forum_desc|nl2br }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if forum.forum_subforums is defined and forum.forum_subforums|length > 0 %}
|
||||
{% if children|length > 0 %}
|
||||
<div class="forum__category__subforums">
|
||||
{% for subforum in forum.forum_subforums %}
|
||||
<a href="{{ url('forum-category', {'forum': subforum.forum_id}) }}"
|
||||
class="forum__category__subforum{% if subforum.forum_unread %} forum__category__subforum--unread{% endif %}">
|
||||
{{ subforum.forum_name }}
|
||||
{% for child in children %}
|
||||
{% if child.info is defined %}
|
||||
{% set child_id = child.info.id %}
|
||||
{% set child_name = child.info.name %}
|
||||
{% set child_unread = child.unread %}
|
||||
{% else %}
|
||||
{% set child_id = child.forum_id %}
|
||||
{% set child_name = child.forum_name %}
|
||||
{% set child_unread = child.forum_unread %}
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ url('forum-category', {'forum': child_id}) }}"
|
||||
class="forum__category__subforum{% if child_unread %} forum__category__subforum--unread{% endif %}">
|
||||
{{ child_name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if forum.forum_type == constant('MSZ_FORUM_TYPE_LINK') %}
|
||||
{% if forum.forum_link_clicks is not null %}
|
||||
{% if forum_is_link %}
|
||||
{% if forum_link_clicks is not null %}
|
||||
<div class="forum__category__stats">
|
||||
<div class="forum__category__stat" title="Clicks">{{ forum.forum_link_clicks|number_format }}</div>
|
||||
<div class="forum__category__stat" title="Clicks">{{ forum_link_clicks|number_format }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elseif forum_may_have_children(forum.forum_type) %}
|
||||
{% elseif forum_may_have_children %}
|
||||
<div class="forum__category__stats">
|
||||
<div class="forum__category__stat" title="Topics">{{ forum.forum_count_topics|number_format }}</div>
|
||||
<div class="forum__category__stat" title="Posts">{{ forum.forum_count_posts|number_format }}</div>
|
||||
<div class="forum__category__stat" title="Topics">{{ forum_count_topics|number_format }}</div>
|
||||
<div class="forum__category__stat" title="Posts">{{ forum_count_posts|number_format }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if forum_may_have_topics(forum.forum_type) or forum.forum_link_clicks is not null %}
|
||||
<div class="forum__category__activity{% if forum.forum_link_clicks is not null %} forum__category__activity--empty{% endif %}">
|
||||
{% if forum.forum_type != constant('MSZ_FORUM_TYPE_LINK') %}
|
||||
{% if forum.recent_topic_id is not defined %}
|
||||
<div class="forum__category__activity__none">
|
||||
There are no posts in this forum yet.
|
||||
</div>
|
||||
{% else %}
|
||||
{% if forum_show_activity %}
|
||||
<div class="forum__category__activity{% if forum_link_clicks is not null %} forum__category__activity--empty{% endif %}">
|
||||
{% if not forum_is_link %}
|
||||
{% if forum_has_recent_post %}
|
||||
<div class="forum__category__activity__details">
|
||||
<a class="forum__category__activity__post"
|
||||
href="{{ url('forum-post', {'post': forum.recent_post_id, 'post_fragment': 'p' ~ forum.recent_post_id}) }}">
|
||||
{{ forum.recent_topic_title }}
|
||||
href="{{ url('forum-post', {'post': forum_recent_post_id, 'post_fragment': 'p' ~ forum_recent_post_id}) }}">
|
||||
{{ forum_recent_topic_title }}
|
||||
</a>
|
||||
|
||||
<div class="forum__category__activity__info">
|
||||
{% if forum.recent_post_user_id is not null %}
|
||||
<a href="{{ url('user-profile', {'user': forum.recent_post_user_id}) }}" class="forum__category__username"
|
||||
style="{{ forum.recent_post_user_colour|html_colour }}">{{ forum.recent_post_username }}</a>
|
||||
{% if forum_has_recent_post_user %}
|
||||
<a href="{{ url('user-profile', {'user': forum_recent_post_user_id}) }}" class="forum__category__username"
|
||||
style="{{ forum_recent_post_user_colour }}">{{ forum_recent_post_user_name }}</a>
|
||||
{% endif %}
|
||||
<time datetime="{{ forum.recent_post_created|date('c') }}" title="{{ forum.recent_post_created|date('r') }}">{{ forum.recent_post_created|time_format }}</time>
|
||||
<time datetime="{{ forum_recent_post_created|date('c') }}" title="{{ forum_recent_post_created|date('r') }}">{{ forum_recent_post_created|time_format }}</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if forum.recent_post_user_id is not null %}
|
||||
<a href="{{ url('user-profile', {'user': forum.recent_post_user_id}) }}" class="avatar forum__category__avatar">
|
||||
{{ avatar(forum.recent_post_user_id, 40, forum.recent_post_username) }}
|
||||
{% if forum_has_recent_post_user %}
|
||||
<a href="{{ url('user-profile', {'user': forum_recent_post_user_id}) }}" class="avatar forum__category__avatar">
|
||||
{{ avatar(forum_recent_post_user_id, 40, forum_recent_post_user_name) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="forum__category__activity__none">
|
||||
There are no posts in this forum yet.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -221,7 +288,7 @@
|
|||
{% from _self import forum_topic_notice %}
|
||||
{% if redirect is not empty %}
|
||||
{% set body %}
|
||||
This topic redirects to <span class="forum__status__emphasis"><a href="{{ redirect.topic_redir_url }}" class="link">{{ redirect.topic_redir_url }}</a></span>.
|
||||
This topic redirects to <span class="forum__status__emphasis"><a href="{{ redirect.linkTarget }}" class="link">{{ redirect.linkTarget }}</a></span>.
|
||||
{% endset %}
|
||||
{{ forum_topic_notice('share', body) }}
|
||||
{% endif %}
|
||||
|
@ -267,33 +334,52 @@
|
|||
|
||||
{% macro forum_topic_entry(topic, topic_icon, topic_unread) %}
|
||||
{% from 'macros.twig' import avatar %}
|
||||
{% set topic_unread = topic_unread|default(topic.topic_unread|default(false)) %}
|
||||
{% set topic_important = topic.topic_type == constant('MSZ_TOPIC_TYPE_STICKY') or topic.topic_type == constant('MSZ_TOPIC_TYPE_ANNOUNCEMENT') or topic.topic_type == constant('MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT') %}
|
||||
|
||||
{% set topic_id = topic.info.id %}
|
||||
{% set topic_title = topic.info.title %}
|
||||
{% set topic_participated = topic.participated %}
|
||||
{% set topic_count_posts = topic.info.postsCount %}
|
||||
{% set topic_count_views = topic.info.viewsCount %}
|
||||
{% set topic_created = topic.info.createdTime %}
|
||||
{% set topic_locked = topic.info.isLocked %}
|
||||
{% set topic_deleted = topic.info.isDeleted %}
|
||||
{% set topic_pages = (topic.info.postsCount / 10)|round(0, 'ceil') %}
|
||||
|
||||
{% set has_topic_author = topic.user is defined %}
|
||||
{% if has_topic_author %}
|
||||
{% set topic_author_id = topic.user.id %}
|
||||
{% set topic_author_name = topic.user.name %}
|
||||
{% set topic_author_colour = '--user-colour: ' ~ topic.colour %}
|
||||
{% endif %}
|
||||
|
||||
{% set has_reply = topic.lastPost is defined %}
|
||||
{% if has_reply %}
|
||||
{% set reply_id = topic.lastPost.info.id %}
|
||||
{% set reply_created = topic.lastPost.info.createdTime %}
|
||||
|
||||
{% set has_reply_author = topic.lastPost.user is defined %}
|
||||
{% if has_reply_author %}
|
||||
{% set reply_author_id = topic.lastPost.user.id %}
|
||||
{% set reply_author_name = topic.lastPost.user.name %}
|
||||
{% set reply_author_colour = '--user-colour: ' ~ topic.lastPost.colour %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% set topic_unread = topic.unread %}
|
||||
{% set topic_important = topic.info.isImportant %}
|
||||
|
||||
{% if topic_icon is null %}
|
||||
{% if topic.topic_deleted is defined and topic.topic_deleted is not null %}
|
||||
{% set topic_icon = 'fas fa-trash-alt' %}
|
||||
{% elseif topic.topic_type is defined and topic.topic_type != constant('MSZ_TOPIC_TYPE_DISCUSSION') %}
|
||||
{% if topic.topic_type == constant('MSZ_TOPIC_TYPE_ANNOUNCEMENT') or topic.topic_type == constant('MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT') %}
|
||||
{% set topic_icon = 'fas fa-bullhorn' %}
|
||||
{% elseif topic.topic_type == constant('MSZ_TOPIC_TYPE_STICKY') %}
|
||||
{% set topic_icon = 'fas fa-thumbtack' %}
|
||||
{% endif %}
|
||||
{% elseif topic.topic_locked is defined and topic.topic_locked is not null %}
|
||||
{% set topic_icon = 'fas fa-lock' %}
|
||||
{% else %}
|
||||
{% set topic_icon = (topic_unread ? 'fas' : 'far') ~ ' fa-comment' %}
|
||||
{% endif %}
|
||||
{% set topic_icon = topic.info.iconForDisplay(topic.unread) %}
|
||||
{% endif %}
|
||||
|
||||
<div class="forum__topic{% if topic.topic_deleted is not null %} forum__topic--deleted{% elseif topic.topic_locked is not null and not topic_important %} forum__topic--locked{% endif %}">
|
||||
<a href="{{ url('forum-topic', {'topic': topic.topic_id}) }}" class="forum__topic__link"></a>
|
||||
<div class="forum__topic{% if topic_deleted %} forum__topic--deleted{% elseif topic_locked and not topic_important %} forum__topic--locked{% endif %}">
|
||||
<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_unread ? 'unread' : 'read' }}">
|
||||
<i class="{{ topic_icon }} fa-fw"></i>
|
||||
|
||||
{% if topic.topic_participated %}
|
||||
{% if topic_participated %}
|
||||
<div class="forum__topic__icon__participated" title="You have posted in this topic"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -301,39 +387,35 @@
|
|||
<div class="forum__topic__details">
|
||||
<div class="forum__topic__title">
|
||||
<span class="forum__topic__title__inner">
|
||||
{{ topic.topic_title }}
|
||||
{{ topic_title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="forum__topic__info">
|
||||
{% if topic.author_id is not null %}
|
||||
by <a
|
||||
href="{{ url('user-profile', {'user': topic.author_id}) }}"
|
||||
class="forum__topic__username"
|
||||
style="{{ topic.author_colour|html_colour }}">{{ topic.author_name }}</a>,
|
||||
|
||||
{% if has_topic_author %}
|
||||
by <a href="{{ url('user-profile', {'user': topic_author_id}) }}" class="forum__topic__username" style="{{ topic_author_colour }}">{{ topic_author_name }}</a>,
|
||||
{% endif %}
|
||||
<time datetime="{{ topic.topic_created|date('c') }}" title="{{ topic.topic_created|date('r') }}">{{ topic.topic_created|time_format }}</time>
|
||||
<time datetime="{{ topic_created|date('c') }}" title="{{ topic_created|date('r') }}">{{ topic_created|time_format }}</time>
|
||||
</div>
|
||||
|
||||
{% if topic.topic_pages|default(0) > 1 %}
|
||||
{% if topic_pages|default(0) > 1 %}
|
||||
<div class="forum__topic__pagination">
|
||||
{% set topic_pages_start_end = min(3, topic.topic_pages) %}
|
||||
{% set topic_pages_start_end = min(3, topic_pages) %}
|
||||
{% for i in 1..topic_pages_start_end %}
|
||||
<a href="{{ url('forum-topic', {'topic': topic.topic_id, 'page': i}) }}" class="forum__topic__pagination__item">
|
||||
<a href="{{ url('forum-topic', {'topic': topic_id, 'page': i}) }}" class="forum__topic__pagination__item">
|
||||
{{ i }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if topic.topic_pages > 3 %}
|
||||
{% if topic.topic_pages > 6 %}
|
||||
{% if topic_pages > 3 %}
|
||||
{% if topic_pages > 6 %}
|
||||
<div class="forum__topic__pagination__separator">
|
||||
<i class="fas fa-ellipsis-h"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set topic_pages_end_start = max(4, min(topic.topic_pages, topic.topic_pages - 2)) %}
|
||||
{% for i in topic_pages_end_start..topic.topic_pages %}
|
||||
<a href="{{ url('forum-topic', {'topic': topic.topic_id, 'page': i}) }}" class="forum__topic__pagination__item">
|
||||
{% set topic_pages_end_start = max(4, min(topic_pages, topic_pages - 2)) %}
|
||||
{% for i in topic_pages_end_start..topic_pages %}
|
||||
<a href="{{ url('forum-topic', {'topic': topic_id, 'page': i}) }}" class="forum__topic__pagination__item">
|
||||
{{ i }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
@ -343,27 +425,25 @@
|
|||
</div>
|
||||
|
||||
<div class="forum__topic__stats">
|
||||
<div class="forum__topic__stat" title="Posts">{{ topic.topic_count_posts|number_format }}</div>
|
||||
<div class="forum__topic__stat" title="Views">{{ topic.topic_count_views|number_format }}</div>
|
||||
<div class="forum__topic__stat" title="Posts">{{ topic_count_posts|number_format }}</div>
|
||||
<div class="forum__topic__stat" title="Views">{{ topic_count_views|number_format }}</div>
|
||||
</div>
|
||||
|
||||
<div class="forum__topic__activity">
|
||||
<div class="forum__topic__activity__details">
|
||||
{% if topic.respondent_id is not null %}
|
||||
<a href="{{ url('user-profile', {'user': topic.respondent_id}) }}" class="forum__topic__username"
|
||||
style="{{ topic.respondent_colour|html_colour }}">{{ topic.respondent_name }}</a>
|
||||
{% if has_reply %}
|
||||
{% if has_reply_author %}
|
||||
<a href="{{ url('user-profile', {'user': reply_author_id}) }}" class="forum__topic__username" style="{{ reply_author_colour }}">{{ reply_author_name }}</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="forum__topic__activity__post"
|
||||
href="{{ url('forum-post', {'post': topic.response_id, 'post_fragment': 'p' ~ topic.response_id}) }}">
|
||||
<time datetime="{{ topic.response_created|date('c') }}"
|
||||
title="{{ topic.response_created|date('r') }}">{{ topic.response_created|time_format }}</time>
|
||||
<a class="forum__topic__activity__post" href="{{ url('forum-post', {'post': reply_id, 'post_fragment': 'p' ~ reply_id}) }}">
|
||||
<time datetime="{{ reply_created|date('c') }}" title="{{ reply_created|date('r') }}">{{ reply_created|time_format }}</time>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if topic.respondent_id is not null %}
|
||||
<a href="{{ url('user-profile', {'user': topic.respondent_id}) }}" class="forum__topic__avatar">
|
||||
{{ avatar(topic.respondent_id, 30, topic.respondent_name) }}
|
||||
{% if has_reply and has_reply_author %}
|
||||
<a href="{{ url('user-profile', {'user': reply_author_id}) }}" class="forum__topic__avatar">
|
||||
{{ avatar(reply_author_id, 30, reply_author_name) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -381,41 +461,57 @@
|
|||
|
||||
{% macro forum_post_entry(post, user_id, perms) %}
|
||||
{% from 'macros.twig' import avatar %}
|
||||
{% set is_deleted = post.post_deleted is not null %}
|
||||
{% set can_post = perms|perms_check(constant('MSZ_FORUM_PERM_CREATE_POST')) %}
|
||||
{% set can_edit = perms|perms_check(constant('MSZ_FORUM_PERM_EDIT_ANY_POST')) or (
|
||||
user_id == post.poster_id
|
||||
and perms|perms_check(constant('MSZ_FORUM_PERM_EDIT_POST'))
|
||||
) %}
|
||||
{% set can_delete = not post.is_opening_post and (
|
||||
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')
|
||||
)
|
||||
) %}
|
||||
|
||||
<div class="container forum__post{% if is_deleted %} forum__post--deleted{% endif %}" id="p{{ post.post_id }}" style="{{ post.poster_colour|html_colour('--accent-colour') }}">
|
||||
{% set post_id = post.info.id %}
|
||||
{% set post_created = post.info.createdTime %}
|
||||
{% set post_edited = post.info.editedTime %}
|
||||
{% set post_is_deleted = post.info.isDeleted %}
|
||||
{% set post_is_op = post.isOriginalPost %}
|
||||
{% set post_body = post.info.body|escape|parse_text(post.info.parser) %}
|
||||
{% set post_is_markdown = post.info.isBodyMarkdown %}
|
||||
{% set post_show_signature = post.info.shouldDisplaySignature %}
|
||||
{% set post_can_be_deleted = post.info.canBeDeleted %}
|
||||
{% set topic_id = post.info.topicId %}
|
||||
|
||||
{% set has_author = post.user is defined %}
|
||||
{% if has_author %}
|
||||
{% set author_id = post.user.id %}
|
||||
{% set author_name = post.user.name %}
|
||||
{% set author_title = post.user.title %}
|
||||
{% set author_colour = post.colour %}
|
||||
{% set author_country = post.user.countryCode %}
|
||||
{% set author_created = post.user.createdTime %}
|
||||
{% set author_posts_count = post.postsCount %}
|
||||
{% set author_is_op = post.isOriginalPoster %}
|
||||
{% set signature_body = post.user.signatureContent|default('')|escape|parse_text(post.user.signatureParser) %}
|
||||
{% set signature_is_markdown = post.user.isSignatureBodyMarkdown %}
|
||||
{% endif %}
|
||||
|
||||
{% set viewer_is_author = has_author and user_id == author_id %}
|
||||
{% set can_edit = perms.can_edit_any_post|default(false) or (viewer_is_author and perms.can_edit_post|default(false)) %}
|
||||
{% set can_delete = not post_is_op and (perms.can_delete_any_post|default(false) or (viewer_is_author and perms.can_delete_post|default(false) and post_can_be_deleted)) %}
|
||||
|
||||
<div class="container forum__post{% if post_is_deleted %} forum__post--deleted{% endif %}" id="p{{ post_id }}"{% if author_colour is defined%} style="{{ author_colour }}"{% endif %}>
|
||||
<div class="forum__post__info">
|
||||
<div class="forum__post__info__background"></div>
|
||||
<div class="forum__post__info__content">
|
||||
{% if post.poster_id is not null %}
|
||||
<a class="forum__post__avatar" href="{{ url('user-profile', {'user': post.poster_id}) }}">
|
||||
{{ avatar(post.poster_id, 120, post.poster_name) }}
|
||||
{% if has_author %}
|
||||
<a class="forum__post__avatar" href="{{ url('user-profile', {'user': author_id}) }}">
|
||||
{{ avatar(author_id, 120, author_name) }}
|
||||
</a>
|
||||
|
||||
<a class="forum__post__username" href="{{ url('user-profile', {'user': post.poster_id}) }}">{{ post.poster_name }}</a>
|
||||
<a class="forum__post__username" href="{{ url('user-profile', {'user': author_id}) }}">{{ author_name }}</a>
|
||||
|
||||
{% if post.poster_title|length > 0 %}
|
||||
<div class="forum__post__usertitle">{{ post.poster_title }}</div>
|
||||
{% if author_title|length > 0 %}
|
||||
<div class="forum__post__usertitle">{{ author_title }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="forum__post__icons">
|
||||
<div class="flag flag--{{ post.poster_country|lower }}" title="{{ post.poster_country|country_name }}"></div>
|
||||
<div class="forum__post__posts-count">{{ post.poster_post_count|number_format }} posts</div>
|
||||
<div class="flag flag--{{ author_country|lower }}" title="{{ author_country|country_name }}"></div>
|
||||
{% if author_posts_count is not null %}<div class="forum__post__posts-count">{{ author_posts_count|number_format }} posts</div>{% endif %}
|
||||
</div>
|
||||
|
||||
{% if post.is_original_poster %}
|
||||
{% if author_is_op %}
|
||||
<div class="forum__post__badge forum__post__badge--original-poster">
|
||||
<div class="forum__post__badge__desktop">Original Poster</div>
|
||||
<div class="forum__post__badge__mobile">OP</div>
|
||||
|
@ -423,7 +519,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="forum__post__joined">
|
||||
joined <time datetime="{{ post.poster_joined|date('c') }}" title="{{ post.poster_joined|date('r') }}">{{ post.poster_joined|time_format }}</time>
|
||||
joined <time datetime="{{ author_created|date('c') }}" title="{{ author_created|date('r') }}">{{ author_created|time_format }}</time>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="forum__post__username">Deleted User</div>
|
||||
|
@ -432,47 +528,47 @@
|
|||
</div>
|
||||
|
||||
<div class="forum__post__content">
|
||||
{% set post_link = url(post.is_opening_post ? 'forum-topic' : 'forum-post', {'topic': post.topic_id, 'post': post.post_id, 'post_fragment': 'p%d'|format(post.post_id)}) %}
|
||||
{% set post_link = url(post_is_op ? 'forum-topic' : 'forum-post', {'topic': topic_id, 'post': post_id, 'post_fragment': 'p%d'|format(post_id)}) %}
|
||||
|
||||
<div class="forum__post__details">
|
||||
<a class="forum__post__datetime" href="{{ post_link }}">
|
||||
<time datetime="{{ post.post_created|date('c') }}" title="{{ post.post_created|date('r') }}">{{ post.post_created|time_format }}</time>
|
||||
{% if post.post_edited is not null %}
|
||||
(edited <time datetime="{{ post.post_edited|date('c') }}" title="{{ post.post_edited|date('r') }}">{{ post.post_edited|time_format }}</time>)
|
||||
<time datetime="{{ post_created|date('c') }}" title="{{ post_created|date('r') }}">{{ post_created|time_format }}</time>
|
||||
{% if post_edited is not null %}
|
||||
(edited <time datetime="{{ post_edited|date('c') }}" title="{{ post_edited|date('r') }}">{{ post_edited|time_format }}</time>)
|
||||
{% endif %}
|
||||
</a>
|
||||
|
||||
<a class="forum__post__id" href="{{ post_link }}">
|
||||
#{{ post.post_id }}
|
||||
#{{ post_id }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="forum__post__text{% if post.post_parse == constant('\\Misuzu\\Parsers\\Parser::MARKDOWN') %} markdown{% endif %}">
|
||||
{{ post.post_text|escape|parse_text(post.post_parse)|raw }}
|
||||
<div class="forum__post__text{% if post_is_markdown %} markdown{% endif %}">
|
||||
{{ post_body|raw }}
|
||||
</div>
|
||||
|
||||
{% if can_post or can_edit or can_delete %}
|
||||
{% if perms.can_create_post|default(false) or can_edit or can_delete %}
|
||||
<div class="forum__post__actions">
|
||||
{% if is_deleted %}
|
||||
<a href="{{ url('forum-post-restore', {'post': post.post_id}) }}" class="forum__post__action forum__post__action--restore"><i class="fas fa-magic fa-fw"></i> Restore</a>
|
||||
<a href="{{ url('forum-post-nuke', {'post': post.post_id}) }}" class="forum__post__action forum__post__action--nuke"><i class="fas fa-radiation-alt fa-fw"></i> Permanently Delete</a>
|
||||
{% if post_is_deleted %}
|
||||
<a href="{{ url('forum-post-restore', {'post': post_id}) }}" class="forum__post__action forum__post__action--restore"><i class="fas fa-magic fa-fw"></i> Restore</a>
|
||||
<a href="{{ url('forum-post-nuke', {'post': post_id}) }}" class="forum__post__action forum__post__action--nuke"><i class="fas fa-radiation-alt fa-fw"></i> Permanently Delete</a>
|
||||
{% else %}
|
||||
{# if can_post %}
|
||||
<a href="{{ url('forum-post-quote', {'post': post.post_id}) }}" class="forum__post__action forum__post__action--quote"><i class="fas fa-quote-left fa-fw"></i> Quote</a>
|
||||
{# if perms.can_create_post|default(false) %}
|
||||
<a href="{{ url('forum-post-quote', {'post': post_id}) }}" class="forum__post__action forum__post__action--quote"><i class="fas fa-quote-left fa-fw"></i> Quote</a>
|
||||
{% endif #}
|
||||
{% if can_edit %}
|
||||
<a href="{{ url('forum-post-edit', {'post': post.post_id}) }}" class="forum__post__action forum__post__action--edit"><i class="fas fa-edit fa-fw"></i> Edit</a>
|
||||
<a href="{{ url('forum-post-edit', {'post': post_id}) }}" class="forum__post__action forum__post__action--edit"><i class="fas fa-edit fa-fw"></i> Edit</a>
|
||||
{% endif %}
|
||||
{% if can_delete %}
|
||||
<a href="{{ url('forum-post-delete', {'post': post.post_id}) }}" class="forum__post__action forum__post__action--delete"><i class="far fa-trash-alt fa-fw"></i> Delete</a>
|
||||
<a href="{{ url('forum-post-delete', {'post': post_id}) }}" class="forum__post__action forum__post__action--delete"><i class="far fa-trash-alt fa-fw"></i> Delete</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if post.post_display_signature and post.poster_signature_content|length > 0 %}
|
||||
<div class="forum__post__signature{% if post.poster_signature_parser == constant('\\Misuzu\\Parsers\\Parser::MARKDOWN') %} markdown{% endif %}">
|
||||
{{ post.poster_signature_content|escape|parse_text(post.poster_signature_parser)|raw }}
|
||||
{% if post_show_signature and signature_body is defined and signature_body|length > 0 %}
|
||||
<div class="forum__post__signature{% if signature_is_markdown %} markdown{% endif %}">
|
||||
{{ signature_body|raw }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -5,32 +5,32 @@
|
|||
|
||||
{% 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.isOriginalPost|default(false) %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" action="{{ url('forum-' ~ (is_reply ? 'post' : 'topic') ~ '-create') }}" class="js-forum-posting">
|
||||
{{ input_hidden('post[' ~ (is_reply ? 'topic' : 'forum') ~ ']', is_reply ? posting_topic.topic_id : posting_forum.forum_id) }}
|
||||
{{ input_hidden('post[' ~ (is_reply ? 'topic' : 'forum') ~ ']', is_reply ? posting_topic.id : posting_forum.id) }}
|
||||
{{ input_hidden('post[mode]', posting_mode) }}
|
||||
{{ input_csrf() }}
|
||||
{{ forum_header(
|
||||
is_reply and not is_opening
|
||||
? posting_topic.topic_title
|
||||
? posting_topic.title
|
||||
: input_text(
|
||||
'post[title]',
|
||||
'forum__header__input',
|
||||
posting_defaults.title|default(posting_topic.topic_title|default('')),
|
||||
posting_defaults.title|default(posting_topic.title|default('')),
|
||||
'text',
|
||||
'Enter your title here...'
|
||||
),
|
||||
posting_breadcrumbs,
|
||||
false,
|
||||
is_reply and not is_opening
|
||||
? url('forum-topic', {'topic': posting_topic.topic_id})
|
||||
? url('forum-topic', {'topic': posting_topic.id})
|
||||
: ''
|
||||
) }}
|
||||
|
||||
{% if posting_post is defined %}
|
||||
{{ input_hidden('post[id]', posting_post.post_id) }}
|
||||
{{ input_hidden('post[id]', posting_post.info.id) }}
|
||||
{% endif %}
|
||||
|
||||
{% if posting_notices|length > 0 %}
|
||||
|
@ -43,21 +43,21 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="container forum__post" style="{{ posting_post.poster_colour|default(posting_info.colour)|html_colour('--accent-colour') }}">
|
||||
<div class="container forum__post" style="{{ posting_post.colour|default(posting_user_colour)|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(posting_info.user_id), 120, posting_post.poster_name|default(posting_info.username)) }}</span>
|
||||
<span class="forum__post__avatar">{{ avatar(posting_post.user.id|default(posting_user.id), 120, posting_post.user.name|default(posting_user.name)) }}</span>
|
||||
|
||||
<span class="forum__post__username">{{ posting_post.poster_name|default(posting_info.username) }}</span>
|
||||
<span class="forum__post__username">{{ posting_post.user.name|default(posting_user.name) }}</span>
|
||||
|
||||
<div class="forum__post__icons">
|
||||
<div class="flag flag--{{ posting_post.poster_country|default(posting_info.user_country)|lower }}" title="{{ posting_post.poster_country|default(posting_info.user_country)|country_name }}"></div>
|
||||
<div class="forum__post__posts-count">{{ posting_post.poster_post_count|default(posting_info.user_forum_posts)|number_format }} posts</div>
|
||||
<div class="flag flag--{{ posting_post.user.countryCode|default(posting_user.countryCode)|lower }}" title="{{ posting_post.user.countryCode|default(posting_user.countryCode)|country_name }}"></div>
|
||||
<div class="forum__post__posts-count">{{ posting_post.postsCount|default(posting_user_posts_count)|number_format }} posts</div>
|
||||
</div>
|
||||
|
||||
<div class="forum__post__joined">
|
||||
joined <time datetime="{{ posting_post.poster_joined|default(posting_info.user_created)|date('c') }}" title="{{ posting_post.poster_joined|default(posting_info.user_created)|date('r') }}">{{ posting_post.poster_joined|default(posting_info.user_created)|time_format }}</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_format }}</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,7 +75,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.info.body|default('')) }}</textarea>
|
||||
<div class="forum__post__text js-forum-posting-preview" hidden></div>
|
||||
|
||||
<div class="forum__post__actions forum__post__actions--bbcode" hidden>
|
||||
|
@ -146,24 +146,23 @@
|
|||
{{ input_select(
|
||||
'post[parser]',
|
||||
constant('\\Misuzu\\Parsers\\Parser::NAMES'),
|
||||
posting_defaults.parser|default(posting_post.post_parse|default(posting_info.user_post_parse|default(constant('\\Misuzu\\Parsers\\Parser::BBCODE')))),
|
||||
posting_defaults.parser|default(posting_post.info.parser|default(posting_user_preferred_parser)),
|
||||
null, null, false, 'forum__post__dropdown js-forum-posting-parser'
|
||||
) }}
|
||||
{% if is_opening and posting_types|length > 1 %}
|
||||
{{ input_select(
|
||||
'post[type]',
|
||||
posting_types,
|
||||
posting_defaults.type|default(posting_topic.topic_type|default(posting_types|keys|first)),
|
||||
null, null, null, 'forum__post__dropdown'
|
||||
) }}
|
||||
<select class="input__select forum__post__dropdown" name="post[type]">
|
||||
{% for type_name, type_title in posting_types %}
|
||||
<option value="{{ type_name }}"{% if type_name == posting_type_selected %} selected{% endif %}>{{ type_title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
{{ input_checkbox(
|
||||
'post[signature]',
|
||||
'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.info.shouldDisplaySignature is defined
|
||||
? posting_post.info.shouldDisplaySignature
|
||||
: true
|
||||
)
|
||||
) }}
|
||||
|
|
|
@ -11,50 +11,50 @@
|
|||
forum_topic_redirect
|
||||
%}
|
||||
|
||||
{% set title = topic_info.topic_title %}
|
||||
{% set title = topic_info.title %}
|
||||
{% set canonical_url = url('forum-topic', {
|
||||
'topic': topic_info.topic_id,
|
||||
'topic': topic_info.id,
|
||||
'page': topic_pagination.page > 1 ? topic_pagination.page : 0,
|
||||
}) %}
|
||||
|
||||
{% set forum_post_csrf = csrf_token() %}
|
||||
{% set topic_tools = forum_topic_tools(topic_info, topic_pagination, can_reply) %}
|
||||
{% set topic_notice = forum_topic_locked(topic_info.topic_locked, topic_info.topic_archived) ~ forum_topic_redirect(topic_redir_info|default(null)) %}
|
||||
{% set topic_notice = forum_topic_locked(topic_info.lockedTime, category_info.isArchived) ~ forum_topic_redirect(topic_redir_info|default(null)) %}
|
||||
{% set topic_actions = [
|
||||
{
|
||||
'html': '<i class="far fa-trash-alt fa-fw"></i> Delete',
|
||||
'url': url('forum-topic-delete', {'topic': topic_info.topic_id}),
|
||||
'url': url('forum-topic-delete', {'topic': topic_info.id}),
|
||||
'display': topic_can_delete,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-magic fa-fw"></i> Restore',
|
||||
'url': url('forum-topic-restore', {'topic': topic_info.topic_id}),
|
||||
'url': url('forum-topic-restore', {'topic': topic_info.id}),
|
||||
'display': topic_can_nuke_or_restore,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-radiation-alt fa-fw"></i> Permanently Delete',
|
||||
'url': url('forum-topic-nuke', {'topic': topic_info.topic_id}),
|
||||
'url': url('forum-topic-nuke', {'topic': topic_info.id}),
|
||||
'display': topic_can_nuke_or_restore,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-plus-circle fa-fw"></i> Bump',
|
||||
'url': url('forum-topic-bump', {'topic': topic_info.topic_id}),
|
||||
'url': url('forum-topic-bump', {'topic': topic_info.id}),
|
||||
'display': topic_can_bump,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-lock fa-fw"></i> Lock',
|
||||
'url': url('forum-topic-lock', {'topic': topic_info.topic_id}),
|
||||
'display': topic_can_lock and topic_info.topic_locked is null,
|
||||
'url': url('forum-topic-lock', {'topic': topic_info.id}),
|
||||
'display': topic_can_lock and not topic_info.isLocked,
|
||||
},
|
||||
{
|
||||
'html': '<i class="fas fa-lock-open fa-fw"></i> Unlock',
|
||||
'url': url('forum-topic-unlock', {'topic': topic_info.topic_id}),
|
||||
'display': topic_can_lock and topic_info.topic_locked is not null,
|
||||
'url': url('forum-topic-unlock', {'topic': topic_info.id}),
|
||||
'display': topic_can_lock and topic_info.isLocked,
|
||||
},
|
||||
] %}
|
||||
|
||||
{% block content %}
|
||||
{{ forum_header(topic_info.topic_title, topic_breadcrumbs, false, canonical_url, topic_actions) }}
|
||||
{{ forum_header(topic_info.title, topic_breadcrumbs, false, canonical_url, topic_actions) }}
|
||||
{{ topic_notice|raw }}
|
||||
{{ topic_tools }}
|
||||
{{ forum_post_listing(topic_posts, topic_user_id, topic_perms) }}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<a href="#topics" class="search__category">
|
||||
<div class="search__category__background"></div>
|
||||
<div class="search__category__content">
|
||||
Topics ({{ forum_topics|length|number_format }})
|
||||
Topics
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -33,7 +33,7 @@
|
|||
<a href="#posts" class="search__category">
|
||||
<div class="search__category__background"></div>
|
||||
<div class="search__category__content">
|
||||
Posts ({{ forum_posts|length|number_format }})
|
||||
Posts
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -42,7 +42,7 @@
|
|||
<a href="#users" class="search__category">
|
||||
<div class="search__category__background"></div>
|
||||
<div class="search__category__content">
|
||||
Users ({{ users|length|number_format }})
|
||||
Members
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -51,7 +51,7 @@
|
|||
<a href="#news" class="search__category">
|
||||
<div class="search__category__background"></div>
|
||||
<div class="search__category__content">
|
||||
News ({{ news_posts|length|number_format }})
|
||||
News
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -80,21 +80,30 @@
|
|||
{% if forum_topics|length > 0 %}
|
||||
<div class="search__anchor" id="topics"></div>
|
||||
{{ forum_topic_listing(forum_topics, 'Topics (%d)'|format(forum_topics|length)) }}
|
||||
{% if forum_topics|length >= 20 %}
|
||||
<div style="text-align: center; padding: 10px;">
|
||||
<a href="{{ url('search-query', {'section': 'topics', 'query': search_merge_query({'type': 'forum:topic', 'after': forum_topics|last.info.id})}) }}" class="input__button">Load next 20 topics...</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if forum_posts|length > 0 %}
|
||||
<div class="search__anchor" id="posts"></div>
|
||||
<div class="container search__container">
|
||||
{{ container_title('<i class="fas fa-comment fa-fw"></i> Posts (%s)'|format(forum_posts|length|number_format)) }}
|
||||
|
||||
{{ forum_post_listing(forum_posts) }}
|
||||
</div>
|
||||
{% if forum_posts|length >= 20 %}
|
||||
<div style="text-align: center; padding: 10px;">
|
||||
<a href="{{ url('search-query', {'section': 'posts', 'query': search_merge_query({'type': 'forum:post', 'after': forum_posts|last.info.id})}) }}" class="input__button">Load next 20 posts...</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if users|length > 0 %}
|
||||
<div class="search__anchor" id="users"></div>
|
||||
<div class="container search__container">
|
||||
{{ container_title('<i class="fas fa-users fa-fw"></i> Users (%s)'|format(users|length|number_format)) }}
|
||||
{{ container_title('<i class="fas fa-users fa-fw"></i> Members (%s)'|format(users|length|number_format)) }}
|
||||
|
||||
<div class="userlist userlist--search">
|
||||
{% for user in users %}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{% extends 'manage/users/master.twig' %}
|
||||
{% from 'macros.twig' import container_title %}
|
||||
{% from 'manage/macros.twig' import permissions_table %}
|
||||
{% from '_layout/input.twig' import input_hidden, input_csrf, input_select %}
|
||||
|
||||
{% block manage_content %}
|
||||
<div class="container">
|
||||
{{ container_title(forum.forum_name) }}
|
||||
there's nothing here go away
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -4,17 +4,7 @@
|
|||
|
||||
{% block manage_content %}
|
||||
<div class="container container--lazy">
|
||||
{{ container_title('Forum Listing') }}
|
||||
|
||||
<div class="container__content">
|
||||
{% for forum in forums %}
|
||||
<a href="{{ url('manage-forum-category', {'forum': forum.forum_id}) }}" class="warning__link">{{ forum.forum_name }}</a><br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container container--lazy">
|
||||
{{ container_title('Permission Calculator') }}
|
||||
{{ container_title('<i class="fas fa-calculator fa-fw"></i> Permission Calculator') }}
|
||||
|
||||
Remove this when the permission manager exists.
|
||||
|
||||
|
|
|
@ -51,23 +51,23 @@
|
|||
{% for redir in manage_redirs %}
|
||||
<tr class="manage-list-setting">
|
||||
<td class="manage-list-setting-key">
|
||||
<div class="manage-list-setting-key-text">{{ redir.topic_id }}</div>
|
||||
<div class="manage-list-setting-key-text">{{ redir.topicId }}</div>
|
||||
</td>
|
||||
<td class="manage-list-setting-key">
|
||||
<div class="manage-list-setting-key-text">{{ redir.user_id }}</div>
|
||||
<div class="manage-list-setting-key-text">{{ redir.hasUserId ? redir.userId : 'System' }}</div>
|
||||
</td>
|
||||
<td class="manage-list-setting-value">
|
||||
<div class="manage-list-setting-value-text">{{ redir.topic_redir_url }}</div>
|
||||
<div class="manage-list-setting-value-text">{{ redir.linkTarget }}</div>
|
||||
</td>
|
||||
<td class="manage-list-setting-value">
|
||||
<div class="manage-list-setting-value-text">
|
||||
<time datetime="{{ redir.topic_redir_created|date('c') }}" title="{{ redir.topic_redir_created|date('r') }}">
|
||||
{{ redir.topic_redir_created|time_format }}
|
||||
<time datetime="{{ redir.createdTime|date('c') }}" title="{{ redir.createdTime|date('r') }}">
|
||||
{{ redir.createdTime|time_format }}
|
||||
</time>
|
||||
</div>
|
||||
</td>
|
||||
<td class="manage-list-setting-options">
|
||||
<a class="input__button input__button--autosize input__button--destroy" href="{{ url('manage-forum-topic-redirs-nuke', {'topic': redir.topic_id}) }}" title="Delete"><i class="fas fa-times fa-fw"></i></a>
|
||||
<a class="input__button input__button--autosize input__button--destroy" href="{{ url('manage-forum-topic-redirs-nuke', {'topic': redir.topicId}) }}" title="Delete"><i class="fas fa-times fa-fw"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
{% set show_profile_fields = not profile_is_guest and (profile_is_editing ? perms.edit_profile : profile_fields_display_values|default([]) is not empty) %}
|
||||
{% set show_background_settings = profile_is_editing and perms.edit_background %}
|
||||
{% set show_birthdate = profile_is_editing and perms.edit_birthdate %}
|
||||
{% set show_active_forum_info = not profile_is_editing and (profile_active_category_info.forum_id|default(0) > 0 or profile_active_topic_info.topic_id|default(0) > 0) %}
|
||||
{% set show_active_forum_info = not profile_is_deleted and not profile_is_editing and (profile_active_category_info is not empty or profile_active_topic_info.topic_id|default(0) > 0) %}
|
||||
{% set show_warnings = profile_warnings is defined and profile_warnings|length > 0 %}
|
||||
{% set show_sidebar = (not profile_is_banned or profile_can_edit) and (profile_is_guest or show_profile_fields or show_background_settings or show_birthdate or show_active_forum_info or show_warnings) %}
|
||||
|
||||
|
@ -144,41 +144,26 @@
|
|||
<div class="profile__forum-activity__content">
|
||||
{% if profile_active_category_info is not empty %}
|
||||
<div class="profile__forum-activity__category">
|
||||
{% set forum = profile_active_category_info %}
|
||||
{% if forum.forum_icon is defined and forum.forum_icon is not empty %}
|
||||
{% set forum_icon = forum.forum_icon %}
|
||||
{% elseif forum.forum_archived is defined and forum.forum_archived %}
|
||||
{% set forum_icon = 'fas fa-archive fa-fw' %}
|
||||
{% elseif forum.forum_type is defined and forum.forum_type != constant('MSZ_FORUM_TYPE_DISCUSSION') %}
|
||||
{% if forum.forum_type == constant('MSZ_FORUM_TYPE_LINK') %}
|
||||
{% set forum_icon = 'fas fa-link fa-fw' %}
|
||||
{% elseif forum.forum_type == constant('MSZ_FORUM_TYPE_CATEGORY') %}
|
||||
{% set forum_icon = 'fas fa-folder fa-fw' %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set forum_icon = 'fas fa-comments fa-fw' %}
|
||||
{% endif %}
|
||||
|
||||
<div class="profile__forum-activity__leader">
|
||||
Most active category
|
||||
</div>
|
||||
|
||||
<div class="forum__category">
|
||||
<a href="{{ url('forum-category', {'forum': forum.forum_id}) }}" class="forum__category__link"></a>
|
||||
<a href="{{ url('forum-category', {'forum': profile_active_category_info.id}) }}" class="forum__category__link"></a>
|
||||
|
||||
<div class="forum__category__container">
|
||||
<div class="forum__category__icon">
|
||||
<span class="{{ forum_icon }}"></span>
|
||||
<span class="{{ profile_active_category_info.iconForDisplay }}"></span>
|
||||
</div>
|
||||
|
||||
<div class="forum__category__details">
|
||||
<div class="forum__category__title">
|
||||
{{ forum.forum_name }}
|
||||
{{ profile_active_category_info.name }}
|
||||
</div>
|
||||
|
||||
<div class="forum__category__description">
|
||||
{{ profile_active_category_stats.post_count|number_format }} post{{ profile_active_category_stats.post_count == 1 ? '' : 's' }}
|
||||
/ {{ ((profile_active_category_stats.post_count / profile_stats.forum_post_count) * 100)|number_format(2) }}% of total posts
|
||||
{{ profile_active_category_stats.postCount|number_format }} post{{ profile_active_category_stats.postCount == 1 ? '' : 's' }}
|
||||
/ {{ ((profile_active_category_stats.postCount / profile_stats.forum_post_count) * 100)|number_format(2) }}% of total posts
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -187,43 +172,28 @@
|
|||
{% endif %}
|
||||
{% if profile_active_topic_info is not empty %}
|
||||
<div class="profile__forum-activity__topic">
|
||||
{% set topic = profile_active_topic_info %}
|
||||
{% if topic.topic_deleted is defined and topic.topic_deleted is not null %}
|
||||
{% set topic_icon = 'fas fa-trash-alt' %}
|
||||
{% elseif topic.topic_type is defined and topic.topic_type != constant('MSZ_TOPIC_TYPE_DISCUSSION') %}
|
||||
{% if topic.topic_type == constant('MSZ_TOPIC_TYPE_ANNOUNCEMENT') or topic.topic_type == constant('MSZ_TOPIC_TYPE_GLOBAL_ANNOUNCEMENT') %}
|
||||
{% set topic_icon = 'fas fa-bullhorn' %}
|
||||
{% elseif topic.topic_type == constant('MSZ_TOPIC_TYPE_STICKY') %}
|
||||
{% set topic_icon = 'fas fa-thumbtack' %}
|
||||
{% endif %}
|
||||
{% elseif topic.topic_locked is defined and topic.topic_locked is not null %}
|
||||
{% set topic_icon = 'fas fa-lock' %}
|
||||
{% else %}
|
||||
{% set topic_icon = 'fas fa-comment' %}
|
||||
{% endif %}
|
||||
|
||||
<div class="profile__forum-activity__leader">
|
||||
Most active topic
|
||||
</div>
|
||||
|
||||
<div class="forum__topic{% if topic.topic_locked is not null %} forum__topic--locked{% endif %}">
|
||||
<a href="{{ url('forum-topic', {'topic': topic.topic_id}) }}" class="forum__topic__link"></a>
|
||||
<div class="forum__topic{% if profile_active_topic_info.isLocked %} forum__topic--locked{% endif %}">
|
||||
<a href="{{ url('forum-topic', {'topic': profile_active_topic_info.id}) }}" class="forum__topic__link"></a>
|
||||
|
||||
<div class="forum__topic__container">
|
||||
<div class="forum__topic__icon">
|
||||
<i class="{{ topic_icon }} fa-fw"></i>
|
||||
<i class="{{ profile_active_topic_info.iconForDisplay }} fa-fw"></i>
|
||||
</div>
|
||||
|
||||
<div class="forum__topic__details">
|
||||
<div class="forum__topic__title">
|
||||
<span class="forum__topic__title__inner">
|
||||
{{ topic.topic_title }}
|
||||
{{ profile_active_topic_info.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="forum__topic__info">
|
||||
{{ profile_active_topic_stats.post_count|number_format }} post{{ profile_active_topic_stats.post_count == 1 ? '' : 's' }}
|
||||
/ {{ ((profile_active_topic_stats.post_count / profile_stats.forum_post_count) * 100)|number_format(2) }}% of total posts
|
||||
{{ profile_active_topic_stats.postCount|number_format }} post{{ profile_active_topic_stats.postCount == 1 ? '' : 's' }}
|
||||
/ {{ ((profile_active_topic_stats.postCount / profile_stats.forum_post_count) * 100)|number_format(2) }}% of total posts
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
{% extends 'profile/master.twig' %}
|
||||
{% from 'macros.twig' import pagination %}
|
||||
{% from 'forum/macros.twig' import forum_post_listing %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile">
|
||||
{% include 'profile/_layout/header.twig' %}
|
||||
|
||||
{% set sp = profile_posts_pagination.pages > 1
|
||||
? '<div class="container profile__pagination">' ~ pagination(profile_posts_pagination, canonical_url) ~ '</div>'
|
||||
: '' %}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
|
||||
{{ forum_post_listing(profile_posts) }}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,23 +0,0 @@
|
|||
{% extends 'profile/master.twig' %}
|
||||
{% from 'macros.twig' import pagination %}
|
||||
{% from 'forum/macros.twig' import forum_topic_listing %}
|
||||
|
||||
{% block content %}
|
||||
<div class="profile">
|
||||
{% include 'profile/_layout/header.twig' %}
|
||||
|
||||
{% set sp = profile_topics_pagination.pages > 1
|
||||
? '<div class="container profile__pagination">' ~ pagination(profile_topics_pagination, canonical_url) ~ '</div>'
|
||||
: '' %}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
|
||||
{{ forum_topic_listing(profile_topics) }}
|
||||
|
||||
{% if sp is not empty %}
|
||||
{{ sp|raw }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -74,8 +74,9 @@ msz_sched_task_sql('Remove stale forum tracking entries.', false,
|
|||
msz_sched_task_sql('Synchronise forum_id.', true,
|
||||
'UPDATE msz_forum_posts AS p INNER JOIN msz_forum_topics AS t ON t.topic_id = p.topic_id SET p.forum_id = t.forum_id');
|
||||
|
||||
msz_sched_task_func('Recount forum topics and posts.', true,
|
||||
function() { forum_count_synchronise(); });
|
||||
msz_sched_task_func('Recount forum topics and posts.', true, function() use ($msz) {
|
||||
$msz->getForum()->syncForumCounters();
|
||||
});
|
||||
|
||||
msz_sched_task_sql('Clean up expired 2fa tokens.', false,
|
||||
'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE');
|
||||
|
|
Loading…
Reference in a new issue