diff --git a/assets/less/classes/forum/forum.less b/assets/less/classes/forum/forum.less new file mode 100644 index 00000000..7ee20beb --- /dev/null +++ b/assets/less/classes/forum/forum.less @@ -0,0 +1,10 @@ +@import "actions"; +@import "categories"; +@import "category"; +@import "confirm"; +@import "header"; +@import "post"; +@import "status"; +@import "topic"; +@import "topics"; +@import "leaderboard/leaderboard"; diff --git a/assets/less/classes/forum/leaderboard/categories.less b/assets/less/classes/forum/leaderboard/categories.less new file mode 100644 index 00000000..2de19ac2 --- /dev/null +++ b/assets/less/classes/forum/leaderboard/categories.less @@ -0,0 +1,8 @@ +.forum__leaderboard__categories { + display: block; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + margin: 2px 0; + scrollbar-width: thin; +} diff --git a/assets/less/classes/forum/leaderboard/category.less b/assets/less/classes/forum/leaderboard/category.less new file mode 100644 index 00000000..55a5c7c7 --- /dev/null +++ b/assets/less/classes/forum/leaderboard/category.less @@ -0,0 +1,19 @@ +.forum__leaderboard__category { + display: inline-block; + color: inherit; + text-decoration: none; + margin: 2px; + padding: 2px 5px; + border-radius: 4px; + transition: background-color .2s; + + &:hover, + &:focus { + background-color: fade(#fff, 20%); + } + + &--active, + &:active { + background-color: fade(#fff, 10%); + } +} diff --git a/assets/less/classes/forum/leaderboard/leaderboard.less b/assets/less/classes/forum/leaderboard/leaderboard.less new file mode 100644 index 00000000..8072ccd4 --- /dev/null +++ b/assets/less/classes/forum/leaderboard/leaderboard.less @@ -0,0 +1,4 @@ +@import "categories"; +@import "category"; +@import "markdown"; +@import "user"; diff --git a/assets/less/classes/forum/leaderboard/markdown.less b/assets/less/classes/forum/leaderboard/markdown.less new file mode 100644 index 00000000..518abbaf --- /dev/null +++ b/assets/less/classes/forum/leaderboard/markdown.less @@ -0,0 +1,8 @@ +.forum__leaderboard__markdown { + display: block; + width: 100%; + min-width: 100%; + max-width: 100%; + min-height: 500px; + margin: 2px auto; +} diff --git a/assets/less/classes/forum/leaderboard/user.less b/assets/less/classes/forum/leaderboard/user.less new file mode 100644 index 00000000..bd3efb0a --- /dev/null +++ b/assets/less/classes/forum/leaderboard/user.less @@ -0,0 +1,83 @@ +.forum__leaderboard__user { + margin: 2px 0; + font-size: 1.2em; + + &--rank-1 { + font-size: 1.6em; + } + + &--rank-2, + &--rank-3 { + font-size: 1.4em; + } + + &__background { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-decoration: none; + color: inherit; + } + + &__content { + display: flex; + pointer-events: none; + } + + &__rank { + height: 40px; + min-width: 50px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 700; + flex: 0 0 auto; + + &:before { + content: "#"; + } + } + + &--rank-1 &__rank { + height: 50px; + } + + &__avatar { + width: 40px; + height: 40px; + margin: 2px 7px; + flex: 0 0 auto; + } + + &--rank-1 &__avatar { + width: 50px; + height: 50px; + margin: 2px; + } + + &__username { + flex: 1 1 auto; + line-height: 30px; + padding: 5px; + margin: 2px; + } + + &--rank-1 &__username { + line-height: 40px; + } + + &__posts { + flex: 0 0 auto; + min-width: 150px; + border-left: 1px solid fade(#fff, 20%); + line-height: 30px; + padding: 5px; + margin: 2px; + } + + &--rank-1 &__posts { + line-height: 40px; + } +} diff --git a/assets/less/main.less b/assets/less/main.less index 140b5ea8..f911d55e 100644 --- a/assets/less/main.less +++ b/assets/less/main.less @@ -194,15 +194,7 @@ html { @import "classes/news/post"; // post needs to be able to override sidebar // Forums -@import "classes/forum/actions"; -@import "classes/forum/categories"; -@import "classes/forum/category"; -@import "classes/forum/confirm"; -@import "classes/forum/post"; -@import "classes/forum/topic"; -@import "classes/forum/topics"; -@import "classes/forum/status"; -@import "classes/forum/header"; +@import "classes/forum/forum"; // User stuff @import "classes/usercard"; diff --git a/misuzu.php b/misuzu.php index 2a4846b0..cba24187 100644 --- a/misuzu.php +++ b/misuzu.php @@ -50,6 +50,7 @@ require_once 'src/twitter.php'; require_once 'src/url.php'; require_once 'src/zalgo.php'; require_once 'src/Forum/forum.php'; +require_once 'src/Forum/leaderboard.php'; require_once 'src/Forum/perms.php'; require_once 'src/Forum/post.php'; require_once 'src/Forum/topic.php'; diff --git a/public/forum/leaderboard.php b/public/forum/leaderboard.php new file mode 100644 index 00000000..2de324fb --- /dev/null +++ b/public/forum/leaderboard.php @@ -0,0 +1,54 @@ + $user['user_id']]), $user['posts']); + } + + tpl_var('leaderboard_markdown', $markdown); +} + +echo tpl_render('forum.leaderboard', [ + 'leaderboard_id' => $leaderboardId, + 'leaderboard_name' => $leaderboardName, + 'leaderboard_categories' => $leaderboards, + 'leaderboard_data' => $leaderboard, + 'leaderboard_mode' => $leaderboardMode, +]); diff --git a/src/Forum/forum.php b/src/Forum/forum.php index 14a4c74c..8d8755af 100644 --- a/src/Forum/forum.php +++ b/src/Forum/forum.php @@ -3,6 +3,7 @@ * GLOBAL PERMISSIONS * **********************/ define('MSZ_PERM_FORUM_MANAGE_FORUMS', 1); +define('MSZ_PERM_FORUM_VIEW_LEADERBOARD', 2); /************************* * PER-FORUM PERMISSIONS * diff --git a/src/Forum/leaderboard.php b/src/Forum/leaderboard.php new file mode 100644 index 00000000..f6a6d0f8 --- /dev/null +++ b/src/Forum/leaderboard.php @@ -0,0 +1,93 @@ += 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 = MSZ_FORUM_LEADERBOARD_START_YEAR; $i <= $currentYear; $i++) { + $categories[$i] = sprintf('Leaderboard %d', $i); + } + + for ($i = MSZ_FORUM_LEADERBOARD_START_YEAR, $j = MSZ_FORUM_LEADERBOARD_START_MONTH;;) { + $categories[sprintf('%d%02d', $i, $j)] = sprintf('Leaderboard %d-%02d', $i, $j); + + if ($j >= 12) { + $i++; $j = 1; + } else $j++; + + if ($i >= $currentYear && $j > $currentMonth) + break; + } + + return $categories; +} + +function forum_leaderboard_listing(?int $year = null, ?int $month = null): array +{ + $hasYear = forum_leaderboard_year_valid($year); + $hasMonth = $hasYear && forum_leaderboard_month_valid($year, $month); + + $rawLeaderboard = db_fetch_all(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 + GROUP BY u.`user_id` + HAVING `posts` > 0 + ORDER BY `posts` DESC + ', + !$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 + ) + ))); + + $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; +} diff --git a/src/manage.php b/src/manage.php index 8a59c3f9..8521e659 100644 --- a/src/manage.php +++ b/src/manage.php @@ -282,6 +282,11 @@ function manage_perms_list(array $rawPerms): array 'title' => 'Can manage forum sections.', 'perm' => MSZ_PERM_FORUM_MANAGE_FORUMS, ], + [ + 'section' => 'view-leaderboard', + 'title' => 'Can view the forum leaderboard live.', + 'perm' => MSZ_PERM_FORUM_VIEW_LEADERBOARD, + ], ], ], [ diff --git a/src/perms.php b/src/perms.php index e9209fac..4917600c 100644 --- a/src/perms.php +++ b/src/perms.php @@ -202,7 +202,7 @@ function perms_check(int $perms, int $perm): bool return ($perms & $perm) > 0; } -function perms_check_user(string $prefix, int $userId, int $perm): bool +function perms_check_user(string $prefix, ?int $userId, int $perm): bool { - return perms_check(perms_get_user($prefix, $userId), $perm); + return $userId > 0 && perms_check(perms_get_user($prefix, $userId), $perm); } diff --git a/src/url.php b/src/url.php index b3b994af..8b16c1bb 100644 --- a/src/url.php +++ b/src/url.php @@ -32,6 +32,7 @@ define('MSZ_URLS', [ 'news-category' => ['/news.php', ['c' => '', 'page' => '']], 'forum-index' => ['/forum'], + 'forum-leaderboard' => ['/forum/leaderboard.php', ['id' => '', 'mode' => '']], 'forum-mark-global' => ['/forum/index.php', ['m' => 'mark', 'c' => '{forum_mark}']], 'forum-mark-single' => ['/forum/index.php', ['m' => 'mark', 'c' => '{forum_mark}', 'f' => '']], 'forum-topic-new' => ['/forum/posting.php', ['f' => '']], @@ -224,3 +225,23 @@ function url_proxy_media(?string $url): ?string return url('media-proxy', compact('hash', 'url')); } + +function url_prefix(bool $trailingSlash = true): string +{ + return 'http' . (empty($_SERVER['HTTPS']) ? '' : 's') . '://' . $_SERVER['HTTP_HOST'] . ($trailingSlash ? '/' : ''); +} + +function is_local_url(string $url): bool +{ + $length = mb_strlen($url); + + if ($length < 1) { + return false; + } + + if ($url[0] === '/' && ($length > 1 ? $url[1] !== '/' : true)) { + return true; + } + + return starts_with($url, url_prefix()); +} diff --git a/templates/forum/leaderboard.twig b/templates/forum/leaderboard.twig new file mode 100644 index 00000000..67932405 --- /dev/null +++ b/templates/forum/leaderboard.twig @@ -0,0 +1,45 @@ +{% extends 'forum/master.twig' %} +{% from 'forum/macros.twig' import forum_header %} + +{% set title = 'Forum Leaderboard ยป ' ~ leaderboard_name %} +{% set canonical_url = url('forum-leaderboard', { + 'id': leaderboard_id, + 'mode': '', +}) %} + +{% block content %} + {{ forum_header(title, [], false, canonical_url, [ + { + 'html': ' Markdown', + 'url': url('forum-leaderboard', {'id': leaderboard_id, 'mode': 'markdown'}), + 'display': leaderboard_mode != 'markdown', + }, + { + 'html': ' Table', + 'url': url('forum-leaderboard', {'id': leaderboard_id}), + 'display': leaderboard_mode == 'markdown', + }, + ]) }} + +
+ {% for id, name in leaderboard_categories %} + {{ name }} + {% endfor %} +
+ + {% if leaderboard_mode == 'markdown' %} + + {% else %} + {% for user in leaderboard_data %} +
+ +
+
{{ user.rank|number_format }}
+
+
{{ user.username }}
+
{{ user.posts|number_format }} posts
+
+
+ {% endfor %} + {% endif %} +{% endblock %} diff --git a/utility.php b/utility.php index 02bba0d4..21d4ee88 100644 --- a/utility.php +++ b/utility.php @@ -64,22 +64,6 @@ function pdo_prepare_array(array $keys, bool $useKeys = false, string $format = return implode(', ', $parts); } -function is_local_url(string $url): bool -{ - $length = mb_strlen($url); - - if ($length < 1) { - return false; - } - - if ($url[0] === '/' && ($length > 1 ? $url[1] !== '/' : true)) { - return true; - } - - $prefix = 'http' . (empty($_SERVER['HTTPS']) ? '' : 's') . '://' . $_SERVER['HTTP_HOST'] . '/'; - return starts_with($url, $prefix); -} - function render_error(int $code, string $template = 'errors.%d'): string { return render_info(null, $code, $template);