Shoved EEPROM into Misuzu.

This commit is contained in:
flash 2025-03-27 00:44:42 +00:00
parent 0b7031959b
commit 5f6133c007
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
63 changed files with 3893 additions and 222 deletions

View file

@ -4,6 +4,12 @@ namespace Misuzu;
use stdClass;
use Exception;
use Misuzu\Storage\Uploads\{UploadInfo,UploadsContext};
use Misuzu\Pools\PoolInfo;
use Misuzu\Pools\Rules\{
EnsureVariantRule,EnsureVariantRuleThumb,RemoveStaleRule,ScanForumRule
};
use Misuzu\Storage\Files\{FilesContext,FileInfo};
require_once __DIR__ . '/../misuzu.php';
@ -47,233 +53,359 @@ else
echo 'Only running quick tasks!' . PHP_EOL;
echo PHP_EOL;
msz_sched_task_sql('Ensure main role exists.', true,
'INSERT IGNORE INTO msz_roles (role_id, role_name, role_rank, role_colour, role_description, role_created) VALUES (1, "Member", 1, 1073741824, NULL, NOW())');
$cronPath = sys_get_temp_dir() . '/msz-cron-' . hash('sha256', __DIR__) . '.lock';
if(is_file($cronPath))
die('Misuzu cron script is already running.' . PHP_EOL);
msz_sched_task_sql('Ensure all users have the main role.', true,
'INSERT INTO msz_users_roles (user_id, role_id) SELECT user_id, 1 FROM msz_users AS u WHERE NOT EXISTS (SELECT 1 FROM msz_users_roles AS ur WHERE role_id = 1 AND u.user_id = ur.user_id)');
touch($cronPath);
try {
msz_sched_task_sql('Ensure main role exists.', true,
'INSERT IGNORE INTO msz_roles (role_id, role_name, role_rank, role_colour, role_description, role_created) VALUES (1, "Member", 1, 1073741824, NULL, NOW())');
msz_sched_task_sql('Ensure all user_display_role_id field values are correct with msz_users_roles.', true,
'UPDATE msz_users AS u SET user_display_role_id = (SELECT ur.role_id FROM msz_users_roles AS ur LEFT JOIN msz_roles AS r ON r.role_id = ur.role_id WHERE ur.user_id = u.user_id ORDER BY role_rank DESC LIMIT 1) WHERE NOT EXISTS (SELECT 1 FROM msz_users_roles AS ur WHERE ur.role_id = u.user_display_role_id AND ur.user_id = u.user_id)');
msz_sched_task_sql('Ensure all users have the main role.', true,
'INSERT INTO msz_users_roles (user_id, role_id) SELECT user_id, 1 FROM msz_users AS u WHERE NOT EXISTS (SELECT 1 FROM msz_users_roles AS ur WHERE role_id = 1 AND u.user_id = ur.user_id)');
msz_sched_task_sql('Remove expired sessions.', false,
'DELETE FROM msz_sessions WHERE session_expires < NOW()');
msz_sched_task_sql('Ensure all user_display_role_id field values are correct with msz_users_roles.', true,
'UPDATE msz_users AS u SET user_display_role_id = (SELECT ur.role_id FROM msz_users_roles AS ur LEFT JOIN msz_roles AS r ON r.role_id = ur.role_id WHERE ur.user_id = u.user_id ORDER BY role_rank DESC LIMIT 1) WHERE NOT EXISTS (SELECT 1 FROM msz_users_roles AS ur WHERE ur.role_id = u.user_display_role_id AND ur.user_id = u.user_id)');
msz_sched_task_sql('Remove old password reset tokens.', false,
'DELETE FROM msz_users_password_resets WHERE reset_requested < NOW() - INTERVAL 1 WEEK');
msz_sched_task_sql('Remove expired sessions.', false,
'DELETE FROM msz_sessions WHERE session_expires < NOW()');
msz_sched_task_sql('Remove old login attempt logs.', false,
'DELETE FROM msz_login_attempts WHERE attempt_created < NOW() - INTERVAL 1 MONTH');
msz_sched_task_sql('Remove old password reset tokens.', false,
'DELETE FROM msz_users_password_resets WHERE reset_requested < NOW() - INTERVAL 1 WEEK');
msz_sched_task_sql('Remove old audit log entries.', false,
'DELETE FROM msz_audit_log WHERE log_created < NOW() - INTERVAL 3 MONTH');
msz_sched_task_sql('Remove old login attempt logs.', false,
'DELETE FROM msz_login_attempts WHERE attempt_created < NOW() - INTERVAL 1 MONTH');
msz_sched_task_sql('Remove stale forum tracking entries.', false,
'DELETE tt FROM msz_forum_topics_track AS tt LEFT JOIN msz_forum_topics AS t ON t.topic_id = tt.topic_id WHERE t.topic_bumped < NOW() - INTERVAL 1 MONTH');
msz_sched_task_sql('Remove old audit log entries.', false,
'DELETE FROM msz_audit_log WHERE log_created < NOW() - INTERVAL 3 MONTH');
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_sql('Remove stale forum tracking entries.', false,
'DELETE tt FROM msz_forum_topics_track AS tt LEFT JOIN msz_forum_topics AS t ON t.topic_id = tt.topic_id WHERE t.topic_bumped < NOW() - INTERVAL 1 MONTH');
msz_sched_task_func('Recount forum topics and posts.', true, function() use ($msz) {
$msz->forumCtx->categories->syncForumCounters();
});
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_sql('Clean up expired 2fa tokens.', false,
'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE');
msz_sched_task_func('Clean up OAuth2 related data.', false, function() use ($msz) {
$msz->oauth2Ctx->pruneExpired(function(string $format, ...$args) {
echo sprintf($format, ...$args) . PHP_EOL;
msz_sched_task_func('Recount forum topics and posts.', true, function() use ($msz) {
$msz->forumCtx->categories->syncForumCounters();
});
});
// very heavy stuff that should
msz_sched_task_sql('Clean up expired 2fa tokens.', false,
'DELETE FROM msz_auth_tfa WHERE tfa_created < NOW() - INTERVAL 15 MINUTE');
msz_sched_task_func('Resync statistics counters.', true, function() use ($msz) {
$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_text_format = ''",
'forum:posts:parse:bbcode' => "SELECT COUNT(*) FROM msz_forum_posts WHERE post_text_format = 'bb'",
'forum:posts:parse:markdown' => "SELECT COUNT(*) FROM msz_forum_posts WHERE post_text_format = 'md'",
'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()',
'pms:msgs:total' => 'SELECT COUNT(*) FROM msz_messages',
'pms:msgs:messages' => 'SELECT COUNT(DISTINCT msg_id) FROM msz_messages',
'pms:msgs:replies' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_reply_to IS NULL',
'pms:msgs:drafts' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_sent IS NULL',
'pms:msgs:unread' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_read IS NULL',
'pms:msgs:deleted' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_deleted IS NOT NULL',
'pms:msgs:plain' => "SELECT COUNT(*) FROM msz_messages WHERE msg_body_format = ''",
'pms:msgs:bbcode' => "SELECT COUNT(*) FROM msz_messages WHERE msg_body_format = 'bb'",
'pms:msgs:markdown' => "SELECT COUNT(*) FROM msz_messages WHERE msg_body_format = 'md'",
];
msz_sched_task_func('Clean up OAuth2 related data.', false, function() use ($msz) {
$msz->oauth2Ctx->pruneExpired(function(string $format, ...$args) {
echo sprintf($format, ...$args) . PHP_EOL;
});
});
foreach($stats as $name => $query) {
$result = $msz->dbCtx->conn->query($query);
$msz->counters->set($name, $result->next() ? $result->getInteger(0) : 0);
}
});
// very heavy stuff that should
msz_sched_task_func('Recalculate permissions (maybe)...', false, function() use ($msz) {
$needsRecalc = $msz->config->getBoolean('perms.needsRecalc');
if(!$needsRecalc)
return;
msz_sched_task_func('Resync statistics counters.', true, function() use ($msz) {
$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_text_format = ''",
'forum:posts:parse:bbcode' => "SELECT COUNT(*) FROM msz_forum_posts WHERE post_text_format = 'bb'",
'forum:posts:parse:markdown' => "SELECT COUNT(*) FROM msz_forum_posts WHERE post_text_format = 'md'",
'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()',
'pms:msgs:total' => 'SELECT COUNT(*) FROM msz_messages',
'pms:msgs:messages' => 'SELECT COUNT(DISTINCT msg_id) FROM msz_messages',
'pms:msgs:replies' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_reply_to IS NULL',
'pms:msgs:drafts' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_sent IS NULL',
'pms:msgs:unread' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_read IS NULL',
'pms:msgs:deleted' => 'SELECT COUNT(*) FROM msz_messages WHERE msg_deleted IS NOT NULL',
'pms:msgs:plain' => "SELECT COUNT(*) FROM msz_messages WHERE msg_body_format = ''",
'pms:msgs:bbcode' => "SELECT COUNT(*) FROM msz_messages WHERE msg_body_format = 'bb'",
'pms:msgs:markdown' => "SELECT COUNT(*) FROM msz_messages WHERE msg_body_format = 'md'",
];
$msz->config->removeValues('perms.needsRecalc');
$msz->perms->precalculatePermissions($msz->forumCtx->categories);
});
msz_sched_task_func('Omega disliking comments...', true, function() use ($msz) {
$commentIds = $msz->config->getArray('comments.omegadislike');
if(!is_array($commentIds))
return;
$stmt = $msz->dbCtx->conn->prepare('REPLACE INTO msz_comments_votes (comment_id, user_id, comment_vote) SELECT ?, user_id, -1 FROM msz_users');
foreach($commentIds as $commentId) {
$stmt->addParameter(1, $commentId);
$stmt->execute();
}
});
msz_sched_task_func('Announcing random topics...', true, function() use ($msz) {
$categoryIds = $msz->config->getArray('forum.rngannounce');
if(!is_array($categoryIds))
return;
$stmtRevert = $msz->dbCtx->conn->prepare('UPDATE msz_forum_topics SET topic_type = 0 WHERE forum_id = ? AND topic_type = 2');
$stmtRandom = $msz->dbCtx->conn->prepare('SELECT topic_id FROM msz_forum_topics WHERE forum_id = ? AND topic_deleted IS NULL ORDER BY RAND() LIMIT 1');
$stmtAnnounce = $msz->dbCtx->conn->prepare('UPDATE msz_forum_topics SET topic_type = 2 WHERE topic_id = ?');
foreach($categoryIds as $categoryId) {
$stmtRevert->addParameter(1, $categoryId);
$stmtRevert->execute();
$stmtRandom->addParameter(1, $categoryId);
$stmtRandom->execute();
$resultRandom = $stmtRandom->getResult();
if($resultRandom->next()) {
$stmtAnnounce->addParameter(1, $resultRandom->getInteger(0));
$stmtAnnounce->execute();
foreach($stats as $name => $query) {
$result = $msz->dbCtx->conn->query($query);
$msz->counters->set($name, $result->next() ? $result->getInteger(0) : 0);
}
}
});
});
msz_sched_task_func('Changing category icons...', false, function() use ($msz) {
$categoryIds = $msz->config->getArray('forum.rngicon');
if(!is_array($categoryIds))
return;
msz_sched_task_func('Recalculate permissions (maybe)...', false, function() use ($msz) {
$needsRecalc = $msz->config->getBoolean('perms.needsRecalc');
if(!$needsRecalc)
return;
$stmtIcon = $msz->dbCtx->conn->prepare('UPDATE msz_forum_categories SET forum_icon = COALESCE(?, forum_icon), forum_name = COALESCE(?, forum_name), forum_colour = ((ROUND(RAND() * 255) << 16) | (ROUND(RAND() * 255) << 8) | ROUND(RAND() * 255)) WHERE forum_id = ?');
$stmtUnlock = $msz->dbCtx->conn->prepare('UPDATE msz_forum_topics SET topic_locked = IF(?, NULL, COALESCE(topic_locked, NOW())) WHERE topic_id = ?');
$msz->config->removeValues('perms.needsRecalc');
$msz->perms->precalculatePermissions($msz->forumCtx->categories);
});
foreach($categoryIds as $categoryId) {
$scoped = $msz->config->scopeTo(sprintf('forum.rngicon.fc%d', $categoryId));
msz_sched_task_func('Omega disliking comments...', true, function() use ($msz) {
$commentIds = $msz->config->getArray('comments.omegadislike');
if(!is_array($commentIds))
return;
$icons = $scoped->getArray('icons');
$icon = $icons[array_rand($icons)];
$stmt = $msz->dbCtx->conn->prepare('REPLACE INTO msz_comments_votes (comment_id, user_id, comment_vote) SELECT ?, user_id, -1 FROM msz_users');
foreach($commentIds as $commentId) {
$stmt->addParameter(1, $commentId);
$stmt->execute();
}
});
$name = null;
$names = $scoped->getArray('names');
for($i = 0; $i < count($names); $i += 2)
if($names[$i] === $icon) {
$name = $names[$i + 1] ?? null;
break;
msz_sched_task_func('Announcing random topics...', true, function() use ($msz) {
$categoryIds = $msz->config->getArray('forum.rngannounce');
if(!is_array($categoryIds))
return;
$stmtRevert = $msz->dbCtx->conn->prepare('UPDATE msz_forum_topics SET topic_type = 0 WHERE forum_id = ? AND topic_type = 2');
$stmtRandom = $msz->dbCtx->conn->prepare('SELECT topic_id FROM msz_forum_topics WHERE forum_id = ? AND topic_deleted IS NULL ORDER BY RAND() LIMIT 1');
$stmtAnnounce = $msz->dbCtx->conn->prepare('UPDATE msz_forum_topics SET topic_type = 2 WHERE topic_id = ?');
foreach($categoryIds as $categoryId) {
$stmtRevert->addParameter(1, $categoryId);
$stmtRevert->execute();
$stmtRandom->addParameter(1, $categoryId);
$stmtRandom->execute();
$resultRandom = $stmtRandom->getResult();
if($resultRandom->next()) {
$stmtAnnounce->addParameter(1, $resultRandom->getInteger(0));
$stmtAnnounce->execute();
}
$name ??= $scoped->getString('name');
$stmtIcon->addParameter(1, $icon);
$stmtIcon->addParameter(2, empty($name) ? null : $name);
$stmtIcon->addParameter(3, $categoryId);
$stmtIcon->execute();
$unlockModes = $scoped->getArray('unlock');
foreach($unlockModes as $unlockMode) {
$unlockScoped = $scoped->scopeTo(sprintf('unlock.%s', $unlockMode));
$topicId = $unlockScoped->getInteger('topic');
$match = $unlockScoped->getArray('match');
$matches = in_array($icon, $match);
$stmtUnlock->addParameter(1, $matches ? 1 : 0);
$stmtUnlock->addParameter(2, $topicId);
$stmtUnlock->execute();
}
}
});
});
echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL;
msz_sched_task_func('Changing category icons...', false, function() use ($msz) {
$categoryIds = $msz->config->getArray('forum.rngicon');
if(!is_array($categoryIds))
return;
foreach($schedTasks as $task) {
echo '=> ' . $task->name . PHP_EOL;
$success = true;
$stmtIcon = $msz->dbCtx->conn->prepare('UPDATE msz_forum_categories SET forum_icon = COALESCE(?, forum_icon), forum_name = COALESCE(?, forum_name), forum_colour = ((ROUND(RAND() * 255) << 16) | (ROUND(RAND() * 255) << 8) | ROUND(RAND() * 255)) WHERE forum_id = ?');
$stmtUnlock = $msz->dbCtx->conn->prepare('UPDATE msz_forum_topics SET topic_locked = IF(?, NULL, COALESCE(topic_locked, NOW())) WHERE topic_id = ?');
try {
switch($task->type) {
case 'sql':
$affected = $msz->dbCtx->conn->execute($task->command);
echo $affected . ' rows affected. ' . PHP_EOL;
break;
foreach($categoryIds as $categoryId) {
$scoped = $msz->config->scopeTo(sprintf('forum.rngicon.fc%d', $categoryId));
case 'func':
$result = ($task->command)();
if(is_bool($result))
$success = $result;
break;
$icons = $scoped->getArray('icons');
$icon = $icons[array_rand($icons)];
default:
$success = false;
echo 'Unknown task type.' . PHP_EOL;
break;
$name = null;
$names = $scoped->getArray('names');
for($i = 0; $i < count($names); $i += 2)
if($names[$i] === $icon) {
$name = $names[$i + 1] ?? null;
break;
}
$name ??= $scoped->getString('name');
$stmtIcon->addParameter(1, $icon);
$stmtIcon->addParameter(2, empty($name) ? null : $name);
$stmtIcon->addParameter(3, $categoryId);
$stmtIcon->execute();
$unlockModes = $scoped->getArray('unlock');
foreach($unlockModes as $unlockMode) {
$unlockScoped = $scoped->scopeTo(sprintf('unlock.%s', $unlockMode));
$topicId = $unlockScoped->getInteger('topic');
$match = $unlockScoped->getArray('match');
$matches = in_array($icon, $match);
$stmtUnlock->addParameter(1, $matches ? 1 : 0);
$stmtUnlock->addParameter(2, $topicId);
$stmtUnlock->execute();
}
}
} catch(Exception $ex) {
$success = false;
echo (string)$ex;
} finally {
if($success)
echo 'Done!';
else
echo '!!! FAILED !!!';
});
msz_sched_task_func('Removing stale entries from storage pools...', false, function() use ($msz) {
$pools = [];
$getPool = fn($ruleRaw) => array_key_exists($ruleRaw->poolId, $pools) ? $pools[$ruleRaw->poolId] : (
$pools[$ruleRaw->poolId] = $msz->storageCtx->poolsCtx->pools->getPool($ruleRaw->poolId)
);
// Run cleanup adjacent tasks first
$rules = $msz->storageCtx->poolsCtx->pools->getPoolRules(types: ['remove_stale', 'scan_forum']);
foreach($rules as $ruleRaw) {
$poolInfo = $getPool($ruleRaw);
$rule = $msz->storageCtx->poolsCtx->rules->create($ruleRaw);
if($rule instanceof RemoveStaleRule) {
printf('Removing stale entries for pool #%d...%s', $poolInfo->id, PHP_EOL);
$msz->storageCtx->uploadsCtx->uploads->deleteStaleUploads(
$poolInfo,
$rule->inactiveForSeconds,
$rule->ignoreUploadIds
);
} elseif($rule instanceof ScanForumRule) {
//var_dump('scan_forum', $rule);
// necessary stuff for this doesn't exist yet so this can remain a no-op
}
}
});
msz_sched_task_func('Purging denylisted files...', true, function() use ($msz) {
$fileInfos = $msz->storageCtx->filesCtx->data->getFiles(denied: true);
foreach($fileInfos as $fileInfo) {
printf('Deleting file #%d...%s', $fileInfo->id, PHP_EOL);
$msz->storageCtx->filesCtx->deleteFile($fileInfo);
}
});
msz_sched_task_func('Purging orphaned files...', true, function() use ($msz) {
$fileInfos = $msz->storageCtx->filesCtx->data->getFiles(orphaned: true);
foreach($fileInfos as $fileInfo) {
printf('Deleting file #%d...%s', $fileInfo->id, PHP_EOL);
$msz->storageCtx->filesCtx->deleteFile($fileInfo);
}
});
msz_sched_task_func('Ensuring alternate variants exist...', true, function() use ($msz) {
$pools = [];
$getPool = fn($ruleRaw) => array_key_exists($ruleRaw->poolId, $pools) ? $pools[$ruleRaw->poolId] : (
$pools[$ruleRaw->poolId] = $msz->storageCtx->poolsCtx->pools->getPool($ruleRaw->poolId)
);
$rules = $msz->storageCtx->poolsCtx->pools->getPoolRules(types: ['ensure_variant']);
foreach($rules as $ruleRaw) {
$poolInfo = $getPool($ruleRaw);
$rule = $msz->storageCtx->poolsCtx->rules->create($ruleRaw);
if($rule instanceof EnsureVariantRule) {
if(!$rule->onCron)
continue;
$createVariant = null; // ensure this wasn't previously assigned
if($rule->params instanceof EnsureVariantRuleThumb) {
$createVariant = function(
FilesContext $filesCtx,
UploadsContext $uploadsCtx,
PoolInfo $poolInfo,
EnsureVariantRule $rule,
EnsureVariantRuleThumb $ruleInfo,
FileInfo $originalInfo,
UploadInfo $uploadInfo
) {
$uploadsCtx->uploads->createUploadVariant(
$uploadInfo,
$rule->variant,
$filesCtx->createThumbnailFromRule(
$originalInfo,
$ruleInfo
)
);
};
}
if(!isset($createVariant) || !is_callable($createVariant)) {
printf('!!! Could not create a constructor for "%s" variants for pool #%d !!! Skipping for now...%s', $rule->variant, $poolInfo->id, PHP_EOL);
continue;
}
printf('Ensuring existence of "%s" variants for pool #%d...%s', $rule->variant, $poolInfo->id, PHP_EOL);
$uploads = $msz->storageCtx->uploadsCtx->uploads->getUploads(
poolInfo: $poolInfo,
variant: $rule->variant,
variantExists: false
);
$processed = 0;
foreach($uploads as $uploadInfo)
try {
$createVariant(
$msz->storageCtx->filesCtx,
$msz->storageCtx->uploadsCtx,
$poolInfo,
$rule,
$rule->params,
$msz->storageCtx->filesCtx->data->getFile(
$msz->storageCtx->uploadsCtx->uploads->getUploadVariant($uploadInfo, '')->fileId
),
$uploadInfo
);
} catch(Exception $ex) {
printf('Exception thrown while processing "%s" variant for upload #%d in pool #%d:%s', $rule->variant, $uploadInfo->id, $poolInfo->id, PHP_EOL);
printf('%s%s', $ex->getMessage(), PHP_EOL);
} finally {
++$processed;
}
printf('Processed %d records!%s%s', $processed, PHP_EOL);
}
}
});
echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL;
foreach($schedTasks as $task) {
echo '=> ' . $task->name . PHP_EOL;
$success = true;
try {
switch($task->type) {
case 'sql':
$affected = $msz->dbCtx->conn->execute($task->command);
echo $affected . ' rows affected. ' . PHP_EOL;
break;
case 'func':
$result = ($task->command)();
if(is_bool($result))
$success = $result;
break;
default:
$success = false;
echo 'Unknown task type.' . PHP_EOL;
break;
}
} catch(Exception $ex) {
$success = false;
echo (string)$ex;
} finally {
if($success)
echo 'Done!';
else
echo '!!! FAILED !!!';
echo PHP_EOL;
}
echo PHP_EOL;
}
echo PHP_EOL;
} finally {
unlink($cronPath);
echo 'Took ' . number_format(microtime(true) - MSZ_STARTUP, 5) . 'ms.' . PHP_EOL;
}
echo 'Took ' . number_format(microtime(true) - MSZ_STARTUP, 5) . 'ms.' . PHP_EOL;

77
tools/delete-upload Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env php
<?php
use Index\XNumber;
require_once __DIR__ . '/../misuzu.php';
$options = getopt('s', [
'base62',
], $restIndex);
if($argc <= $restIndex)
die(sprintf('No upload ID specified.%s', PHP_EOL));
$uploadId = $argv[$restIndex];
$isBase62 = isset($options['s']) || isset($options['base62']);
if($isBase62)
$uploadId = XNumber::fromBase62($uploadId);
try {
$uploadInfo = $msz->uploadsCtx->uploads->getUpload(uploadId: $uploadId);
} catch(RuntimeException $ex) {
$hint = $isBase62 ? 'trim last four characters from base62 id' : 'specify -s for base62 ids';
die(sprintf('The requested upload does not exist. (hint: %s)%s', $hint, PHP_EOL));
}
printf(
'Found upload #%d (%s) in pool #%d uploaded by user #%d (%s) at %s with canonical name "%s".%s',
$uploadInfo->id,
$uploadInfo->id62,
$uploadInfo->poolId,
$uploadInfo->userId,
$uploadInfo->remoteAddress,
$uploadInfo->createdAt->toIso8601ZuluString(),
htmlspecialchars($uploadInfo->name),
PHP_EOL
);
$variants = [];
foreach($msz->uploadsCtx->uploads->getUploadVariants($uploadInfo) as $variantInfo) {
$variants[] = $variantInfo;
printf(
'Found variant "%s" linking to file #%d with media type %s.%s',
$variantInfo->variant,
$variantInfo->fileId,
htmlspecialchars($variantInfo->type ?? '(none)'),
PHP_EOL
);
}
$displayStorageDeleteHint = false;
if(count($variants) < 1) {
printf('This upload has no variants to delete.%s', PHP_EOL);
} else {
$variants = array_reverse($variants);
foreach($variants as $variantInfo)
try {
printf('Deleting variant "%s" for upload #%d...%s', $variantInfo->variant, $variantInfo->uploadId, PHP_EOL);
$msz->storageCtx->uploadsCtx->uploads->deleteUploadVariant($variantInfo);
printf('Attempting to delete file...%s', PHP_EOL);
$msz->storageCtx->filesCtx->deleteFile($variantInfo->fileId, false);
} catch(Exception $ex) {
printf('Exception occurred while deleting variant:%s', PHP_EOL);
printf('%s%s', $ex->getMessage(), PHP_EOL);
}
}
try {
printf('Deleting upload #%d...%s', $uploadInfo->id, PHP_EOL);
$msz->uploadsCtx->uploads->deleteUpload($uploadInfo);
} catch(Exception $ex) {
printf('Exception occurred while deleting upload:%s%s%s', PHP_EOL, $ex->getMessage(), PHP_EOL);
}
if($displayStorageDeleteHint)
printf('NOTE: if the exception(s) that occurred while deleting variants were related to database constraints, an upload linking to the same file exists, if they occurred due to filesystem permissions, the file will linger but will be removed during the next scheduled clean up.%s', PHP_EOL);

93
tools/deny-file Executable file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env php
<?php
use Index\{UriBase64,XArray,XNumber};
use Misuzu\Storage\Denylist\DenylistReason;
use Misuzu\Storage\Files\FileInfoGetFileField;
require_once __DIR__ . '/../misuzu.php';
$options = getopt('hsur:', [
'hash',
'base62',
'upload',
'reason:',
], $restIndex);
if($argc <= $restIndex)
die(sprintf('No file ID specified.%s', PHP_EOL));
$fileId = $argv[$restIndex];
$fileField = FileInfoGetFileField::Id;
$uploadId = null;
$isHash = isset($options['h']) || isset($options['hash']);
$isBase62 = isset($options['s']) || isset($options['base62']);
$isUploadId = isset($options['u']) || isset($options['upload']);
try {
$reason = DenylistReason::from($options['r'] ?? $options['reason'] ?? '');
} catch(ValueError $ex) {
die(sprintf(
'Reason value is not supported, it must be one of the following values: %s.%s',
implode(', ', XArray::select(DenylistReason::cases(), fn($case) => $case->value)),
PHP_EOL
));
}
if($isHash) {
$fileId = match(strlen($fileId)) {
32 => $fileId,
43 => UriBase64::decode($fileId),
64 => hex2bin($fileId),
66 => hex2bin(substr($fileId, 2)),
default => null,
};
$fileField = FileInfoGetFileField::Hash;
} else {
if($isBase62)
$fileId = XNumber::fromBase62($fileId);
if($isUploadId) {
$uploadId = $fileId;
$fileId = null;
try {
$variantInfo = $msz->storageCtx->uploadsCtx->uploads->getUploadVariant($uploadId, '');
} catch(RuntimeException $ex) {
$hint = $isBase62 ? 'trim last four characters from base62 id' : 'specify -s for base62 ids';
die(sprintf('The requested upload does not exist. (hint: %s)%s', $hint, PHP_EOL));
}
$fileId = $variantInfo->fileId;
}
}
if($fileId === null)
die(sprintf('Could not resolve file ID from the provided inputs.%s', PHP_EOL));
try {
$fileInfo = $msz->storageCtx->filesCtx->data->getFile($fileId, $fileField);
} catch(RuntimeException $ex) {
$hint = $isUploadId ? (
$isBase62 ? 'trim last four characters from base62 id' : 'specify -s for base62 ids'
) : 'specify -h for hashes or -u to use upload ids';
die(sprintf('The requested file does not exist. (hint: %s)%s', $hint, PHP_EOL));
}
printf(
'Found file #%d of %d bytes with type %s at %s with SHA256 hash %s.%s',
$fileInfo->id,
$fileInfo->size,
htmlspecialchars($fileInfo->type),
$fileInfo->createdAt->toIso8601ZuluString(),
UriBase64::encode($fileInfo->hash),
PHP_EOL
);
try {
printf('Attempting to deny file...%s', PHP_EOL);
$msz->storageCtx->denylistCtx->denyFile($fileInfo, $reason);
} catch(Exception $ex) {
printf('Exception occurred while deleting file:%s', PHP_EOL);
printf('%s%s', $ex->getMessage(), PHP_EOL);
}

83
tools/nuke-file Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env php
<?php
use Index\{UriBase64,XNumber};
use Misuzu\Storage\Files\FileInfoGetFileField;
require_once __DIR__ . '/../misuzu.php';
$options = getopt('hsuv:', [
'hash',
'base62',
'upload',
'variant:',
], $restIndex);
if($argc <= $restIndex)
die(sprintf('No file ID specified.%s', PHP_EOL));
$fileId = $argv[$restIndex];
$fileField = FileInfoGetFileField::Id;
$uploadId = null;
$isHash = isset($options['h']) || isset($options['hash']);
$isBase62 = isset($options['s']) || isset($options['base62']);
$isUploadId = isset($options['u']) || isset($options['upload']);
$variant = $options['variant'] ?? $options['v'] ?? '';
if($isHash) {
$fileId = match(strlen($fileId)) {
32 => $fileId,
43 => UriBase64::decode($fileId),
64 => hex2bin($fileId),
66 => hex2bin(substr($fileId, 2)),
default => null,
};
$fileField = FileInfoGetFileField::Hash;
} else {
if($isBase62)
$fileId = XNumber::fromBase62($fileId);
if($isUploadId) {
$uploadId = $fileId;
$fileId = null;
try {
$variantInfo = $msz->storageCtx->uploadsCtx->uploads->getUploadVariant($uploadId, $variant);
} catch(RuntimeException $ex) {
$hint = $isBase62 ? 'trim last four characters from base62 id' : 'specify -s for base62 ids';
die(sprintf('The requested upload variant does not exist. (hint: %s)%s', $hint, PHP_EOL));
}
$fileId = $variantInfo->fileId;
}
}
if($fileId === null)
die(sprintf('Could not resolve file ID from the provided inputs.%s', PHP_EOL));
try {
$fileInfo = $msz->storageCtx->filesCtx->data->getFile($fileId, $fileField);
} catch(RuntimeException $ex) {
$hint = $isUploadId ? (
$isBase62 ? 'trim last four characters from base62 id' : 'specify -s for base62 ids'
) : 'specify -h for hashes or -u to use upload ids, with -v for variant selection';
die(sprintf('The requested file does not exist. (hint: %s)%s', $hint, PHP_EOL));
}
printf(
'Found file #%d of %d bytes with type %s at %s with SHA256 hash %s.%s',
$fileInfo->id,
$fileInfo->size,
htmlspecialchars($fileInfo->type),
$fileInfo->createdAt->toIso8601ZuluString(),
UriBase64::encode($fileInfo->hash),
PHP_EOL
);
try {
printf('Attempting to delete file...%s', PHP_EOL);
$msz->storageCtx->filesCtx->deleteFile($fileInfo);
} catch(Exception $ex) {
printf('Exception occurred while deleting file:%s', PHP_EOL);
printf('%s%s', $ex->getMessage(), PHP_EOL);
}

17
tools/purge-user-uploads Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/../misuzu.php';
if($argc <= 1)
die(sprintf('No uploader ID specified.%s', PHP_EOL));
$userId = $argv[1];
try {
printf('Attempting to delete uploads by user #%s...%s', $userId, PHP_EOL);
$msz->storageCtx->uploadsCtx->uploads->deleteUploadsByUser($userId);
printf('Done! Actual purging of data in storage will be handled by the next cron cycle.%s', PHP_EOL);
} catch(Exception $ex) {
printf('Exception occurred while trying to delete user uploads:%s', PHP_EOL);
printf('%s%s', $ex->getMessage(), PHP_EOL);
}