misuzu/src/Forum/forum.php
flash 383e2ed0e0 Rewrote the user information class.
This one took multiple days and it pretty invasive into the core of Misuzu so issue might (will) arise, there's also some features that have gone temporarily missing in the mean time and some inefficiencies introduced that will be fixed again at a later time.
The old class isn't gone entirely because I still have to figure out what I'm gonna do about validation, but for the most part this knocks out one of the "layers of backwards compatibility", as I've been referring to it, and is moving us closer to a future where Flashii actually gets real updates.
If you run into anything that's broken and you're inhibited from reporting it through the forum, do it through chat or mail me at flashii-issues@flash.moe.
2023-08-02 22:12:47 +00:00

587 lines
18 KiB
PHP

<?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;
}