diff --git a/assets/misuzu.css/manage/statistic.css b/assets/misuzu.css/manage/statistic.css index 2825a21..210e1de 100644 --- a/assets/misuzu.css/manage/statistic.css +++ b/assets/misuzu.css/manage/statistic.css @@ -9,6 +9,12 @@ } .manage__statistic__value { text-align: right; - font-size: 1.5em; - line-height: 2em; + font-size: 1.4em; + line-height: 1.5em; +} +.manage__statistic__updated { + text-align: right; + font-size: .9em; + font-style: italic; + line-height: 1.5em; } diff --git a/assets/misuzu.css/manage/statistics.css b/assets/misuzu.css/manage/statistics.css index 1fe2777..7bc9264 100644 --- a/assets/misuzu.css/manage/statistics.css +++ b/assets/misuzu.css/manage/statistics.css @@ -1,9 +1,14 @@ .manage__statistics { display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr 1fr 1fr 1fr; padding: 5px; grid-gap: 5px; } +@media (max-width: 1100px) { + .manage__statistics { + grid-template-columns: 1fr 1fr 1fr; + } +} @media (max-width: 900px) { .manage__statistics { grid-template-columns: 1fr 1fr; diff --git a/database/2023_07_28_212101_create_counters_table.php b/database/2023_07_28_212101_create_counters_table.php new file mode 100644 index 0000000..2b436f1 --- /dev/null +++ b/database/2023_07_28_212101_create_counters_table.php @@ -0,0 +1,16 @@ +execute(' + CREATE TABLE msz_counters ( + counter_name VARBINARY(64) NOT NULL, + counter_value BIGINT(20) NOT NULL DEFAULT "0", + counter_updated TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + PRIMARY KEY (counter_name) + ) ENGINE=InnoDB COLLATE=utf8mb4_bin + '); + } +} diff --git a/public-legacy/manage/general/index.php b/public-legacy/manage/general/index.php index 05e3a10..5c3139d 100644 --- a/public-legacy/manage/general/index.php +++ b/public-legacy/manage/general/index.php @@ -1,183 +1,15 @@ 0 - ) AS `stat_comment_likes`, - ( - SELECT COUNT(*) - FROM `msz_comments_votes` - WHERE `comment_vote` < 0 - ) AS `stat_comment_dislikes`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - ) AS `stat_forum_posts_total`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - WHERE `post_deleted` IS NOT NULL - ) AS `stat_forum_posts_deleted`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - WHERE `post_edited` IS NOT NULL - ) AS `stat_forum_posts_edited`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - WHERE `post_parse` = 0 - ) AS `stat_forum_posts_plain`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - WHERE `post_parse` = 1 - ) AS `stat_forum_posts_bbcode`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - WHERE `post_parse` = 2 - ) AS `stat_forum_posts_markdown`, - ( - SELECT COUNT(*) - FROM `msz_forum_posts` - WHERE `post_display_signature` != 0 - ) AS `stat_forum_posts_signature`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - ) AS `stat_forum_topics_total`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - WHERE `topic_type` = 0 - ) AS `stat_forum_topics_normal`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - WHERE `topic_type` = 1 - ) AS `stat_forum_topics_pinned`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - WHERE `topic_type` = 2 - ) AS `stat_forum_topics_announce`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - WHERE `topic_type` = 3 - ) AS `stat_forum_topics_global_announce`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - WHERE `topic_deleted` IS NOT NULL - ) AS `stat_forum_topics_deleted`, - ( - SELECT COUNT(*) - FROM `msz_forum_topics` - WHERE `topic_locked` IS NOT NULL - ) AS `stat_forum_topics_locked`, - ( - SELECT COUNT(*) - FROM `msz_login_attempts` - ) AS `stat_login_attempts_total`, - ( - SELECT COUNT(*) - FROM `msz_login_attempts` - WHERE `attempt_success` = 0 - ) AS `stat_login_attempts_failed`, - ( - SELECT COUNT(*) - FROM `msz_sessions` - ) AS `stat_user_sessions`, - ( - SELECT COUNT(*) - FROM `msz_users_password_resets` - ) AS `stat_user_password_resets`, - ( - SELECT COUNT(*) - FROM `msz_users_modnotes` - ) AS `stat_user_modnotes`, - ( - SELECT COUNT(*) - FROM `msz_users_warnings` - ) AS `stat_user_warnings_total`, - ( - SELECT COUNT(*) - FROM `msz_users_warnings` - WHERE warn_created > NOW() - INTERVAL 90 DAY - ) AS `stat_user_warnings_visible`, - ( - SELECT COUNT(*) - FROM `msz_users_bans` - ) AS `stat_user_bans_total`, - ( - SELECT COUNT(*) - FROM `msz_users_bans` - WHERE ban_expires IS NULL OR ban_expires > NOW() - ) AS `stat_user_bans_active` -')->fetch(); +$counterInfos = $msz->getCounters()->getCounters(orderBy: 'name'); +$counterNamesRaw = $msz->getConfig()->getArray('counters.names'); +$counterNamesCount = count($counterNamesRaw); +$counterNames = []; + +for($i = -1; $i < $counterNamesCount;) + $counterNames[$counterNamesRaw[++$i] ?? ''] = $counterNamesRaw[++$i] ?? ''; Template::render('manage.general.overview', [ - 'statistics' => $statistics, + 'counter_infos' => $counterInfos, + 'counter_names' => $counterNames, ]); diff --git a/src/Counters/CounterInfo.php b/src/Counters/CounterInfo.php new file mode 100644 index 0000000..476193d --- /dev/null +++ b/src/Counters/CounterInfo.php @@ -0,0 +1,33 @@ +name = $result->getString(0); + $this->value = $result->getInteger(1); + $this->updated = $result->getInteger(2); + } + + public function getName(): string { + return $this->name; + } + + public function getValue(): int { + return $this->value; + } + + public function getUpdatedTime(): int { + return $this->updated; + } + + public function getUpdatedAt(): DateTime { + return DateTime::fromUnixTimeSeconds($this->updated); + } +} diff --git a/src/Counters/Counters.php b/src/Counters/Counters.php new file mode 100644 index 0000000..2f5cb29 --- /dev/null +++ b/src/Counters/Counters.php @@ -0,0 +1,139 @@ +cache = new DbStatementCache($dbConn); + } + + private const GET_COUNTERS_SORT = [ + 'name' => 'counter_name', + 'value' => 'counter_value', + 'updated' => 'counter_updated', + ]; + + public function getCounters( + ?string $orderBy = null, + ?Pagination $pagination = null + ): array { + $hasOrderBy = $orderBy !== null; + $hasPagination = $pagination !== null; + + $query = 'SELECT counter_name, counter_value, UNIX_TIMESTAMP(counter_updated) FROM msz_counters'; + if($hasOrderBy) { + if(!array_key_exists($orderBy, self::GET_COUNTERS_SORT)) + throw new InvalidArgumentException('Invalid sort specified.'); + + $query .= ' ORDER BY ' . self::GET_COUNTERS_SORT[$orderBy]; + } + if($hasPagination) + $query .= ' LIMIT ? OFFSET ?'; + + $args = 0; + $stmt = $this->cache->get($query); + if($hasPagination) { + $stmt->addParameter(++$args, $pagination->getRange()); + $stmt->addParameter(++$args, $pagination->getOffset()); + } + $stmt->execute(); + + $result = $stmt->getResult(); + $counters = []; + + while($result->next()) + $counters[] = new CounterInfo($result); + + return $counters; + } + + public function get(array|string $names): array|int { + if(is_string($names)) { + $returnFirst = true; + $names = [$names]; + } else $returnFirst = false; + + $args = 0; + $stmt = $this->cache->get(sprintf( + 'SELECT counter_name, counter_value FROM msz_counters WHERE counter_name IN (%s)', + DbTools::prepareListString($names) + )); + foreach($names as $name) + $stmt->addParameter(++$args, (string)$name); + $stmt->execute(); + + $values = []; + $result = $stmt->getResult(); + + while($result->next()) + $values[$result->getString(0)] = $result->getInteger(1); + + return $returnFirst ? $values[array_key_first($values)] : $values; + } + + public function set(string|array $nameOrValues, ?int $value = null): void { + if(empty($nameOrValues)) + throw new InvalidArgumentException('$nameOrValues may not be empty.'); + + if(is_string($nameOrValues)) { + if($value === null) + throw new InvalidArgumentException('$value may not be null.'); + $values = [$nameOrValues => $value]; + } else $values = $nameOrValues; + + + $args = 0; + $stmt = $this->cache->get(sprintf( + 'REPLACE INTO msz_counters (counter_name, counter_value) VALUES %s', + DbTools::prepareListString($values, '(?, ?)') + )); + + foreach($values as $name => $value) { + $stmt->addParameter(++$args, (string)$name); + $stmt->addParameter(++$args, (int)$value); + } + + $stmt->execute(); + } + + public function reset(string|array $names): void { + if(empty($names)) + throw new InvalidArgumentException('$names may not be empty.'); + if(is_string($names)) + $names = [$names]; + + $args = 0; + $stmt = $this->cache->get(sprintf( + 'DELETE FROM msz_counters WHERE counter_name IN (%s)', + DbTools::prepareListString($names) + )); + foreach($names as $name) + $stmt->addParameter(++$args, (string)$name); + $stmt->execute(); + } + + public function increment(string $name, int $step = 1): void { + $stmt = $this->cache->get('INSERT INTO msz_counters (counter_name, counter_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE counter_value = counter_value + ?'); + $stmt->addParameter(1, $name); + $stmt->addParameter(2, $step); + $stmt->addParameter(3, $step); + $stmt->execute(); + } + + public function decrement(string $name, int $step = 1): void { + $stmt = $this->cache->get('INSERT INTO msz_counters (counter_name, counter_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE counter_value = counter_value - ?'); + $stmt->addParameter(1, $name); + $stmt->addParameter(2, -$step); + $stmt->addParameter(3, $step); + $stmt->execute(); + } +} diff --git a/src/Http/Handlers/HomeHandler.php b/src/Http/Handlers/HomeHandler.php index 753641f..1de3d56 100644 --- a/src/Http/Handlers/HomeHandler.php +++ b/src/Http/Handlers/HomeHandler.php @@ -9,6 +9,15 @@ use Misuzu\Comments\CommentsCategory; use Misuzu\Users\User; final class HomeHandler extends Handler { + private const STATS = [ + 'users:active', + 'users:online:recent', + 'users:online:today', + 'comments:posts:visible', + 'forum:topics:visible', + 'forum:posts:visible', + ]; + public function index($response, $request): void { if(User::hasCurrent()) $this->home($response, $request); @@ -18,6 +27,7 @@ final class HomeHandler extends Handler { public function landing($response, $request): void { $config = $this->context->getConfig(); + $counters = $this->context->getCounters(); if($config->getBoolean('social.embed_linked')) { $ldr = $config->getValues([ @@ -39,16 +49,7 @@ final class HomeHandler extends Handler { pagination: new Pagination(3) ); - $stats = DB::query( - 'SELECT' - . ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_deleted` IS NULL) AS `count_users_all`,' - . ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) AS `count_users_online`,' - . ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 24 HOUR)) AS `count_users_active`,' - . ' (SELECT COUNT(`comment_id`) FROM `msz_comments_posts` WHERE `comment_deleted` IS NULL) AS `count_comments`,' - . ' (SELECT COUNT(`topic_id`) FROM `msz_forum_topics` WHERE `topic_deleted` IS NULL) AS `count_forum_topics`,' - . ' (SELECT COUNT(`post_id`) FROM `msz_forum_posts` WHERE `post_deleted` IS NULL) AS `count_forum_posts`' - )->fetch(); - + $stats = $counters->get(self::STATS); $onlineUsers = DB::query( 'SELECT u.`user_id`, u.`username`, COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`' . ' FROM `msz_users` AS u' @@ -56,7 +57,7 @@ final class HomeHandler extends Handler { . ' ON r.`role_id` = u.`display_role`' . ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)' . ' ORDER BY u.`user_active` DESC, RAND()' - . ' LIMIT 100' + . ' LIMIT 50' )->fetchAll(); // TODO: don't hardcode forum ids @@ -113,6 +114,7 @@ final class HomeHandler extends Handler { public function home($response, $request): void { $news = $this->context->getNews(); $comments = $this->context->getComments(); + $counters = $this->context->getCounters(); $featuredNews = []; $userInfos = []; $categoryInfos = []; @@ -153,16 +155,7 @@ final class HomeHandler extends Handler { ]; } - $stats = DB::query( - 'SELECT' - . ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_deleted` IS NULL) AS `count_users_all`,' - . ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)) AS `count_users_online`,' - . ' (SELECT COUNT(`user_id`) FROM `msz_users` WHERE `user_active` >= DATE_SUB(NOW(), INTERVAL 24 HOUR)) AS `count_users_active`,' - . ' (SELECT COUNT(`comment_id`) FROM `msz_comments_posts` WHERE `comment_deleted` IS NULL) AS `count_comments`,' - . ' (SELECT COUNT(`topic_id`) FROM `msz_forum_topics` WHERE `topic_deleted` IS NULL) AS `count_forum_topics`,' - . ' (SELECT COUNT(`post_id`) FROM `msz_forum_posts` WHERE `post_deleted` IS NULL) AS `count_forum_posts`' - )->fetch(); - + $stats = $counters->get(self::STATS); $changelog = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10)); $birthdays = User::byBirthdate(); @@ -175,9 +168,11 @@ final class HomeHandler extends Handler { . ' ON r.`role_id` = u.`display_role`' . ' WHERE u.`user_active` >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)' . ' ORDER BY u.`user_active` DESC, RAND()' - . ' LIMIT 104' )->fetchAll(); + // today we cheat + $stats['users:online:recent'] = count($onlineUsers); + $response->setContent(Template::renderRaw('home.home', [ 'statistics' => $stats, 'latest_user' => $latestUser, diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 404779e..5c40772 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -10,6 +10,7 @@ use Misuzu\AuditLog\AuditLog; use Misuzu\Changelog\Changelog; use Misuzu\Comments\Comments; use Misuzu\Config\IConfig; +use Misuzu\Counters\Counters; use Misuzu\Emoticons\Emotes; use Misuzu\News\News; use Misuzu\SharpChat\SharpChatRoutes; @@ -52,6 +53,7 @@ class MisuzuContext { private Roles $roles; private Users $users; private Sessions $sessions; + private Counters $counters; public function __construct(IDbConnection $dbConn, IConfig $config) { $this->dbConn = $dbConn; @@ -70,6 +72,7 @@ class MisuzuContext { $this->roles = new Roles($this->dbConn); $this->users = new Users($this->dbConn); $this->sessions = new Sessions($this->dbConn); + $this->counters = new Counters($this->dbConn); } public function getDbConn(): IDbConnection { @@ -153,6 +156,10 @@ class MisuzuContext { return $this->sessions; } + public function getCounters(): Counters { + return $this->counters; + } + private array $activeBansCache = []; public function tryGetActiveBan(User|string|null $userInfo = null): ?BanInfo { diff --git a/templates/home/home.twig b/templates/home/home.twig index 43ea30f..fa8ad67 100644 --- a/templates/home/home.twig +++ b/templates/home/home.twig @@ -9,32 +9,32 @@ { icon: 'fas fa-users fa-fw', name: 'Members', - value: statistics.count_users_all|number_format, + value: statistics['users:active']|number_format, }, { icon: 'fas fa-comment-dots fa-fw', name: 'Comments', - value: statistics.count_comments|number_format, + value: statistics['comments:posts:visible']|number_format, }, { icon: 'fas fa-user-check fa-fw', name: 'Online', - value: statistics.count_users_online|number_format, + value: statistics['users:online:recent']|number_format, }, { icon: 'fas fa-user-clock fa-fw', name: 'Active (24 hr)', - value: statistics.count_users_active|number_format, + value: statistics['users:online:today']|number_format, }, { icon: 'fas fa-list fa-fw', name: 'Topics', - value: statistics.count_forum_topics|number_format, + value: statistics['forum:topics:visible']|number_format, }, { icon: 'fas fa-comments fa-fw', name: 'Posts', - value: statistics.count_forum_posts|number_format, + value: statistics['forum:posts:visible']|number_format, }, ] %} diff --git a/templates/home/landing.twig b/templates/home/landing.twig index da64ffb..002cade 100644 --- a/templates/home/landing.twig +++ b/templates/home/landing.twig @@ -7,32 +7,32 @@ { icon: 'fas fa-users fa-fw', name: 'members', - value: statistics.count_users_all|number_format, + value: statistics['users:active']|number_format, }, { icon: 'fas fa-user-check fa-fw', name: 'online', - value: statistics.count_users_online|number_format, + value: statistics['users:online:recent']|number_format, }, { icon: 'fas fa-user-clock fa-fw', name: 'active (24 hr)', - value: statistics.count_users_active|number_format, + value: statistics['users:online:today']|number_format, }, { icon: 'fas fa-list fa-fw', name: 'topics', - value: statistics.count_forum_topics|number_format, + value: statistics['forum:topics:visible']|number_format, }, { icon: 'fas fa-comments fa-fw', name: 'posts', - value: statistics.count_forum_posts|number_format, + value: statistics['forum:posts:visible']|number_format, }, { icon: 'fas fa-comment-dots fa-fw', name: 'comments', - value: statistics.count_comments|number_format, + value: statistics['comments:posts:visible']|number_format, }, ] %} diff --git a/templates/manage/general/overview.twig b/templates/manage/general/overview.twig index 33ab1bc..2083f37 100644 --- a/templates/manage/general/overview.twig +++ b/templates/manage/general/overview.twig @@ -1,46 +1,6 @@ {% extends 'manage/general/master.twig' %} {% from 'macros.twig' import container_title %} -{% set stat_names = { - 'stat_users_total': 'Total Users', - 'stat_users_deleted': 'Deleted Users', - 'stat_users_active': 'Active Users', - 'stat_audit_logs': 'Logged Actions', - 'stat_changelog_entries': 'Changelogs', - 'stat_comment_categories_total': 'Comment Sections', - 'stat_comment_categories_locked': 'Locked Comment Sections', - 'stat_comment_posts_total': 'Total Comments', - 'stat_comment_posts_deleted': 'Deleted Comments', - 'stat_comment_posts_replies': 'Comment Replies', - 'stat_comment_posts_pinned': 'Pinned Comments', - 'stat_comment_posts_edited': 'Edited Comments', - 'stat_comment_likes': 'Comments Like Votes', - 'stat_comment_dislikes': 'Comments Dislike Votes', - 'stat_forum_posts_total': 'Total Forum Posts', - 'stat_forum_posts_deleted': 'Deleted Forum Posts', - 'stat_forum_posts_edited': 'Edited Forum Posts', - 'stat_forum_posts_plain': 'Forum Posts using Plain Text', - 'stat_forum_posts_bbcode': 'Forum Posts using BBCode', - 'stat_forum_posts_markdown': 'Forum Posts using Markdown', - 'stat_forum_posts_signature': 'Forum Posts with Visible Signature', - 'stat_forum_topics_total': 'Total Forum Topics', - 'stat_forum_topics_normal': 'Regular Forum Topics', - 'stat_forum_topics_pinned': 'Pinned Forum Topics', - 'stat_forum_topics_announce': 'Announcement Forum Topics', - 'stat_forum_topics_global_announce': 'Global Announcement Forum Topics', - 'stat_forum_topics_deleted': 'Deleted Forum Topics', - 'stat_forum_topics_locked': 'Locked Forum Topics', - 'stat_login_attempts_total': 'Total Login Attempts', - 'stat_login_attempts_failed': 'Failed Login Attempts', - 'stat_user_sessions': 'Active User Sessions', - 'stat_user_password_resets': 'Pending Password Resets', - 'stat_user_modnotes': 'Moderator Notes', - 'stat_user_warnings_total': 'Total User Warnings', - 'stat_user_warnings_visible': 'Visible User Warnings', - 'stat_user_bans_total': 'Total User Bans', - 'stat_user_bans_active': 'Active User Bans', -} %} - {% block manage_content %}
{{ container_title('Overview') }} @@ -54,10 +14,11 @@ {{ container_title('Statistics') }}
- {% for name, value in statistics %} -
-
{{ stat_names[name]|default(name) }}
-
{{ value|number_format }}
+ {% for counter in counter_infos %} +
+
{{ counter_names[counter.name]|default(counter.name) }}
+
{{ counter.value|number_format }}
+
as of
{% endfor %}
diff --git a/tools/cron b/tools/cron index 03451df..cd0fca3 100755 --- a/tools/cron +++ b/tools/cron @@ -80,6 +80,64 @@ msz_sched_task_func('Recount forum topics and posts.', true, msz_sched_task_sql('Clean up expired 2fa tokens.', false, 'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE'); +// make sure this one remains last +msz_sched_task_func('Resync statistics counters.', true, function() use ($msz) { + $dbConn = $msz->getDbConn(); + $counters = $msz->getCounters(); + + $stats = [ + 'users:total' => 'SELECT COUNT(*) FROM msz_users', + 'users:active' => 'SELECT COUNT(*) FROM msz_users WHERE user_active IS NOT NULL AND user_deleted IS NULL', + 'users:inactive' => 'SELECT COUNT(*) FROM msz_users WHERE user_active IS NULL OR user_deleted IS NOT NULL', + 'users:online:recent' => 'SELECT COUNT(*) FROM msz_users WHERE user_active >= NOW() - INTERVAL 1 HOUR', // used to be 5 minutes, but this script runs every hour soooo + 'users:online:today' => 'SELECT COUNT(*) FROM msz_users WHERE user_active >= NOW() - INTERVAL 24 HOUR', + 'auditlogs:total' => 'SELECT COUNT(*) FROM msz_audit_log', + 'changelog:changes:total' => 'SELECT COUNT(*) FROM msz_changelog_changes', + 'changelog:tags:total' => 'SELECT COUNT(*) FROM msz_changelog_tags', + 'comments:cats:total' => 'SELECT COUNT(*) FROM msz_comments_categories', + 'comments:cats:locked' => 'SELECT COUNT(*) FROM msz_comments_categories WHERE category_locked IS NOT NULL', + 'comments:posts:total' => 'SELECT COUNT(*) FROM msz_comments_posts', + 'comments:posts:visible' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_deleted IS NULL', + 'comments:posts:deleted' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_deleted IS NOT NULL', + 'comments:posts:replies' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_reply_to IS NOT NULL', + 'comments:posts:pinned' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_pinned IS NOT NULL', + 'comments:posts:edited' => 'SELECT COUNT(*) FROM msz_comments_posts WHERE comment_edited IS NOT NULL', + 'comments:votes:likes' => 'SELECT COUNT(*) FROM msz_comments_votes WHERE comment_vote > 0', + 'comments:votes:dislikes' => 'SELECT COUNT(*) FROM msz_comments_votes WHERE comment_vote < 0', + 'forum:posts:total' => 'SELECT COUNT(*) FROM msz_forum_posts', + 'forum:posts:visible' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_deleted IS NULL', + 'forum:posts:deleted' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_deleted IS NOT NULL', + 'forum:posts:edited' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_edited IS NOT NULL', + 'forum:posts:parse:plain' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_parse = 0', + 'forum:posts:parse:bbcode' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_parse = 1', + 'forum:posts:parse:markdown' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_parse = 2', + 'forum:posts:signature' => 'SELECT COUNT(*) FROM msz_forum_posts WHERE post_display_signature <> 0', + 'forum:topics:total' => 'SELECT COUNT(*) FROM msz_forum_topics', + 'forum:topics:type:normal' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 0', + 'forum:topics:type:pinned' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 1', + 'forum:topics:type:announce' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 2', + 'forum:topics:type:global' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_type = 3', + 'forum:topics:visible' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_deleted IS NULL', + 'forum:topics:deleted' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_deleted IS NOT NULL', + 'forum:topics:locked' => 'SELECT COUNT(*) FROM msz_forum_topics WHERE topic_locked IS NOT NULL', + 'auth:attempts:total' => 'SELECT COUNT(*) FROM msz_login_attempts', + 'auth:attempts:failed' => 'SELECT COUNT(*) FROM msz_login_attempts WHERE attempt_success = 0', + 'auth:sessions:total' => 'SELECT COUNT(*) FROM msz_sessions', + 'auth:tfasessions:total' => 'SELECT COUNT(*) FROM msz_auth_tfa', + 'auth:recovery:total' => 'SELECT COUNT(*) FROM msz_users_password_resets', + 'users:modnotes:total' => 'SELECT COUNT(*) FROM msz_users_modnotes', + 'users:warnings:total' => 'SELECT COUNT(*) FROM msz_users_warnings', + 'users:warnings:visible' => 'SELECT COUNT(*) FROM msz_users_warnings WHERE warn_created > NOW() - INTERVAL 90 DAY', + 'users:bans:total' => 'SELECT COUNT(*) FROM msz_users_bans', + 'users:bans:active' => 'SELECT COUNT(*) FROM msz_users_bans WHERE ban_expires IS NULL OR ban_expires > NOW()', + ]; + + foreach($stats as $name => $query) { + $result = $dbConn->query($query); + $counters->set($name, $result->next() ? $result->getInteger(0) : 0); + } +}); + echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL; $dbConn = $msz->getDbConn();