Restructured public folder and initialisation process.

This commit is contained in:
flash 2023-07-19 19:03:53 +00:00
parent 67b41379e5
commit a09c8b7ef6
62 changed files with 210 additions and 331 deletions

View file

@ -1,134 +0,0 @@
<?php
namespace Misuzu;
$misuzuBypassLockdown = true;
require_once '../misuzu.php';
function ghcb_strip_prefix(string $line): string {
$findColon = mb_strpos($line, ':');
return trim($findColon === false || $findColon >= 10 ? $line : mb_substr($line, $findColon + 1));
}
function ghcb_changelog_action(string &$line): string {
$original = trim($line);
$line = ghcb_strip_prefix($line);
$firstSix = mb_strtolower(mb_substr($original, 0, 6));
if($firstSix === 'revert'
|| $firstSix === 'restor')
return 'revert';
if($firstSix === 'import')
return 'import';
$firstThree = mb_strtolower(mb_substr($original, 0, 3));
if($firstThree === 'add'
|| $firstSix === 'create')
return 'add';
if($firstThree === 'fix')
return 'fix';
$firstFour = mb_strtolower(mb_substr($original, 0, 4));
$firstEight = mb_strtolower(mb_substr($original, 0, 8));
if($firstSix === 'delete'
|| $firstSix === 'remove'
|| $firstFour === 'nuke'
|| $firstEight === 'dropkick')
return 'remove';
return 'update';
}
header('Content-Type: text/plain; charset=utf-8');
if($_SERVER['REQUEST_METHOD'] !== 'POST')
die('no');
$config = MSZ_ROOT . '/config/github.ini';
if(!is_file($config))
die('config missing');
$config = parse_ini_file(MSZ_ROOT . '/config/github.ini', true);
if(empty($config['tokens']['token']))
die('config invalid');
$isGitea = isset($_SERVER['HTTP_X_GITEA_DELIVERY']) && isset($_SERVER['HTTP_X_GITEA_EVENT']);
$rawData = file_get_contents('php://input');
$sigParts = $isGitea
? ['sha256', $_SERVER['HTTP_X_GITEA_SIGNATURE']]
: explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'] ?? '', 2);
if(empty($sigParts[1]))
die('invalid signature');
$repoAuthenticated = false;
foreach($config['tokens']['token'] as $repoName => $repoToken) {
if(hash_equals(hash_hmac($sigParts[0], $rawData, $repoToken), $sigParts[1])) {
$repoAuthenticated = true;
break;
}
}
if(!$repoAuthenticated)
die('signature check failed');
$data = json_decode($_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'
? $_POST['payload']
: $rawData);
if(empty($data))
die('body is corrupt');
if(empty($data->repository->full_name))
die('body is corrupt');
if($data->repository->full_name !== $repoName)
die('invalid repository token');
if($_SERVER['HTTP_X_GITHUB_EVENT'] !== 'push')
die('only push event is supported');
$commitCount = count($data->commits);
if($commitCount < 1)
die('no commits received');
$repoInfo = $config['repo:' . $repoName] ?? [];
$repoMaster = 'refs/heads/master';
if(!empty($repoInfo['master']))
$repoMaster = $repoInfo['master'];
if($data->ref !== $repoMaster)
die('only the master branch is tracked');
$changelog = $msz->getChangelog();
$tags = $repoInfo['tags'] ?? [];
$addresses = $config['addresses'] ?? [];
foreach($data->commits as $commit) {
$message = trim($commit->message);
if(mb_strpos($message, $repoName) !== false
|| mb_substr($message, 0, 2) === '//'
|| mb_strpos(mb_strtolower($message), 'merge pull request') !== false)
continue;
$index = mb_strpos($message, "\n");
$line = $index === false ? $message : mb_substr($message, 0, $index);
$body = trim($index === false ? '' : mb_substr($message, $index + 1));
$changeInfo = $changelog->createChange(
ghcb_changelog_action($line),
$line, $body,
$addresses[$commit->author->email] ?? null,
max(1, strtotime($commit->timestamp))
);
if(!empty($tags))
foreach($tags as $tag)
$changelog->addTagToChange($changeInfo, $tag);
}

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';

View file

@ -1,6 +0,0 @@
<?php
namespace Misuzu;
require_once '../../misuzu.php';
url_redirect('auth-login');

View file

@ -1,167 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\AuthToken;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserAuthSession;
use Misuzu\Users\UserLoginAttempt;
use Misuzu\Users\UserSession;
use Misuzu\Users\UserSessionCreationFailedException;
require_once '../../misuzu.php';
if(UserSession::hasCurrent()) {
url_redirect('index');
return;
}
if(!empty($_GET['resolve'])) {
header('Content-Type: application/json; charset=utf-8');
try {
// Only works for usernames, this is by design
$userInfo = User::byUsername((string)filter_input(INPUT_GET, 'name'));
} catch(UserNotFoundException $ex) {
echo json_encode([
'id' => 0,
'name' => '',
'avatar' => url('user-avatar', ['res' => 200, 'user' => 0]),
]);
return;
}
echo json_encode([
'id' => $userInfo->getId(),
'name' => $userInfo->getUsername(),
'avatar' => url('user-avatar', ['user' => $userInfo->getId(), 'res' => 200]),
]);
return;
}
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$remainingAttempts = UserLoginAttempt::remaining($ipAddress);
$siteIsPrivate = $cfg->getBoolean('private.enable');
if($siteIsPrivate) {
[
'private.perm.cat' => $loginPermCat,
'private.perm.val' => $loginPermVal,
'private.msg' => $sitePrivateMessage,
'private.allow_password_reset' => $canResetPassword,
] = $cfg->getValues([
'private.perm.cat:s',
'private.perm.val:i',
'private.msg:s',
['private.allow_password_reset:b', true],
]);
} else {
$loginPermCat = '';
$loginPermVal = 0;
$sitePrivateMessage = '';
$canResetPassword = true;
}
while(!empty($_POST['login']) && is_array($_POST['login'])) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$loginRedirect = empty($_POST['login']['redirect']) || !is_string($_POST['login']['redirect']) ? '' : $_POST['login']['redirect'];
if(empty($_POST['login']['username']) || empty($_POST['login']['password'])
|| !is_string($_POST['login']['username']) || !is_string($_POST['login']['password'])) {
$notices[] = "You didn't fill in a username and/or password.";
break;
}
if($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, please try again later.";
break;
}
$attemptsRemainingError = sprintf(
"%d attempt%s remaining",
$remainingAttempts - 1,
$remainingAttempts === 2 ? '' : 's'
);
$loginFailedError = "Invalid username or password, {$attemptsRemainingError}.";
try {
$userInfo = User::byUsernameOrEMailAddress($_POST['login']['username']);
} catch(UserNotFoundException $ex) {
UserLoginAttempt::create($ipAddress, $countryCode, false);
$notices[] = $loginFailedError;
break;
}
if(!$userInfo->hasPassword()) {
$notices[] = 'Your password has been invalidated, please reset it.';
break;
}
if($userInfo->isDeleted() || !$userInfo->checkPassword($_POST['login']['password'])) {
UserLoginAttempt::create($ipAddress, $countryCode, false, $userInfo);
$notices[] = $loginFailedError;
break;
}
if($userInfo->passwordNeedsRehash())
$userInfo->setPassword($_POST['login']['password'])->save();
if(!empty($loginPermCat) && $loginPermVal > 0 && !perms_check_user($loginPermCat, $userInfo->getId(), $loginPermVal)) {
$notices[] = "Login succeeded, but you're not allowed to browse the site right now.";
UserLoginAttempt::create($ipAddress, $countryCode, true, $userInfo);
break;
}
if($userInfo->hasTOTP()) {
url_redirect('auth-two-factor', [
'token' => UserAuthSession::create($userInfo)->getToken(),
]);
return;
}
UserLoginAttempt::create($ipAddress, $countryCode, true, $userInfo);
try {
$sessionInfo = UserSession::create($userInfo, $ipAddress, $countryCode);
$sessionInfo->setCurrent();
} catch(UserSessionCreationFailedException $ex) {
$notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!";
break;
}
$authToken = AuthToken::create($userInfo, $sessionInfo);
$authToken->applyCookie($sessionInfo->getExpiresTime());
if(!is_local_url($loginRedirect))
$loginRedirect = url('index');
redirect($loginRedirect);
return;
}
$welcomeMode = !empty($_GET['welcome']);
$loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login']['username']) ? $_POST['login']['username'] : (
!empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : ''
);
$loginRedirect = $welcomeMode ? url('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? url('index');
$canRegisterAccount = !$siteIsPrivate;
Template::render('auth.login', [
'login_notices' => $notices,
'login_username' => $loginUsername,
'login_redirect' => $loginRedirect,
'login_can_reset_password' => $canResetPassword,
'login_can_register' => $canRegisterAccount,
'login_attempts_remaining' => $remainingAttempts,
'login_welcome' => $welcomeMode,
'login_private' => [
'enabled' => $siteIsPrivate,
'message' => $sitePrivateMessage,
],
]);

View file

@ -1,23 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
require_once '../../misuzu.php';
if(!UserSession::hasCurrent()) {
url_redirect('index');
return;
}
if(CSRF::validateRequest()) {
AuthToken::nukeCookie();
UserSession::getCurrent()->delete();
UserSession::unsetCurrent();
User::unsetCurrent();
url_redirect('index');
return;
}
Template::render('auth.logout');

View file

@ -1,150 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserLoginAttempt;
use Misuzu\Users\UserRecoveryToken;
use Misuzu\Users\UserRecoveryTokenNotFoundException;
use Misuzu\Users\UserRecoveryTokenCreationFailedException;
use Misuzu\Users\UserSession;
require_once '../../misuzu.php';
if(UserSession::hasCurrent()) {
url_redirect('settings-account');
return;
}
$reset = !empty($_POST['reset']) && is_array($_POST['reset']) ? $_POST['reset'] : [];
$forgot = !empty($_POST['forgot']) && is_array($_POST['forgot']) ? $_POST['forgot'] : [];
$userId = !empty($reset['user']) ? (int)$reset['user'] : (
!empty($_GET['user']) ? (int)$_GET['user'] : 0
);
if($userId > 0)
try {
$userInfo = User::byId($userId);
} catch(UserNotFoundException $ex) {
url_redirect('auth-forgot');
return;
}
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$siteIsPrivate = $cfg->getBoolean('private.enable');
$canResetPassword = $siteIsPrivate ? $cfg->getBoolean('private.allow_password_reset', true) : true;
$remainingAttempts = UserLoginAttempt::remaining($ipAddress);
while($canResetPassword) {
if(!empty($reset) && $userId > 0) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$verificationCode = !empty($reset['verification']) && is_string($reset['verification']) ? $reset['verification'] : '';
try {
$tokenInfo = UserRecoveryToken::byToken($verificationCode);
} catch(UserRecoveryTokenNotFoundException $ex) {
unset($tokenInfo);
}
if(empty($tokenInfo) || !$tokenInfo->isValid() || $tokenInfo->getUserId() !== $userInfo->getId()) {
$notices[] = 'Invalid verification code!';
break;
}
$password = !empty($reset['password']) && is_array($reset['password']) ? $reset['password'] : [];
$passwordNew = !empty($password['new']) && is_string($password['new']) ? $password['new'] : '';
$passwordConfirm = !empty($password['confirm']) && is_string($password['confirm']) ? $password['confirm'] : '';
if(empty($passwordNew) || empty($passwordConfirm)
|| $passwordNew !== $passwordConfirm) {
$notices[] = "Password confirmation failed!";
break;
}
if(User::validatePassword($passwordNew) !== '') {
$notices[] = 'Your password is too weak!';
break;
}
// also disables two factor auth to prevent getting locked out of account entirely
// this behaviour should really be replaced with recovery keys...
$userInfo->setPassword($passwordNew)
->removeTOTPKey()
->save();
$msz->createAuditLog('PASSWORD_RESET', [], $userInfo);
$tokenInfo->invalidate();
url_redirect('auth-login', ['redirect' => '/']);
return;
}
if(!empty($forgot)) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
if(empty($forgot['email']) || !is_string($forgot['email'])) {
$notices[] = "You didn't supply an e-mail address.";
break;
}
if($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, please try again later.";
break;
}
try {
$forgotUser = User::byEMailAddress($forgot['email']);
} catch(UserNotFoundException $ex) {
unset($forgotUser);
}
if(empty($forgotUser) || $forgotUser->isDeleted()) {
$notices[] = "This e-mail address is not registered with us.";
break;
}
try {
$tokenInfo = UserRecoveryToken::byUserAndRemoteAddress($forgotUser, $ipAddress);
} catch(UserRecoveryTokenNotFoundException $ex) {
$tokenInfo = UserRecoveryToken::create($forgotUser, $ipAddress);
$recoveryMessage = Mailer::template('password-recovery', [
'username' => $forgotUser->getUsername(),
'token' => $tokenInfo->getToken(),
]);
$recoveryMail = Mailer::sendMessage(
[$forgotUser->getEMailAddress() => $forgotUser->getUsername()],
$recoveryMessage['subject'], $recoveryMessage['message']
);
if(!$recoveryMail) {
$notices[] = "Failed to send reset email, please contact the administrator.";
$tokenInfo->invalidate();
break;
}
}
url_redirect('auth-reset', ['user' => $forgotUser->getId()]);
return;
}
break;
}
Template::render(isset($userInfo) ? 'auth.password_reset' : 'auth.password_forgot', [
'password_notices' => $notices,
'password_email' => !empty($forget['email']) && is_string($forget['email']) ? $forget['email'] : '',
'password_attempts_remaining' => $remainingAttempts,
'password_user' => $userInfo ?? null,
'password_verification' => $verificationCode ?? '',
]);

View file

@ -1,101 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserCreationFailedException;
use Misuzu\Users\UserLoginAttempt;
use Misuzu\Users\UserRole;
use Misuzu\Users\UserSession;
use Misuzu\Users\UserWarning;
require_once '../../misuzu.php';
if(UserSession::hasCurrent()) {
url_redirect('index');
return;
}
$register = !empty($_POST['register']) && is_array($_POST['register']) ? $_POST['register'] : [];
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$remainingAttempts = UserLoginAttempt::remaining($_SERVER['REMOTE_ADDR']);
$restricted = UserWarning::countByRemoteAddress($ipAddress) > 0 ? 'ban' : '';
while(!$restricted && !empty($register)) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
if($remainingAttempts < 1) {
$notices[] = "There are too many failed login attempts from your IP address, you may not create an account right now.";
break;
}
if(empty($register['username']) || empty($register['password']) || empty($register['email']) || empty($register['question'])
|| !is_string($register['username']) || !is_string($register['password']) || !is_string($register['email']) || !is_string($register['question'])) {
$notices[] = "You haven't filled in all fields.";
break;
}
$checkSpamBot = mb_strtolower($register['question']);
$spamBotValid = [
'21', 'twentyone', 'twenty-one', 'twenty one',
];
$spamBotHint = [
'19', 'nineteen', 'nine-teen', 'nine teen',
];
if(!in_array($checkSpamBot, $spamBotValid)) {
if(in_array($checkSpamBot, $spamBotHint))
$notices[] = '_play_hint';
$notices[] = 'Human only cool club, robots begone.';
break;
}
$usernameValidation = User::validateUsername($register['username']);
if($usernameValidation !== '')
$notices[] = User::usernameValidationErrorString($usernameValidation);
$emailValidation = User::validateEMailAddress($register['email']);
if($emailValidation !== '')
$notices[] = $emailValidation === 'in-use'
? 'This e-mail address has already been used!'
: 'The e-mail address you entered is invalid!';
if($register['password_confirm'] !== $register['password'])
$notices[] = 'The given passwords don\'t match.';
if(User::validatePassword($register['password']) !== '')
$notices[] = 'Your password is too weak!';
if(!empty($notices))
break;
try {
$createUser = User::create(
$register['username'],
$register['password'],
$register['email'],
$ipAddress,
$countryCode
);
} catch(UserCreationFailedException $ex) {
$notices[] = 'Something went wrong while creating your account, please alert an administrator or a developer about this!';
break;
}
$createUser->addRole(UserRole::byDefault());
url_redirect('auth-login-welcome', ['username' => $createUser->getUsername()]);
return;
}
Template::render('auth.register', [
'register_notices' => $notices,
'register_username' => !empty($register['username']) && is_string($register['username']) ? $register['username'] : '',
'register_email' => !empty($register['email']) && is_string($register['email']) ? $register['email'] : '',
'register_restricted' => $restricted,
]);

View file

@ -1,21 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../misuzu.php';
if(!isset($userInfoReal) || !$authToken->hasImpersonatedUserId() || !CSRF::validateRequest()) {
url_redirect('index');
return;
}
$authToken->removeImpersonatedUserId();
$authToken->applyCookie();
$impUserId = User::hasCurrent() ? User::getCurrent()->getId() : 0;
url_redirect(
$impUserId > 0 ? 'manage-user' : 'index',
['user' => $impUserId]
);

View file

@ -1,102 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserLoginAttempt;
use Misuzu\Users\UserSession;
use Misuzu\Users\UserSessionCreationFailedException;
use Misuzu\Users\UserAuthSession;
use Misuzu\Users\UserAuthSessionNotFoundException;
require_once '../../misuzu.php';
if(UserSession::hasCurrent()) {
url_redirect('index');
return;
}
$ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$twofactor = !empty($_POST['twofactor']) && is_array($_POST['twofactor']) ? $_POST['twofactor'] : [];
$notices = [];
$remainingAttempts = UserLoginAttempt::remaining($ipAddress);
try {
$tokenInfo = UserAuthSession::byToken(
!empty($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : (
!empty($twofactor['token']) && is_string($twofactor['token']) ? $twofactor['token'] : ''
)
);
} catch(UserAuthSessionNotFoundException $ex) {}
if(empty($tokenInfo) || $tokenInfo->hasExpired()) {
url_redirect('auth-login');
return;
}
$userInfo = $tokenInfo->getUser();
// checking user_totp_key specifically because there's a fringe chance that
// there's a token present, but totp is actually disabled
if(!$userInfo->hasTOTP()) {
url_redirect('auth-login');
return;
}
while(!empty($twofactor)) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
break;
}
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$redirect = !empty($twofactor['redirect']) && is_string($twofactor['redirect']) ? $twofactor['redirect'] : '';
if(empty($twofactor['code']) || !is_string($twofactor['code'])) {
$notices[] = 'Code field was empty.';
break;
}
if($remainingAttempts < 1) {
$notices[] = 'There are too many failed login attempts from your IP address, please try again later.';
break;
}
if(!in_array($twofactor['code'], $userInfo->getValidTOTPTokens())) {
$notices[] = sprintf(
"Invalid two factor code, %d attempt%s remaining",
$remainingAttempts - 1,
$remainingAttempts === 2 ? '' : 's'
);
UserLoginAttempt::create($ipAddress, $countryCode, false, $userInfo);
break;
}
UserLoginAttempt::create($ipAddress, $countryCode, true, $userInfo);
$tokenInfo->delete();
try {
$sessionInfo = UserSession::create($userInfo, $ipAddress, $countryCode);
$sessionInfo->setCurrent();
} catch(UserSessionCreationFailedException $ex) {
$notices[] = "Something broke while creating a session for you, please tell an administrator or developer about this!";
break;
}
$authToken = AuthToken::create($userInfo, $sessionInfo);
$authToken->applyCookie($sessionInfo->getExpiresTime());
if(!is_local_url($redirect)) {
$redirect = url('index');
}
redirect($redirect);
return;
}
Template::render('auth.twofactor', [
'twofactor_notices' => $notices,
'twofactor_redirect' => !empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : url('index'),
'twofactor_attempts_remaining' => $remainingAttempts,
'twofactor_token' => $tokenInfo->getToken(),
]);

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';

View file

@ -1,259 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\User;
require_once '../misuzu.php';
// basing whether or not this is an xhr request on whether a referrer header is present
// this page is never directy accessed, under normal circumstances
$redirect = !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : url('index');
if(!is_local_url($redirect)) {
echo render_info('Possible request forgery detected.', 403);
return;
}
if(!CSRF::validateRequest()) {
echo render_info("Couldn't verify this request, please refresh the page and try again.", 403);
return;
}
$currentUserInfo = User::getCurrent();
if($currentUserInfo === null) {
echo render_info('You must be logged in to manage comments.', 401);
return;
}
if($currentUserInfo->isBanned()) {
echo render_info('You have been banned, check your profile for more information.', 403);
return;
}
if($currentUserInfo->isSilenced()) {
echo render_info('You have been silenced, check your profile for more information.', 403);
return;
}
$comments = $msz->getComments();
$commentPerms = $currentUserInfo->commentPerms();
$commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$commentMode = (string)filter_input(INPUT_GET, 'm');
$commentVote = (int)filter_input(INPUT_GET, 'v', FILTER_SANITIZE_NUMBER_INT);
if(!empty($commentId)) {
try {
$commentInfo = $comments->getPostById($commentId);
} catch(RuntimeException $ex) {
echo render_info('Post not found.', 404);
return;
}
$categoryInfo = $comments->getCategoryByPost($commentInfo);
}
if($commentMode !== 'create' && empty($commentInfo)) {
echo render_error(400);
return;
}
switch($commentMode) {
case 'pin':
case 'unpin':
if(!$commentPerms['can_pin'] && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to pin comments.", 403);
break;
}
if($commentInfo->isDeleted()) {
echo render_info("This comment doesn't exist!", 400);
break;
}
if($commentInfo->isReply()) {
echo render_info("You can't pin replies!", 400);
break;
}
$isPinning = $commentMode === 'pin';
if($isPinning) {
if($commentInfo->isPinned()) {
echo render_info('This comment is already pinned.', 400);
break;
}
$comments->pinPost($commentInfo);
} else {
if(!$commentInfo->isPinned()) {
echo render_info("This comment isn't pinned yet.", 400);
break;
}
$comments->unpinPost($commentInfo);
}
redirect($redirect . '#comment-' . $commentInfo->getId());
break;
case 'vote':
if(!$commentPerms['can_vote'] && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to vote on comments.", 403);
break;
}
if($commentInfo->isDeleted()) {
echo render_info("This comment doesn't exist!", 400);
break;
}
if($commentVote > 0)
$comments->addPostPositiveVote($commentInfo, $currentUserInfo);
elseif($commentVote < 0)
$comments->addPostNegativeVote($commentInfo, $currentUserInfo);
else
$comments->removePostVote($commentInfo, $currentUserInfo);
redirect($redirect . '#comment-' . $commentInfo->getId());
break;
case 'delete':
if(!$commentPerms['can_delete'] && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to delete comments.", 403);
break;
}
if($commentInfo->isDeleted()) {
echo render_info(
$commentPerms['can_delete_any'] ? 'This comment is already marked for deletion.' : "This comment doesn't exist.",
400
);
break;
}
$isOwnComment = $commentInfo->getUserId() === (string)$currentUserInfo->getId();
$isModAction = $commentPerms['can_delete_any'] && !$isOwnComment;
if(!$isModAction && !$isOwnComment) {
echo render_info("You're not allowed to delete comments made by others.", 403);
break;
}
$comments->deletePost($commentInfo);
if($isModAction) {
$msz->createAuditLog('COMMENT_ENTRY_DELETE_MOD', [
$commentInfo->getId(),
$commentUserId = $commentInfo->getUserId(),
'<username>',
]);
} else {
$msz->createAuditLog('COMMENT_ENTRY_DELETE', [$commentInfo->getId()]);
}
redirect($redirect);
break;
case 'restore':
if(!$commentPerms['can_delete_any']) {
echo render_info("You're not allowed to restore deleted comments.", 403);
break;
}
if(!$commentInfo->isDeleted()) {
echo render_info("This comment isn't in a deleted state.", 400);
break;
}
$comments->restorePost($commentInfo);
$msz->createAuditLog('COMMENT_ENTRY_RESTORE', [
$commentInfo->getId(),
$commentUserId = $commentInfo->getUserId(),
'<username>',
]);
redirect($redirect . '#comment-' . $commentInfo->getId());
break;
case 'create':
if(!$commentPerms['can_comment'] && !$categoryInfo->isOwner($currentUserInfo)) {
echo render_info("You're not allowed to post comments.", 403);
break;
}
if(empty($_POST['comment']) || !is_array($_POST['comment'])) {
echo render_info('Missing data.', 400);
break;
}
try {
$categoryId = isset($_POST['comment']['category']) && is_string($_POST['comment']['category'])
? (int)$_POST['comment']['category']
: 0;
$categoryInfo = $comments->getCategoryById($categoryId);
} catch(RuntimeException $ex) {
echo render_info('This comment category doesn\'t exist.', 404);
break;
}
if($categoryInfo->isLocked() && !$commentPerms['can_lock']) {
echo render_info('This comment category has been locked.', 403);
break;
}
$commentText = !empty($_POST['comment']['text']) && is_string($_POST['comment']['text']) ? $_POST['comment']['text'] : '';
$commentReply = (string)(!empty($_POST['comment']['reply']) && is_string($_POST['comment']['reply']) ? (int)$_POST['comment']['reply'] : 0);
$commentLock = !empty($_POST['comment']['lock']) && $commentPerms['can_lock'];
$commentPin = !empty($_POST['comment']['pin']) && $commentPerms['can_pin'];
if($commentLock) {
if($categoryInfo->isLocked())
$comments->unlockCategory($categoryInfo);
else
$comments->lockCategory($categoryInfo);
}
if(strlen($commentText) > 0) {
$commentText = preg_replace("/[\r\n]{2,}/", "\n", $commentText);
} else {
if($commentPerms['can_lock']) {
echo render_info('The action has been processed.', 400);
} else {
echo render_info('Your comment is too short.', 400);
}
break;
}
if(mb_strlen($commentText) > 5000) {
echo render_info('Your comment is too long.', 400);
break;
}
if($commentReply > 0) {
try {
$parentInfo = $comments->getPostById($commentReply);
} catch(RuntimeException $ex) {}
if(!isset($parentInfo) || $parentInfo->isDeleted()) {
echo render_info('The comment you tried to reply to does not exist.', 404);
break;
}
}
$commentInfo = $comments->createPost(
$categoryInfo,
$parentInfo ?? null,
$currentUserInfo,
$commentText,
$commentPin
);
redirect($redirect . '#comment-' . $commentInfo->getId());
break;
default:
echo render_info('Not found.', 404);
}

View file

@ -1,80 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../misuzu.php';
$forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
$forumId = max($forumId, 0);
if($forumId === 0) {
url_redirect('forum-index');
exit;
}
$forum = forum_get($forumId);
$forumUser = User::getCurrent();
$forumUserId = $forumUser === null ? 0 : $forumUser->getId();
if(empty($forum) || ($forum['forum_type'] == MSZ_FORUM_TYPE_LINK && empty($forum['forum_link']))) {
echo render_error(404);
return;
}
$perms = forum_perms_get_user($forum['forum_id'], $forumUserId)[MSZ_FORUM_PERMS_GENERAL];
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
echo render_error(403);
return;
}
if(isset($forumUser) && $forumUser->hasActiveWarning())
$perms &= ~MSZ_FORUM_PERM_SET_WRITE;
Template::set('forum_perms', $perms);
if($forum['forum_type'] == MSZ_FORUM_TYPE_LINK) {
forum_increment_clicks($forum['forum_id']);
redirect($forum['forum_link']);
return;
}
$forumPagination = new Pagination($forum['forum_topic_count'], 20);
if(!$forumPagination->hasValidOffset() && $forum['forum_topic_count'] > 0) {
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)
)
: [];
$forumMayHaveChildren = forum_may_have_children($forum['forum_type']);
if($forumMayHaveChildren) {
$forum['forum_subforums'] = forum_get_children($forum['forum_id'], $forumUserId);
foreach($forum['forum_subforums'] as $skey => $subforum) {
$forum['forum_subforums'][$skey]['forum_subforums']
= forum_get_children($subforum['forum_id'], $forumUserId);
}
}
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_topics' => $topics,
'forum_pagination' => $forumPagination,
]);

View file

@ -1,41 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../misuzu.php';
$indexMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
$forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
$currentUser = User::getCurrent();
$currentUserId = $currentUser === null ? 0 : $currentUser->getId();
switch($indexMode) {
case 'mark':
url_redirect($forumId < 1 ? 'forum-mark-global' : 'forum-mark-single', ['forum' => $forumId]);
break;
default:
$categories = forum_get_root_categories($currentUserId);
$blankForum = count($categories) < 1;
foreach($categories as $key => $category) {
$categories[$key]['forum_subforums'] = forum_get_children($category['forum_id'], $currentUserId);
foreach($categories[$key]['forum_subforums'] as $skey => $sub) {
if(!forum_may_have_children($sub['forum_type'])) {
continue;
}
$categories[$key]['forum_subforums'][$skey]['forum_subforums']
= forum_get_children($sub['forum_id'], $currentUserId);
}
}
Template::render('forum.index', [
'forum_categories' => $categories,
'forum_empty' => $blankForum,
]);
break;
}

View file

@ -1,67 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_FORUM, User::getCurrent()->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_leader.unranked.forum:a',
'forum_leader.unranked.topic:a',
]);
} else $unrankedForums = $unrankedTopics = [];
$leaderboards = forum_leaderboard_categories();
$leaderboard = forum_leaderboard_listing($leaderboardYear, $leaderboardMonth, $unrankedForums, $unrankedTopics);
$leaderboardName = 'All Time';
if($leaderboardYear) {
$leaderboardName = "Leaderboard {$leaderboardYear}";
if($leaderboardMonth)
$leaderboardName .= "-{$leaderboardMonth}";
}
if($leaderboardMode === 'markdown') {
$markdown = <<<MD
# {$leaderboardName}
| 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']);
}
Template::set('leaderboard_markdown', $markdown);
}
Template::render('forum.leaderboard', [
'leaderboard_id' => $leaderboardId,
'leaderboard_name' => $leaderboardName,
'leaderboard_categories' => $leaderboards,
'leaderboard_data' => $leaderboard,
'leaderboard_mode' => $leaderboardMode,
]);

View file

@ -1,217 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
require_once '../../misuzu.php';
$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';
$postRequestVerified = CSRF::validateRequest();
if(!empty($postMode) && !UserSession::hasCurrent()) {
echo render_info('You must be logged in to manage posts.', 401);
return;
}
$currentUser = User::getCurrent();
$currentUserId = $currentUser === null ? 0 : $currentUser->getId();
if(isset($currentUser) && $currentUser->isBanned()) {
echo render_info('You have been banned, check your profile for more information.', 403);
return;
}
if(isset($currentUser) && $currentUser->isSilenced()) {
echo render_info('You have been silenced, check your profile for more information.', 403);
return;
}
$postInfo = forum_post_get($postId, true);
$perms = empty($postInfo)
? 0
: forum_perms_get_user($postInfo['forum_id'], $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
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($canDelete !== MSZ_E_FORUM_POST_DELETE_OK) {
echo render_info($canDeleteMsg, $responseCode);
break;
}
if($postRequestVerified && !$submissionConfirmed) {
url_redirect('forum-post', [
'post' => $postInfo['post_id'],
'post_fragment' => 'p' . $postInfo['post_id'],
]);
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']),
'params' => [
'p' => $postInfo['post_id'],
'm' => 'delete',
],
]);
break;
}
$deletePost = forum_post_delete($postInfo['post_id']);
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']]);
break;
case 'nuke':
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST)) {
echo render_error(403);
break;
}
if($postRequestVerified && !$submissionConfirmed) {
url_redirect('forum-post', [
'post' => $postInfo['post_id'],
'post_fragment' => 'p' . $postInfo['post_id'],
]);
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']),
'params' => [
'p' => $postInfo['post_id'],
'm' => 'nuke',
],
]);
break;
}
$nukePost = forum_post_nuke($postInfo['post_id']);
if(!$nukePost) {
echo render_error(500);
break;
}
$msz->createAuditLog('FORUM_POST_NUKE', [$postInfo['post_id']]);
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
break;
case 'restore':
if(!perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST)) {
echo render_error(403);
break;
}
if($postRequestVerified && !$submissionConfirmed) {
url_redirect('forum-post', [
'post' => $postInfo['post_id'],
'post_fragment' => 'p' . $postInfo['post_id'],
]);
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']),
'params' => [
'p' => $postInfo['post_id'],
'm' => 'restore',
],
]);
break;
}
$restorePost = forum_post_restore($postInfo['post_id']);
if(!$restorePost) {
echo render_error(500);
break;
}
$msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo['post_id']]);
url_redirect('forum-topic', ['topic' => $postInfo['topic_id']]);
break;
default: // function as an alt for topic.php?p= by default
$canDeleteAny = perms_check($perms, MSZ_FORUM_PERM_DELETE_ANY_POST);
if(!empty($postInfo['post_deleted']) && !$canDeleteAny) {
echo render_error(404);
break;
}
$postFind = forum_post_find($postInfo['post_id'], $currentUserId);
if(empty($postFind)) {
echo render_error(404);
break;
}
if($canDeleteAny) {
$postInfo['preceeding_post_count'] += $postInfo['preceeding_post_deleted_count'];
}
unset($postInfo['preceeding_post_deleted_count']);
url_redirect('forum-topic', [
'topic' => $postFind['topic_id'],
'page' => floor($postFind['preceeding_post_count'] / MSZ_FORUM_POSTS_PER_PAGE) + 1,
]);
break;
}

View file

@ -1,276 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Parsers\Parser;
use Misuzu\Users\User;
require_once '../../misuzu.php';
$currentUser = User::getCurrent();
if($currentUser === null) {
echo render_error(401);
return;
}
$currentUserId = $currentUser->getId();
if($currentUser->hasActiveWarning()) {
echo render_error(403);
return;
}
$forumPostingModes = [
'create', 'edit', 'quote', 'preview',
];
if(!empty($_POST)) {
$mode = !empty($_POST['post']['mode']) && is_string($_POST['post']['mode']) ? $_POST['post']['mode'] : 'create';
$postId = !empty($_POST['post']['id']) && is_string($_POST['post']['id']) ? (int)$_POST['post']['id'] : 0;
$topicId = !empty($_POST['post']['topic']) && is_string($_POST['post']['topic']) ? (int)$_POST['post']['topic'] : 0;
$forumId = !empty($_POST['post']['forum']) && is_string($_POST['post']['forum']) ? (int)$_POST['post']['forum'] : 0;
} else {
$mode = !empty($_GET['m']) && is_string($_GET['m']) ? $_GET['m'] : 'create';
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
$forumId = !empty($_GET['f']) && is_string($_GET['f']) ? (int)$_GET['f'] : 0;
}
if(!in_array($mode, $forumPostingModes, true)) {
echo render_error(400);
return;
}
if($mode === 'preview') {
header('Content-Type: text/plain; charset=utf-8');
$postText = (string)($_POST['post']['text']);
$postParser = (int)($_POST['post']['parser']);
if(!Parser::isValid($postParser)) {
http_response_code(400);
return;
}
http_response_code(200);
echo Parser::instance($postParser)->parseText(htmlspecialchars($postText));
return;
}
if(empty($postId) && empty($topicId) && empty($forumId)) {
echo render_error(404);
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)) {
echo render_error(404);
return;
}
$perms = forum_perms_get_user($forum['forum_id'], $currentUserId)[MSZ_FORUM_PERMS_GENERAL];
if($forum['forum_archived']
|| (!empty($topic['topic_locked']) && !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))) {
echo render_error(403);
return;
}
if(!forum_may_have_topics($forum['forum_type'])) {
echo render_error(400);
return;
}
$topicTypes = [];
if($mode === 'create' || $mode === 'edit') {
$topicTypes[MSZ_TOPIC_TYPE_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';
}
}
// edit mode stuff
if($mode === 'edit') {
if(empty($post)) {
echo render_error(404);
return;
}
if(!perms_check($perms, $post['poster_id'] === $currentUserId ? MSZ_FORUM_PERM_EDIT_POST : MSZ_FORUM_PERM_EDIT_ANY_POST)) {
echo render_error(403);
return;
}
}
$notices = [];
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;
$postSignature = isset($_POST['post']['signature']);
if(!CSRF::validateRequest()) {
$notices[] = 'Could not verify request.';
} else {
$isEditingTopic = empty($topic) || ($mode === 'edit' && $post['is_opening_post']);
if($mode === 'create') {
$timeoutCheck = max(1, forum_timeout($forumId, $currentUserId));
if($timeoutCheck < 5) {
$notices[] = sprintf("You're posting too quickly! Please wait %s seconds before posting again.", number_format($timeoutCheck));
$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;
$topicTitleChanged = $topicTitle !== $originalTopicTitle;
$originalTopicType = (int)($topic['topic_type'] ?? MSZ_TOPIC_TYPE_DISCUSSION);
$topicTypeChanged = $topicType !== null && $topicType !== $originalTopicType;
switch(forum_validate_title($topicTitle)) {
case 'too-short':
$notices[] = 'Topic title was too short.';
break;
case 'too-long':
$notices[] = 'Topic title was too long.';
break;
}
if($mode === 'create' && $topicType === null) {
$topicType = array_key_first($topicTypes);
} elseif(!array_key_exists($topicType, $topicTypes) && $topicTypeChanged) {
$notices[] = 'You are not allowed to set this topic type.';
}
}
if(!Parser::isValid($postParser)) {
$notices[] = 'Invalid parser selected.';
}
switch(forum_validate_post($postText)) {
case 'too-short':
$notices[] = 'Post content was too short.';
break;
case 'too-long':
$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,
$topicTitle,
$topicType
);
}
$postId = forum_post_create(
$topicId,
$forum['forum_id'],
$currentUserId,
$_SERVER['REMOTE_ADDR'],
$postText,
$postParser,
$postSignature
);
forum_topic_mark_read($currentUserId, $topicId, $forum['forum_id']);
forum_count_increase($forum['forum_id'], empty($topic));
break;
case 'edit':
$markUpdated = $post['poster_id'] === $currentUserId
&& $post['post_created_unix'] < strtotime('-1 minutes')
&& $postText !== $post['post_text'];
if(!forum_post_update($postId, $_SERVER['REMOTE_ADDR'], $postText, $postParser, $postSignature, $markUpdated)) {
$notices[] = 'Post edit failed.';
}
if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged)) {
if(!forum_topic_update($topicId, $topicTitle, $topicType)) {
$notices[] = 'Topic update failed.';
}
}
break;
}
if(empty($notices)) {
$redirect = url(empty($topic) ? 'forum-topic' : 'forum-post', [
'topic' => $topicId ?? 0,
'post' => $postId ?? 0,
'post_fragment' => 'p' . ($postId ?? 0),
]);
redirect($redirect);
return;
}
}
}
}
if(!empty($topic)) {
Template::set('posting_topic', $topic);
}
if($mode === 'edit') { // $post is pretty much sure to be populated at this point
Template::set('posting_post', $post);
}
$displayInfo = forum_posting_info($currentUserId);
Template::render('forum.posting', [
'posting_breadcrumbs' => forum_get_breadcrumbs($forumId),
'global_accent_colour' => forum_get_colour($forumId),
'posting_forum' => $forum,
'posting_info' => $displayInfo,
'posting_notices' => $notices,
'posting_mode' => $mode,
'posting_types' => $topicTypes,
'posting_defaults' => [
'title' => $topicTitle ?? null,
'type' => $topicType ?? null,
'text' => $postText ?? null,
'parser' => $postParser ?? null,
'signature' => $postSignature ?? null,
],
]);

View file

@ -1,339 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
require_once '../../misuzu.php';
$postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0;
$topicId = !empty($_GET['t']) && is_string($_GET['t']) ? (int)$_GET['t'] : 0;
$moderationMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
$submissionConfirmed = !empty($_GET['confirm']) && is_string($_GET['confirm']) && $_GET['confirm'] === '1';
$topicUser = User::getCurrent();
$topicUserId = $topicUser === null ? 0 : $topicUser->getId();
if($topicId < 1 && $postId > 0) {
$postInfo = forum_post_find($postId, $topicUserId);
if(!empty($postInfo['topic_id'])) {
$topicId = (int)$postInfo['topic_id'];
}
}
$topic = forum_topic_get($topicId, true);
$perms = $topic
? forum_perms_get_user($topic['forum_id'], $topicUserId)[MSZ_FORUM_PERMS_GENERAL]
: 0;
if(isset($topicUser) && $topicUser->hasActiveWarning())
$perms &= ~MSZ_FORUM_PERM_SET_WRITE;
$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);
Template::set('topic_redir_info', $topicRedirectInfo);
if($topicIsNuked || !$canDeleteAny) {
if(empty($topicRedirectInfo))
echo render_error(404);
else
header('Location: ' . $topicRedirectInfo->topic_redir_url);
return;
}
}
if(!perms_check($perms, MSZ_FORUM_PERM_VIEW_FORUM)) {
echo render_error(403);
return;
}
$topicIsLocked = !empty($topic['topic_locked']);
$topicIsArchived = !empty($topic['topic_archived']);
$topicPostsTotal = (int)($topic['topic_count_posts'] + $topic['topic_count_posts_deleted']);
$topicIsFrozen = $topicIsArchived || $topicIsDeleted;
$canDeleteOwn = !$topicIsFrozen && !$topicIsLocked && perms_check($perms, MSZ_FORUM_PERM_DELETE_POST);
$canBumpTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_BUMP_TOPIC);
$canLockTopic = !$topicIsFrozen && perms_check($perms, MSZ_FORUM_PERM_LOCK_TOPIC);
$canNukeOrRestore = $canDeleteAny && $topicIsDeleted;
$canDelete = !$topicIsDeleted && (
$canDeleteAny || (
$topicPostsTotal > 0
&& $topicPostsTotal <= MSZ_FORUM_TOPIC_DELETE_POST_LIMIT
&& $canDeleteOwn
&& $topic['author_user_id'] === $topicUserId
)
);
$validModerationModes = [
'delete', 'restore', 'nuke',
'bump', 'lock', 'unlock',
];
if(in_array($moderationMode, $validModerationModes, true)) {
if(!CSRF::validateRequest()) {
echo render_info("Couldn't verify this request, please refresh the page and try again.", 403);
return;
}
if(!UserSession::hasCurrent()) {
echo render_info('You must be logged in to manage posts.', 401);
return;
}
if($topicUser->isBanned()) {
echo render_info('You have been banned, check your profile for more information.', 403);
return;
}
if($topicUser->isSilenced()) {
echo render_info('You have been silenced, check your profile for more information.', 403);
return;
}
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($canDeleteCode !== MSZ_E_FORUM_TOPIC_DELETE_OK) {
echo render_info($canDeleteMsg, $responseCode);
break;
}
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']),
'params' => [
't' => $topic['topic_id'],
'm' => 'delete',
],
]);
break;
} elseif(!$submissionConfirmed) {
url_redirect(
'forum-topic',
['topic' => $topic['topic_id']]
);
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;
}
url_redirect('forum-category', [
'forum' => $topic['forum_id'],
]);
break;
case 'restore':
if(!$canNukeOrRestore) {
echo render_error(403);
break;
}
if(!isset($_GET['confirm'])) {
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']),
'params' => [
't' => $topic['topic_id'],
'm' => 'restore',
],
]);
break;
} elseif(!$submissionConfirmed) {
url_redirect('forum-topic', [
'topic' => $topic['topic_id'],
]);
break;
}
$restoreTopic = forum_topic_restore($topic['topic_id']);
if(!$restoreTopic) {
echo render_error(500);
break;
}
$msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topic['topic_id']]);
url_redirect('forum-category', [
'forum' => $topic['forum_id'],
]);
break;
case 'nuke':
if(!$canNukeOrRestore) {
echo render_error(403);
break;
}
if(!isset($_GET['confirm'])) {
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']),
'params' => [
't' => $topic['topic_id'],
'm' => 'nuke',
],
]);
break;
} elseif(!$submissionConfirmed) {
url_redirect('forum-topic', [
'topic' => $topic['topic_id'],
]);
break;
}
$nukeTopic = forum_topic_nuke($topic['topic_id']);
if(!$nukeTopic) {
echo render_error(500);
break;
}
$msz->createAuditLog('FORUM_TOPIC_NUKE', [$topic['topic_id']]);
url_redirect('forum-category', [
'forum' => $topic['forum_id'],
]);
break;
case 'bump':
if($canBumpTopic && forum_topic_bump($topic['topic_id'])) {
$msz->createAuditLog('FORUM_TOPIC_BUMP', [$topic['topic_id']]);
}
url_redirect('forum-topic', [
'topic' => $topic['topic_id'],
]);
break;
case 'lock':
if($canLockTopic && !$topicIsLocked && forum_topic_lock($topic['topic_id'])) {
$msz->createAuditLog('FORUM_TOPIC_LOCK', [$topic['topic_id']]);
}
url_redirect('forum-topic', [
'topic' => $topic['topic_id'],
]);
break;
case 'unlock':
if($canLockTopic && $topicIsLocked && forum_topic_unlock($topic['topic_id'])) {
$msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topic['topic_id']]);
}
url_redirect('forum-topic', [
'topic' => $topic['topic_id'],
]);
break;
}
return;
}
$topicPosts = $topic['topic_count_posts'];
if($canDeleteAny) {
$topicPosts += $topic['topic_count_posts_deleted'];
}
$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(!$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)
);
if(!$posts) {
echo render_error(404);
return;
}
$canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && perms_check($perms, MSZ_FORUM_PERM_CREATE_POST);
forum_topic_mark_read($topicUserId, $topic['topic_id'], $topic['forum_id']);
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_posts' => $posts,
'can_reply' => $canReply,
'topic_pagination' => $topicPagination,
'topic_can_delete' => $canDelete,
'topic_can_nuke_or_restore' => $canNukeOrRestore,
'topic_can_bump' => $canBumpTopic,
'topic_can_lock' => $canLockTopic,
]);

View file

@ -1,9 +1,207 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
require_once __DIR__ . '/../misuzu.php';
set_exception_handler(function(\Throwable $ex) {
http_response_code(500);
ob_clean();
if(MSZ_DEBUG) {
header('Content-Type: text/plain; charset=utf-8');
echo (string)$ex;
} else {
header('Content-Type: text/html; charset=utf-8');
echo file_get_contents(MSZ_TEMPLATES . '/500.html');
}
exit;
});
// The whole wall of shit before the router setup and dispatch should be worked away
// Lockdown things should be middleware when there's no more legacy files
$request = \Index\Http\HttpRequest::fromRequest();
$msz->setUpHttp(str_contains($request->getPath(), '.php'));
ob_start();
if(file_exists(MSZ_ROOT . '/.migrating')) {
http_response_code(503);
if(!isset($_GET['_check'])) {
header('Content-Type: text/html; charset=utf-8');
echo file_get_contents(MSZ_TEMPLATES . '/503.html');
}
exit;
}
if(!MSZ_DEBUG) {
$twigCacheDirSfx = GitInfo::hash(true);
if(empty($twigCacheDirSfx))
$twigCacheDirSfx = md5(MSZ_ROOT);
$twigCache = sys_get_temp_dir() . '/msz-tpl-' . $twigCacheDirSfx;
if(!is_dir($twigCache))
mkdir($twigCache, 0775, true);
}
$globals = $cfg->getValues([
['site.name:s', 'Misuzu'],
'site.desc:s',
'site.url:s',
'sockChat.chatPath.normal:s',
'eeprom.path:s',
'eeprom.app:s',
['auth.secret:s', 'meow'],
['csrf.secret:s', 'soup'],
'private.enabled:b',
]);
Template::init($msz, $twigCache ?? null, MSZ_DEBUG);
Template::set('globals', [
'site_name' => $globals['site.name'],
'site_description' => $globals['site.desc'],
'site_url' => $globals['site.url'],
'site_chat' => $globals['sockChat.chatPath.normal'],
'eeprom' => [
'path' => $globals['eeprom.path'],
'app' => $globals['eeprom.app'],
],
]);
$mszAssetsInfo = json_decode(file_get_contents(MSZ_ASSETS . '/current.json'));
if(!empty($mszAssetsInfo))
Template::set('assets', $mszAssetsInfo);
unset($mszAssetsInfo);
Template::addPath(MSZ_TEMPLATES);
AuthToken::setSecretKey($globals['auth.secret']);
if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) {
$authToken = new AuthToken;
$authToken->setUserId(filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT) ?? 0);
$authToken->setSessionToken(filter_input(INPUT_COOKIE, 'msz_sid') ?? '');
if($authToken->isValid())
$authToken->applyCookie(strtotime('1 year'));
AuthToken::nukeCookieLegacy();
}
if(!isset($authToken))
$authToken = AuthToken::unpack(filter_input(INPUT_COOKIE, 'msz_auth') ?? '');
if($authToken->isValid()) {
$authToken->setCurrent();
try {
$sessionInfo = UserSession::byToken($authToken->getSessionToken());
if($sessionInfo->hasExpired()) {
$sessionInfo->delete();
} elseif($sessionInfo->getUserId() === $authToken->getUserId()) {
$userInfo = $sessionInfo->getUser();
if(!$userInfo->isDeleted()) {
$sessionInfo->setCurrent();
$userInfo->setCurrent();
$sessionInfo->bump($_SERVER['REMOTE_ADDR']);
if($sessionInfo->shouldBumpExpire())
$authToken->applyCookie($sessionInfo->getExpiresTime());
// only allow impersonation when super user
if($authToken->hasImpersonatedUserId() && $userInfo->isSuper()) {
$userInfoReal = $userInfo;
try {
$userInfo = User::byId($authToken->getImpersonatedUserId());
} catch(UserNotFoundException $ex) {
$userInfo = $userInfoReal;
$authToken->removeImpersonatedUserId();
$authToken->applyCookie();
}
$userInfo->setCurrent();
}
}
}
} catch(UserNotFoundException $ex) {
UserSession::unsetCurrent();
User::unsetCurrent();
} catch(UserSessionNotFoundException $ex) {
UserSession::unsetCurrent();
User::unsetCurrent();
}
if(UserSession::hasCurrent()) {
$userInfo->bumpActivity($_SERVER['REMOTE_ADDR']);
} else
AuthToken::nukeCookie();
}
CSRF::init(
$globals['csrf.secret'],
(UserSession::hasCurrent() ? UserSession::getCurrent()->getToken() : ($_SERVER['REMOTE_ADDR'] ?? '::1'))
);
if($globals['private.enabled']) {
$onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login');
$onPasswordPage = parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH) === url('auth-forgot');
$misuzuBypassLockdown = !empty($misuzuBypassLockdown) || $onLoginPage;
if(!$misuzuBypassLockdown) {
if(UserSession::hasCurrent()) {
['private.perm.cat' => $privatePermCat, 'private.perm.val' => $privatePermVal] = $cfg->getValues(['private.perm.cat:s', 'private.perm.val:i']);
if(!empty($privatePermCat) && $privatePermVal > 0) {
if(!perms_check_user($privatePermCat, User::getCurrent()->getId(), $privatePermVal)) {
// au revoir
UserSession::unsetCurrent();
User::unsetCurrent();
}
}
} elseif(!$onLoginPage && !($onPasswordPage && $cfg->getBoolean('private.allow_password_reset', true))) {
url_redirect('auth-login');
exit;
}
}
}
if(!empty($userInfo))
Template::set('current_user', $userInfo);
if(!empty($userInfoReal))
Template::set('current_user_real', $userInfoReal);
$inManageMode = str_starts_with($_SERVER['REQUEST_URI'], '/manage');
$hasManageAccess = User::hasCurrent()
&& !User::getCurrent()->hasActiveWarning()
&& perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_CAN_MANAGE);
Template::set('has_manage_access', $hasManageAccess);
if($inManageMode) {
if(!$hasManageAccess) {
echo render_error(403);
exit;
}
Template::set('manage_menu', manage_get_menu(User::getCurrent()->getId()));
}
$mszRequestPath = $request->getPath();
$mszLegacyPathPrefix = MSZ_PUBLIC . '-legacy/';
$mszLegacyPath = realpath($mszLegacyPathPrefix . $mszRequestPath);
if(!empty($mszLegacyPath) && str_starts_with($mszLegacyPath, $mszLegacyPathPrefix)) {
if(is_dir($mszLegacyPath))
$mszLegacyPath .= '/index.php';
if(is_file($mszLegacyPath)) {
require_once $mszLegacyPath;
return;
}
}
$msz->setUpHttp(str_contains($mszRequestPath, '.php'));
$msz->dispatchHttp($request);

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';

View file

@ -1,121 +0,0 @@
<?php
namespace Misuzu;
use DateTimeInterface;
use RuntimeException;
use Index\DateTime;
use Misuzu\Changelog\Changelog;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) {
echo render_error(403);
return;
}
$changeActions = [];
foreach(Changelog::ACTIONS as $action)
$changeActions[$action] = Changelog::actionText($action);
$changelog = $msz->getChangelog();
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$loadChangeInfo = fn() => $changelog->getChangeById($changeId, withTags: true);
$changeTags = $changelog->getAllTags();
if(empty($changeId))
$isNew = true;
else
try {
$isNew = false;
$changeInfo = $loadChangeInfo();
} catch(RuntimeException $ex) {
echo render_error(404);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(CSRF::validateRequest()) {
$changelog->deleteChange($changeInfo);
$msz->createAuditLog('CHANGELOG_ENTRY_DELETE', [$changeInfo->getId()]);
url_redirect('manage-changelog-changes');
} else render_error(403);
return;
}
// make errors not echos lol
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$action = trim((string)filter_input(INPUT_POST, 'cl_action'));
$summary = trim((string)filter_input(INPUT_POST, 'cl_summary'));
$body = trim((string)filter_input(INPUT_POST, 'cl_body'));
$userId = (int)filter_input(INPUT_POST, 'cl_user', FILTER_SANITIZE_NUMBER_INT);
$createdAt = trim((string)filter_input(INPUT_POST, 'cl_created'));
$tags = filter_input(INPUT_POST, 'cl_tags', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
if($userId < 1) $userId = null;
else $userId = (string)$userId;
if(empty($createdAt))
$createdAt = null;
else {
$createdAt = DateTime::createFromFormat(DateTimeInterface::ATOM, $createdAt . ':00Z');
if($createdAt->getUnixTimeSeconds() < 0)
$createdAt = null;
}
if($isNew) {
$changeInfo = $changelog->createChange($action, $summary, $body, $userId, $createdAt);
} else {
if($action === $changeInfo->getAction())
$action = null;
if($summary === $changeInfo->getSummary())
$summary = null;
if($body === $changeInfo->getBody())
$body = null;
if($createdAt !== null && $createdAt->equals($changeInfo->getCreatedAt()))
$createdAt = null;
$updateUserInfo = $userId !== $changeInfo->getUserId();
if($action !== null || $summary !== null || $body !== null || $createdAt !== null || $updateUserInfo)
$changelog->updateChange($changeInfo, $action, $summary, $body, $updateUserInfo, $userId, $createdAt);
}
if(!empty($tags)) {
$tCurrent = $changeInfo->getTagIds();
$tApply = $tags;
$tRemove = [];
foreach($tCurrent as $tag)
if(!in_array($tag, $tApply)) {
$tRemove[] = $tag;
$changelog->removeTagFromChange($changeInfo, $tag);
}
$tCurrent = array_diff($tCurrent, $tRemove);
foreach($tApply as $tag)
if(!in_array($tag, $tCurrent)) {
$changelog->addTagToChange($changeInfo, $tag);
$tCurrent[] = $tag;
}
}
$msz->createAuditLog(
$isNew ? 'CHANGELOG_ENTRY_CREATE' : 'CHANGELOG_ENTRY_EDIT',
[$changeInfo->getId()]
);
if($isNew) {
url_redirect('manage-changelog-change', ['change' => $changeInfo->getId()]);
return;
} else $changeInfo = $loadChangeInfo();
break;
}
Template::render('manage.changelog.change', [
'change_new' => $isNew,
'change_info' => $changeInfo ?? null,
'change_tags' => $changeTags,
'change_actions' => $changeActions,
]);

View file

@ -1,49 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) {
echo render_error(403);
return;
}
$changelog = $msz->getChangelog();
$changelogPagination = new Pagination($changelog->countAllChanges(), 30);
if(!$changelogPagination->hasValidOffset()) {
echo render_error(404);
return;
}
$changeInfos = $changelog->getAllChanges(withTags: true, pagination: $changelogPagination);
$changes = [];
$userInfos = [];
foreach($changeInfos as $changeInfo) {
$userId = $changeInfo->getUserId();
if(array_key_exists($userId, $userInfos)) {
$userInfo = $userInfos[$userId];
} else {
try {
$userInfo = User::byId($userId);
} catch(UserNotFoundException $ex) {
$userInfo = null;
}
$userInfos[$userId] = $userInfo;
}
$changes[] = [
'change' => $changeInfo,
'user' => $userInfo,
];
}
Template::render('manage.changelog.changes', [
'changelog_changes' => $changes,
'changelog_pagination' => $changelogPagination,
]);

View file

@ -1,72 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) {
echo render_error(403);
return;
}
$changelog = $msz->getChangelog();
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
$loadTagInfo = fn() => $changelog->getTagById($tagId);
if(empty($tagId))
$isNew = true;
else
try {
$isNew = false;
$tagInfo = $loadTagInfo();
} catch(RuntimeException $ex) {
echo render_error(404);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(CSRF::validateRequest()) {
$changelog->deleteTag($tagInfo);
$msz->createAuditLog('CHANGELOG_TAG_DELETE', [$tagInfo->getId()]);
url_redirect('manage-changelog-tags');
} else render_error(403);
return;
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$name = trim((string)filter_input(INPUT_POST, 'ct_name'));
$description = trim((string)filter_input(INPUT_POST, 'ct_desc'));
$archive = !empty($_POST['ct_archive']);
if($isNew) {
$tagInfo = $changelog->createTag($name, $description, $archive);
} else {
if($name === $tagInfo->getName())
$name = null;
if($description === $tagInfo->getDescription())
$description = null;
if($archive === $tagInfo->isArchived())
$archive = null;
if($name !== null || $description !== null || $archive !== null)
$changelog->updateTag($tagInfo, $name, $description, $archive);
}
$msz->createAuditLog(
$isNew ? 'CHANGELOG_TAG_CREATE' : 'CHANGELOG_TAG_EDIT',
[$tagInfo->getId()]
);
if($isNew) {
url_redirect('manage-changelog-tag', ['tag' => $tagInfo->getId()]);
return;
} else $tagInfo = $loadTagInfo();
break;
}
Template::render('manage.changelog.tag', [
'tag_new' => $isNew,
'tag_info' => $tagInfo ?? null,
]);

View file

@ -1,15 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_CHANGELOG, User::getCurrent()->getId(), MSZ_PERM_CHANGELOG_MANAGE_TAGS)) {
echo render_error(403);
return;
}
Template::render('manage.changelog.tags', [
'changelog_tags' => $msz->getChangelog()->getAllTags(),
]);

View file

@ -1,26 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->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'));

View file

@ -1,23 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_FORUM_MANAGE_FORUMS)) {
echo render_error(403);
return;
}
$forums = DB::query('SELECT * FROM `msz_forum_categories`')->fetchAll();
$rawPerms = perms_create(MSZ_FORUM_PERM_MODES);
$perms = manage_forum_perms_list($rawPerms);
if(!empty($_POST['perms']) && is_array($_POST['perms'])) {
$finalPerms = manage_perms_apply($perms, $_POST['perms'], $rawPerms);
$perms = manage_forum_perms_list($finalPerms);
Template::set('calculated_perms', $finalPerms);
}
Template::render('manage.forum.listing', compact('forums', 'perms'));

View file

@ -1,51 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_FORUM_TOPIC_REDIRS)) {
echo render_error(403);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest())
throw new \Exception("Request verification failed.");
$rTopicId = (int)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, User::getCurrent()->getId(), $rTopicURL);
url_redirect('manage-forum-topic-redirs');
return;
}
if(filter_input(INPUT_GET, 'm') === 'explode') {
if(!CSRF::validateRequest())
throw new \Exception("Request verification failed.");
$rTopicId = (int)filter_input(INPUT_GET, 't');
$msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]);
forum_topic_redir_remove($rTopicId);
url_redirect('manage-forum-topic-redirs');
return;
}
$pagination = new Pagination(forum_topic_redir_count(), 20);
if(!$pagination->hasValidOffset()) {
echo render_error(404);
return;
}
$redirs = forum_topic_redir_all($pagination->getOffset(), $pagination->getRange());
Template::render('manage.forum.redirs', [
'manage_redirs' => $redirs,
'manage_redirs_pagination' => $pagination,
]);

View file

@ -1,113 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) {
echo render_error(403);
return;
}
$emotes = $msz->getEmotes();
$emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT);
$loadEmoteInfo = fn() => $emotes->getEmoteById($emoteId, true);
if(empty($emoteId))
$isNew = true;
else
try {
$isNew = false;
$emoteInfo = $loadEmoteInfo();
} catch(RuntimeException $ex) {
echo render_error(404);
return;
}
// make errors not echos lol
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$order = (int)filter_input(INPUT_POST, 'em_order', FILTER_SANITIZE_NUMBER_INT);
$minRank = (int)filter_input(INPUT_POST, 'em_minrank', FILTER_SANITIZE_NUMBER_INT);
$url = trim((string)filter_input(INPUT_POST, 'em_url'));
$strings = explode(' ', trim((string)filter_input(INPUT_POST, 'em_strings')));
if($isNew || $url !== $emoteInfo->getUrl()) {
$checkUrl = $emotes->checkEmoteUrl($url);
if($checkUrl !== '') {
echo match($checkUrl) {
'empty' => 'URL may not be empty.',
'spaces' => 'URL may not end or start with spaces.',
'used' => 'This URL already belongs to another emoticon.',
default => 'URL cannot be accepted: ' . $checkUrl,
};
break;
}
}
if($order == 0)
$order = null;
if($isNew) {
$emoteInfo = $emotes->createEmote($url, $minRank, $order);
} else {
if($order === $emoteInfo->getOrder())
$order = null;
if($minRank === $emoteInfo->getMinRank())
$minRank = null;
if($url === $emoteInfo->getUrl())
$url = null;
if($order !== null || $minRank !== null || $url !== null)
$emotes->updateEmote($emoteInfo, $order, $minRank, $url);
}
$sCurrent = $emoteInfo->getStringsRaw();
$sApply = $strings;
$sRemove = [];
foreach($sCurrent as $string)
if(!in_array($string, $sApply)) {
$sRemove[] = $string;
$emotes->removeEmoteString($string);
}
$sCurrent = array_diff($sCurrent, $sRemove);
foreach($sApply as $string)
if(!in_array($string, $sCurrent)) {
$checkString = $emotes->checkEmoteString($string);
if($checkString === '') {
$emotes->addEmoteString($emoteInfo, $string);
} else {
echo match($checkString) {
'empty' => 'String may not be empty.',
'spaces' => 'String may not end or start with spaces.',
'case' => 'String must be lowercase.',
'format' => 'String must follow proper formatting.',
'used' => 'This string has already been used for another emoticon.',
default => 'String cannot be accepted: ' . $checkString,
};
break;
}
$sCurrent[] = $string;
}
$msz->createAuditLog(
$isNew ? 'EMOTICON_CREATE' : 'EMOTICON_EDIT',
[$emoteInfo->getId()]
);
if($isNew) {
url_redirect('manage-general-emoticon', ['emote' => $emoteInfo->getId()]);
return;
} else $emoteInfo = $loadEmoteInfo();
break;
}
Template::render('manage.general.emoticon', [
'emote_new' => $isNew,
'emote_info' => $emoteInfo ?? null,
]);

View file

@ -1,52 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) {
echo render_error(403);
return;
}
$emotes = $msz->getEmotes();
if(CSRF::validateRequest() && !empty($_GET['emote'])) {
$emoteId = (string)filter_input(INPUT_GET, 'emote', FILTER_SANITIZE_NUMBER_INT);
try {
$emoteInfo = $emotes->getEmoteById($emoteId);
} catch(RuntimeException $ex) {
echo render_error(404);
return;
}
if(!empty($_GET['delete'])) {
$emotes->deleteEmote($emoteInfo);
$msz->createAuditLog('EMOTICON_DELETE', [$emoteInfo->getId()]);
} else {
if(isset($_GET['order'])) {
$order = filter_input(INPUT_GET, 'order');
$offset = $order === 'i' ? 1 : ($order === 'd' ? -1 : 0);
$emotes->updateEmoteOrderOffset($emoteInfo, $offset);
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->getId()]);
}
if(isset($_GET['alias'])) {
$alias = (string)filter_input(INPUT_GET, 'alias');
if($emotes->checkEmoteString($alias) === '') {
$emotes->addEmoteString($emoteInfo, $alias);
$msz->createAuditLog('EMOTICON_ALIAS', [$emoteInfo->getId(), $alias]);
}
}
}
url_redirect('manage-general-emoticons');
return;
}
Template::render('manage.general.emoticons', [
'emotes' => $emotes->getAllEmotes(),
]);

View file

@ -1,168 +0,0 @@
<?php
namespace Misuzu;
require_once '../../../misuzu.php';
$statistics = DB::query('
SELECT
(
SELECT COUNT(`user_id`)
FROM `msz_users`
) AS `stat_users_total`,
(
SELECT COUNT(`user_id`)
FROM `msz_users`
WHERE `user_deleted` IS NOT NULL
) AS `stat_users_deleted`,
(
SELECT COUNT(`user_id`)
FROM `msz_users`
WHERE `user_active` IS NOT NULL
AND `user_deleted` IS NULL
) AS `stat_users_active`,
(
SELECT COUNT(*)
FROM `msz_audit_log`
) AS `stat_audit_logs`,
(
SELECT COUNT(`change_id`)
FROM `msz_changelog_changes`
) AS `stat_changelog_entries`,
(
SELECT COUNT(`category_id`)
FROM `msz_comments_categories`
) AS `stat_comment_categories_total`,
(
SELECT COUNT(`category_id`)
FROM `msz_comments_categories`
WHERE `category_locked` IS NOT NULL
) AS `stat_comment_categories_locked`,
(
SELECT COUNT(`comment_id`)
FROM `msz_comments_posts`
) AS `stat_comment_posts_total`,
(
SELECT COUNT(`comment_id`)
FROM `msz_comments_posts`
WHERE `comment_deleted` IS NOT NULL
) AS `stat_comment_posts_deleted`,
(
SELECT COUNT(`comment_id`)
FROM `msz_comments_posts`
WHERE `comment_reply_to` IS NOT NULL
) AS `stat_comment_posts_replies`,
(
SELECT COUNT(`comment_id`)
FROM `msz_comments_posts`
WHERE `comment_pinned` IS NOT NULL
) AS `stat_comment_posts_pinned`,
(
SELECT COUNT(`comment_id`)
FROM `msz_comments_posts`
WHERE `comment_edited` IS NOT NULL
) AS `stat_comment_posts_edited`,
(
SELECT COUNT(`user_id`)
FROM `msz_comments_votes`
WHERE `comment_vote` > 0
) AS `stat_comment_likes`,
(
SELECT COUNT(`user_id`)
FROM `msz_comments_votes`
WHERE `comment_vote` < 0
) AS `stat_comment_dislikes`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
) AS `stat_forum_posts_total`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `post_deleted` IS NOT NULL
) AS `stat_forum_posts_deleted`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `post_edited` IS NOT NULL
) AS `stat_forum_posts_edited`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `post_parse` = 0
) AS `stat_forum_posts_plain`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `post_parse` = 1
) AS `stat_forum_posts_bbcode`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `post_parse` = 2
) AS `stat_forum_posts_markdown`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `post_display_signature` != 0
) AS `stat_forum_posts_signature`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
) AS `stat_forum_topics_total`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `topic_type` = 0
) AS `stat_forum_topics_normal`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `topic_type` = 1
) AS `stat_forum_topics_pinned`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `topic_type` = 2
) AS `stat_forum_topics_announce`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `topic_type` = 3
) AS `stat_forum_topics_global_announce`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `topic_deleted` IS NOT NULL
) AS `stat_forum_topics_deleted`,
(
SELECT COUNT(`topic_id`)
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(`session_id`)
FROM `msz_sessions`
) AS `stat_user_sessions`,
(
SELECT COUNT(`user_id`)
FROM `msz_users_password_resets`
) AS `stat_user_password_resets`,
(
SELECT COUNT(`warning_id`)
FROM `msz_user_warnings`
WHERE `warning_type` != 0
) AS `stat_user_warnings`
')->fetch();
Template::render('manage.general.overview', [
'statistics' => $statistics,
]);

View file

@ -1,35 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Pagination;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_VIEW_LOGS)) {
echo render_error(403);
return;
}
$auditLog = $msz->getAuditLog();
$pagination = new Pagination($auditLog->countLogs(), 50);
if(!$pagination->hasValidOffset()) {
echo render_error(404);
return;
}
$logs = $auditLog->getLogs(pagination: $pagination);
$userInfos = [];
foreach($logs as $log)
if($log->hasUserId()) {
$userId = $log->getUserId();
if(!array_key_exists($userId, $userInfos))
$userInfos[$userId] = User::byId($userId);
}
Template::render('manage.general.logs', [
'global_logs' => $logs,
'global_logs_pagination' => $pagination,
'global_logs_users' => $userInfos,
]);

View file

@ -1,32 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Config\CfgTools;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent()
|| !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) {
echo render_error(403);
return;
}
$valueName = (string)filter_input(INPUT_GET, 'name');
$valueInfo = $cfg->getValueInfo($valueName);
if($valueInfo === null) {
echo render_error(404);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$valueName = $valueInfo->getName();
$msz->createAuditLog('CONFIG_DELETE', [$valueName]);
$cfg->removeValues($valueName);
url_redirect('manage-general-settings');
return;
}
Template::render('manage.general.setting-delete', [
'config_value' => $valueInfo,
]);

View file

@ -1,95 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Config\DbConfig;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent()
|| !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) {
echo render_error(403);
return;
}
$isNew = true;
$sName = (string)filter_input(INPUT_GET, 'name');
$sType = (string)filter_input(INPUT_GET, 'type');
$sValue = null;
$loadValueInfo = fn() => $cfg->getValueInfo($sName);
if(!empty($sName)) {
$sInfo = $loadValueInfo();
if($sInfo !== null) {
$isNew = false;
$sName = $sInfo->getName();
$sType = $sInfo->getType();
$sValue = $sInfo->getValue();
}
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
if($isNew) {
$sName = trim((string)filter_input(INPUT_POST, 'conf_name'));
if(!DbConfig::validateName($sName)) {
echo 'Name contains invalid characters.';
break;
}
$sType = trim((string)filter_input(INPUT_POST, 'conf_type'));
if(!in_array($sType, ['string', 'int', 'float', 'bool', 'array'])) {
echo 'Invalid type specified.';
break;
}
}
if($sType === 'array') {
$applyFunc = $cfg->setArray(...);
$sValue = [];
$sRaw = filter_input(INPUT_POST, 'conf_value', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
foreach($sRaw as $rValue) {
if(strpos($rValue, ':') === 1) {
$rType = $rValue[0];
$rValue = substr($rValue, 2);
$sValue[] = match($rType) {
's' => $rValue,
'i' => (int)$rValue,
'd' => (float)$rValue,
'f' => (float)$rValue,
'b' => $rValue !== 'false' && $rValue !== '0' && $rValue !== '',
default => '',
};
} else
$sValue[] = $rValue;
}
} elseif($sType === 'bool') {
$sValue = !empty($_POST['conf_value']);
$applyFunc = $cfg->setBoolean(...);
} else {
$sValue = filter_input(INPUT_POST, 'conf_value');
if($sType === 'int') {
$applyFunc = $cfg->setInteger(...);
$sValue = (int)$sValue;
} elseif($sType === 'float') {
$applyFunc = $cfg->setFloat(...);
$sValue = (float)$sValue;
} else
$applyFunc = $cfg->setString(...);
}
$msz->createAuditLog($isNew ? 'CONFIG_CREATE' : 'CONFIG_UPDATE', [$sName]);
$applyFunc($sName, $sValue);
url_redirect('manage-general-settings');
return;
}
if($sType === 'array' && !empty($sValue))
foreach($sValue as $key => $value)
$sValue[$key] = gettype($value)[0] . ':' . $value;
Template::render('manage.general.setting', [
'config_new' => $isNew,
'config_name' => $sName,
'config_type' => $sType,
'config_value' => $sValue,
]);

View file

@ -1,20 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent()
|| !perms_check_user(MSZ_PERMS_GENERAL, User::getCurrent()->getId(), MSZ_PERM_GENERAL_MANAGE_CONFIG)) {
echo render_error(403);
return;
}
$hidden = $cfg->getArray('settings.hidden');
$vars = $cfg->getAllValueInfos();
Template::render('manage.general.settings', [
'config_vars' => $vars,
'config_hidden' => $hidden,
]);

View file

@ -1,6 +0,0 @@
<?php
namespace Misuzu;
require_once '../../misuzu.php';
url_redirect('manage-general-overview');

View file

@ -1,26 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) {
echo render_error(403);
return;
}
$news = $msz->getNews();
$pagination = new Pagination($news->countAllCategories(true), 15);
if(!$pagination->hasValidOffset()) {
echo render_error(404);
return;
}
$categories = $news->getAllCategories(true, $pagination);
Template::render('manage.news.categories', [
'news_categories' => $categories,
'categories_pagination' => $pagination,
]);

View file

@ -1,72 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_CATEGORIES)) {
echo render_error(403);
return;
}
$news = $msz->getNews();
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$loadCategoryInfo = fn() => $news->getCategoryById($categoryId);
if(empty($categoryId))
$isNew = true;
else
try {
$isNew = false;
$categoryInfo = $loadCategoryInfo();
} catch(RuntimeException $ex) {
echo render_error(404);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(CSRF::validateRequest()) {
$news->deleteCategory($categoryInfo);
$msz->createAuditLog('NEWS_CATEGORY_DELETE', [$categoryInfo->getId()]);
url_redirect('manage-news-categories');
} else render_error(403);
return;
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$name = trim((string)filter_input(INPUT_POST, 'nc_name'));
$description = trim((string)filter_input(INPUT_POST, 'nc_desc'));
$hidden = !empty($_POST['nc_hidden']);
if($isNew) {
$categoryInfo = $news->createCategory($name, $description, $hidden);
} else {
if($name === $categoryInfo->getName())
$name = null;
if($description === $categoryInfo->getDescription())
$description = null;
if($hidden === $categoryInfo->isHidden())
$hidden = null;
if($name !== null || $description !== null || $hidden !== null)
$news->updateCategory($categoryInfo, $name, $description, $hidden);
}
$msz->createAuditLog(
$isNew ? 'NEWS_CATEGORY_CREATE' : 'NEWS_CATEGORY_EDIT',
[$categoryInfo->getId()]
);
if($isNew) {
url_redirect('manage-news-category', ['category' => $categoryInfo->getId()]);
return;
} else $categoryInfo = $loadCategoryInfo();
break;
}
Template::render('manage.news.category', [
'category_new' => $isNew,
'category_info' => $categoryInfo ?? null,
]);

View file

@ -1,6 +0,0 @@
<?php
namespace Misuzu;
require_once '../../../misuzu.php';
url_redirect('manage-news-categories');

View file

@ -1,84 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) {
echo render_error(403);
return;
}
$news = $msz->getNews();
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
$loadPostInfo = fn() => $news->getPostById($postId);
if(empty($postId))
$isNew = true;
else
try {
$isNew = false;
$postInfo = $loadPostInfo();
} catch(RuntimeException $ex) {
echo render_error(404);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
if(CSRF::validateRequest()) {
$news->deletePost($postInfo);
$msz->createAuditLog('NEWS_POST_DELETE', [$postInfo->getId()]);
url_redirect('manage-news-posts');
} else render_error(403);
return;
}
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$title = trim((string)filter_input(INPUT_POST, 'np_title'));
$category = (string)filter_input(INPUT_POST, 'np_category', FILTER_SANITIZE_NUMBER_INT);
$featured = !empty($_POST['np_featured']);
$body = trim((string)filter_input(INPUT_POST, 'np_body'));
if($isNew) {
$postInfo = $news->createPost($category, $title, $body, $featured, User::getCurrent());
} else {
if($category === $postInfo->getCategoryId())
$category = null;
if($title === $postInfo->getTitle())
$title = null;
if($body === $postInfo->getBody())
$body = null;
if($featured === $postInfo->isFeatured())
$featured = null;
if($category !== null || $title !== null || $body !== null || $featured !== null)
$news->updatePost($postInfo, $category, $title, $body, $featured);
}
$msz->createAuditLog(
$isNew ? 'NEWS_POST_CREATE' : 'NEWS_POST_EDIT',
[$postInfo->getId()]
);
if($isNew) {
if($postInfo->isFeatured()) {
// Twitter integration used to be here, replace with Railgun Pulse integration
}
url_redirect('manage-news-post', ['post' => $postInfo->getId()]);
return;
} else $postInfo = $loadPostInfo();
break;
}
$categories = [];
foreach($news->getAllCategories(true) as $categoryInfo)
$categories[$categoryInfo->getId()] = $categoryInfo->getName();
Template::render('manage.news.post', [
'categories' => $categories,
'post_new' => $isNew,
'post_info' => $postInfo ?? null,
]);

View file

@ -1,33 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->getId(), MSZ_PERM_NEWS_MANAGE_POSTS)) {
echo render_error(403);
return;
}
$news = $msz->getNews();
$pagination = new Pagination($news->countAllPosts(
includeScheduled: true,
includeDeleted: true
), 15);
if(!$pagination->hasValidOffset()) {
echo render_error(404);
return;
}
$posts = $news->getAllPosts(
includeScheduled: true,
includeDeleted: true,
pagination: $pagination
);
Template::render('manage.news.posts', [
'news_posts' => $posts,
'posts_pagination' => $pagination,
]);

View file

@ -1,23 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS)) {
echo render_error(403);
return;
}
$pagination = new Pagination(User::countAll(true), 30);
if(!$pagination->hasValidOffset()) {
echo render_error(404);
return;
}
Template::render('manage.users.users', [
'manage_users' => User::all(true, $pagination),
'manage_users_pagination' => $pagination,
]);

View file

@ -1,136 +0,0 @@
<?php
namespace Misuzu;
use Index\Colour\Colour;
use Index\Colour\ColourRGB;
use Misuzu\Users\User;
use Misuzu\Users\UserRole;
use Misuzu\Users\UserRoleNotFoundException;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
echo render_error(403);
return;
}
$roleId = (int)filter_input(INPUT_GET, 'r', FILTER_SANITIZE_NUMBER_INT);
if($roleId > 0)
try {
$roleInfo = UserRole::byId($roleId);
} catch(UserRoleNotFoundException $ex) {
echo render_error(404);
return;
}
$currentUser = User::getCurrent();
$currentUserId = $currentUser->getId();
$canEditPerms = perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS);
if($canEditPerms)
$permissions = manage_perms_list(perms_get_role_raw($roleId ?? 0));
if(!empty($_POST['role']) && is_array($_POST['role']) && CSRF::validateRequest()) {
$roleHierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
if(!$currentUser->isSuper() && (isset($roleInfo) ? $roleInfo->hasAuthorityOver($currentUser) : $currentUser->getRank() <= $roleHierarchy)) {
echo 'You don\'t hold authority over this role.';
return;
}
$roleName = $_POST['role']['name'] ?? '';
$roleNameLength = strlen($roleName);
if($roleNameLength < 1 || $roleNameLength > 255) {
echo 'invalid name length';
return;
}
$roleSecret = !empty($_POST['role']['secret']);
if($roleHierarchy < 1 || $roleHierarchy > 100) {
echo 'Invalid hierarchy value.';
return;
}
if(!empty($_POST['role']['colour']['inherit'])) {
$roleColour = \Index\Colour\Colour::none();
} else {
$roleColour = new ColourRGB(
(int)($_POST['role']['colour']['red'] ?? -1),
(int)($_POST['role']['colour']['green'] ?? -1),
(int)($_POST['role']['colour']['blue'] ?? -1)
);
}
$roleDescription = $_POST['role']['description'] ?? '';
$roleTitle = $_POST['role']['title'] ?? '';
if($roleDescription !== null) {
$rdLength = strlen($roleDescription);
if($rdLength > 1000) {
echo 'description is too long';
return;
}
}
if($roleTitle !== null) {
$rtLength = strlen($roleTitle);
if($rtLength > 64) {
echo 'title is too long';
return;
}
}
if(!isset($roleInfo))
$roleInfo = new UserRole;
$roleInfo->setName($roleName)
->setRank($roleHierarchy)
->setHidden($roleSecret)
->setColour($roleColour)
->setDescription($roleDescription)
->setTitle($roleTitle)
->save();
if(!empty($permissions) && !empty($_POST['perms']) && is_array($_POST['perms'])) {
$perms = manage_perms_apply($permissions, $_POST['perms']);
if($perms !== null) {
$permKeys = array_keys($perms);
$setPermissions = DB::prepare('
REPLACE INTO `msz_permissions`
(`role_id`, `user_id`, `' . implode('`, `', $permKeys) . '`)
VALUES
(:role_id, NULL, :' . implode(', :', $permKeys) . ')
');
$setPermissions->bind('role_id', $roleInfo->getId());
foreach($perms as $key => $value) {
$setPermissions->bind($key, $value);
}
$setPermissions->execute();
} else {
$deletePermissions = DB::prepare('
DELETE FROM `msz_permissions`
WHERE `role_id` = :role_id
AND `user_id` IS NULL
');
$deletePermissions->bind('role_id', $roleInfo->getId());
$deletePermissions->execute();
}
}
url_redirect('manage-role', ['role' => $roleInfo->getId()]);
return;
}
Template::render('manage.users.role', [
'role_info' => $roleInfo ?? null,
'can_manage_perms' => $canEditPerms,
'permissions' => $permissions ?? [],
]);

View file

@ -1,24 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserRole;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_ROLES)) {
echo render_error(403);
return;
}
$pagination = new Pagination(UserRole::countAll(true), 10);
if(!$pagination->hasValidOffset()) {
echo render_error(404);
return;
}
Template::render('manage.users.roles', [
'manage_roles' => UserRole::all(true, $pagination),
'manage_roles_pagination' => $pagination,
]);

View file

@ -1,210 +0,0 @@
<?php
namespace Misuzu;
use Index\Colour\Colour;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserRole;
use Misuzu\Users\UserRoleNotFoundException;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_USERS)) {
echo render_error(403);
return;
}
$notices = [];
$userId = (int)filter_input(INPUT_GET, 'u', FILTER_SANITIZE_NUMBER_INT);
$currentUser = User::getCurrent();
$currentUserId = $currentUser->getId();
try {
$userInfo = User::byId($userId);
} catch(UserNotFoundException $ex) {
echo render_error(404);
return;
}
$canEdit = $currentUser->hasAuthorityOver($userInfo);
$canEditPerms = $canEdit && perms_check_user(MSZ_PERMS_USER, $currentUserId, MSZ_PERM_USER_MANAGE_PERMS);
$permissions = manage_perms_list(perms_get_user_raw($userId));
if(CSRF::validateRequest() && $canEdit) {
if(!empty($_POST['impersonate_user'])) {
if(!$currentUser->isSuper()) {
$notices[] = 'You must be a super user to do this.';
} elseif(!is_string($_POST['impersonate_user']) || $_POST['impersonate_user'] !== 'meow') {
$notices[] = 'You didn\'t say the magic word.';
} else {
$authToken->setImpersonatedUserId($userInfo->getId());
$authToken->applyCookie();
url_redirect('index');
return;
}
}
if(!empty($_POST['send_test_email'])) {
if(!$currentUser->isSuper()) {
$notices[] = 'You must be a super user to do this.';
} elseif(!is_string($_POST['send_test_email']) || $_POST['send_test_email'] !== 'yes_send_it') {
$notices[] = 'Invalid request thing shut the fuck up.';
} else {
$testMail = Mailer::sendMessage(
[$userInfo->getEMailAddress() => $userInfo->getUsername()],
'Flashii Test E-mail',
'You were sent this e-mail to validate if you can receive e-mails from Flashii. You may discard it.'
);
if(!$testMail)
$notices[] = 'Failed to send test e-mail.';
}
}
if(!empty($_POST['roles']) && is_array($_POST['roles'])) {
// Read user input array and throw intval on em
$applyRoles = [];
foreach($_POST['roles'] as $item) {
if(!ctype_digit($item))
die('Invalid item encountered in roles list.');
$applyRoles[] = (int)$item;
}
// Fetch existing roles
$existingRoles = $userInfo->getRoles();
// Initialise set array with existing roles
$setRoles = $existingRoles;
// Storage array for roles to dump
$removeRoles = [];
// STEP 1: Check for roles to be removed in the existing set.
// Roles that the current users isn't allowed to touch (hierarchy) will stay.
foreach($setRoles as $role) {
// Also prevent the main role from being removed.
if($role->isDefault() || !$currentUser->hasAuthorityOver($role))
continue;
if(!in_array($role->getId(), $applyRoles))
$removeRoles[] = $role;
}
// STEP 2: Purge the ones marked for removal.
$setRoles = array_diff($setRoles, $removeRoles);
// STEP 3: Add roles to the set array from the user input, if the user has authority over the given roles.
foreach($applyRoles as $roleId) {
try {
$role = $existingRoles[$roleId] ?? UserRole::byId($roleId);
} catch(UserRoleNotFoundException $ex) {
continue;
}
if(!$currentUser->hasAuthorityOver($role))
continue;
if(!in_array($role, $setRoles))
$setRoles[] = $role;
}
foreach($removeRoles as $role)
$userInfo->removeRole($role);
foreach($setRoles as $role)
$userInfo->addRole($role);
}
if(!empty($_POST['user']) && is_array($_POST['user'])) {
//$setUsername = (string)($_POST['user']['username'] ?? '');
//$setEMailAddress = (string)($_POST['user']['email'] ?? '');
$setCountry = (string)($_POST['user']['country'] ?? '');
$setTitle = (string)($_POST['user']['title'] ?? '');
$displayRole = (int)($_POST['user']['display_role'] ?? 0);
try {
$userInfo->setDisplayRole(UserRole::byId($displayRole));
} catch(UserRoleNotFoundException $ex) {}
//$usernameValidation = User::validateUsername($setUsername);
//$emailValidation = User::validateEMailAddress($setEMailAddress);
$countryValidation = strlen($setCountry) === 2
&& ctype_alpha($setCountry)
&& ctype_upper($setCountry);
//if(!empty($usernameValidation))
// $notices[] = User::usernameValidationErrorString($usernameValidation);
// if(!empty($emailValidation)) {
// $notices[] = $emailValidation === 'in-use'
// ? 'This e-mail address has already been used!'
// : 'This e-mail address is invalid!';
// }
if(!$countryValidation)
$notices[] = 'Country code was invalid.';
if(strlen($setTitle) > 64)
$notices[] = 'User title was invalid.';
if(empty($notices))
$userInfo
// ->setUsername((string)($_POST['user']['username'] ?? ''))
// ->setEMailAddress((string)($_POST['user']['email'] ?? ''))
->setCountry((string)($_POST['user']['country'] ?? ''))
->setTitle((string)($_POST['user']['title'] ?? ''))
->setDisplayRole(UserRole::byId((int)($_POST['user']['display_role'] ?? 0)));
}
if(!empty($_POST['colour']) && is_array($_POST['colour'])) {
$setColour = null;
if(!empty($_POST['colour']['enable'])) {
$setColour = \Index\Colour\Colour::parse((string)($_POST['colour']['hex'] ?? ''));
if($setColour->shouldInherit())
$notices[] = 'Invalid colour specified.';
}
if(empty($notices))
$userInfo->setColour($setColour);
}
if(!empty($_POST['password']) && is_array($_POST['password'])) {
$passwordNewValue = (string)($_POST['password']['new'] ?? '');
$passwordConfirmValue = (string)($_POST['password']['confirm'] ?? '');
if(!empty($passwordNewValue)) {
if($passwordNewValue !== $passwordConfirmValue)
$notices[] = 'Confirm password does not match.';
elseif(!empty(User::validatePassword($passwordNewValue)))
$notices[] = 'New password is too weak.';
else
$userInfo->setPassword($passwordNewValue);
}
}
if(empty($notices))
$userInfo->save();
if($canEditPerms && !empty($_POST['perms']) && is_array($_POST['perms'])) {
$perms = manage_perms_apply($permissions, $_POST['perms']);
if($perms !== null) {
if(!perms_set_user_raw($userId, $perms))
$notices[] = 'Failed to update permissions.';
} else {
if(!perms_delete_user($userId))
$notices[] = 'Failed to remove permissions.';
}
// this smells, make it refresh/apply in a non-retarded way
$permissions = manage_perms_list(perms_get_user_raw($userId));
}
}
Template::render('manage.users.user', [
'user_info' => $userInfo,
'manage_notices' => $notices,
'manage_roles' => UserRole::all(true),
'can_edit_user' => $canEdit,
'can_edit_perms' => $canEdit && $canEditPerms,
'permissions' => $permissions ?? [],
]);

View file

@ -1,165 +0,0 @@
<?php
namespace Misuzu;
use InvalidArgumentException;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserWarning;
use Misuzu\Users\UserWarningNotFoundException;
use Misuzu\Users\UserWarningCreationFailedException;
require_once '../../../misuzu.php';
if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_USER, User::getCurrent()->getId(), MSZ_PERM_USER_MANAGE_WARNINGS)) {
echo render_error(403);
return;
}
$notices = [];
$currentUser = User::getCurrent();
$currentUserId = $currentUser->getId();
if(!empty($_POST['lookup']) && is_string($_POST['lookup'])) {
try {
$userId = User::byUsername((string)filter_input(INPUT_POST, 'lookup'))->getId();
} catch(UserNotFoundException $ex) {
$userId = 0;
}
url_redirect('manage-users-warnings', ['user' => $userId]);
return;
}
// instead of just kinda taking $_GET['w'] this should really fetch the info from the database
// and make sure that the user has authority
if(!empty($_GET['delete'])) {
try {
UserWarning::byId((int)filter_input(INPUT_GET, 'w', FILTER_SANITIZE_NUMBER_INT))->delete();
} catch(UserWarningNotFoundException $ex) {}
redirect($_SERVER['HTTP_REFERER'] ?? url('manage-users-warnings'));
return;
}
if(!empty($_POST['warning']) && is_array($_POST['warning'])) {
$warningType = (int)($_POST['warning']['type'] ?? 0);
$warningDuration = 0;
$warningDuration = (int)($_POST['warning']['duration'] ?? 0);
if($warningDuration < -1) {
$customDuration = $_POST['warning']['duration_custom'] ?? '';
if(!empty($customDuration)) {
switch($warningDuration) {
case -100: // YYYY-MM-DD
$splitDate = explode('-', $customDuration, 3);
if(count($splitDate) !== 3)
die('Invalid date specified.');
$wYear = (int)$splitDate[0];
$wMonth = (int)$splitDate[1];
$wDay = (int)$splitDate[2];
if(checkdate($wMonth, $wDay, $wYear))
$warningDuration = mktime(0, 0, 0, $wMonth, $wDay, $wYear) - time();
else
die('Invalid date specified.');
break;
case -200: // Raw seconds
$warningDuration = (int)$customDuration;
break;
case -300: // strtotime
$warningDuration = strtotime($customDuration) - time();
break;
}
}
}
try {
$warningsUserInfo = User::byId((int)($_POST['warning']['user'] ?? 0));
$warningsUser = $warningsUserInfo->getId();
if(!$currentUser->hasAuthorityOver($warningsUserInfo))
$notices[] = 'You do not have authority over this user.';
} catch(UserNotFoundException $ex) {
$notices[] = 'This user doesn\'t exist.';
}
if(empty($notices) && !empty($warningsUserInfo)) {
try {
$warningInfo = UserWarning::create(
$warningsUserInfo,
$currentUser,
$warningType,
$warningDuration,
$_POST['warning']['note'],
$_POST['warning']['private']
);
} catch(InvalidArgumentException $ex) {
$notices[] = $ex->getMessage();
} catch(UserWarningCreationFailedException $ex) {
$notices[] = 'Warning creation failed.';
}
}
}
if(empty($warningsUser))
$warningsUser = max(0, (int)($_GET['u'] ?? 0));
if(empty($warningsUserInfo))
try {
$warningsUserInfo = User::byId($warningsUser);
} catch(UserNotFoundException $ex) {
$warningsUserInfo = null;
}
$warningsPagination = new Pagination(UserWarning::countAll($warningsUserInfo), 10);
if(!$warningsPagination->hasValidOffset()) {
echo render_error(404);
return;
}
// calling array_flip since the input_select macro wants value => display, but this looks cuter
$warningDurations = array_flip([
'Pick a duration...' => 0,
'5 Minutes' => 60 * 5,
'15 Minutes' => 60 * 15,
'30 Minutes' => 60 * 30,
'45 Minutes' => 60 * 45,
'1 Hour' => 60 * 60,
'2 Hours' => 60 * 60 * 2,
'3 Hours' => 60 * 60 * 3,
'6 Hours' => 60 * 60 * 6,
'12 Hours' => 60 * 60 * 12,
'1 Day' => 60 * 60 * 24,
'2 Days' => 60 * 60 * 24 * 2,
'1 Week' => 60 * 60 * 24 * 7,
'2 Weeks' => 60 * 60 * 24 * 7 * 2,
'1 Month' => 60 * 60 * 24 * 365 / 12,
'3 Months' => 60 * 60 * 24 * 365 / 12 * 3,
'6 Months' => 60 * 60 * 24 * 365 / 12 * 6,
'9 Months' => 60 * 60 * 24 * 365 / 12 * 9,
'1 Year' => 60 * 60 * 24 * 365,
'Permanent' => -1,
'Until (YYYY-MM-DD) ->' => -100,
'Until (Seconds) ->' => -200,
'Until (strtotime) ->' => -300,
]);
Template::render('manage.users.warnings', [
'warnings' => [
'notices' => $notices,
'pagination' => $warningsPagination,
'list' => UserWarning::all($warningsUserInfo, $warningsPagination),
'user' => $warningsUserInfo,
'durations' => $warningDurations,
'types' => [
UserWarning::TYPE_NOTE => 'Note',
UserWarning::TYPE_WARN => 'Warning',
UserWarning::TYPE_MUTE => 'Silence',
UserWarning::TYPE_BAHN => 'Ban',
],
],
]);

View file

@ -1,139 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserRole;
use Misuzu\Users\UserRoleNotFoundException;
require_once '../misuzu.php';
$roleId = !empty($_GET['r']) && is_string($_GET['r']) ? (int)$_GET['r'] : UserRole::DEFAULT;
$orderBy = !empty($_GET['ss']) && is_string($_GET['ss']) ? mb_strtolower($_GET['ss']) : '';
$orderDir = !empty($_GET['sd']) && is_string($_GET['sd']) ? mb_strtolower($_GET['sd']) : '';
$orderDirs = [
'asc' => 'Ascending',
'desc' => 'Descending',
];
$defaultOrder = 'last-online';
$orderFields = [
'id' => [
'column' => 'u.`user_id`',
'default-dir' => 'asc',
'title' => 'User ID',
],
'name' => [
'column' => 'u.`username`',
'default-dir' => 'asc',
'title' => 'Username',
],
'country' => [
'column' => 'u.`user_country`',
'default-dir' => 'asc',
'title' => 'Country',
],
'registered' => [
'column' => 'u.`user_created`',
'default-dir' => 'desc',
'title' => 'Registration Date',
],
'last-online' => [
'column' => 'u.`user_active`',
'default-dir' => 'desc',
'title' => 'Last Online',
],
'forum-topics' => [
'column' => '`user_count_topics`',
'default-dir' => 'desc',
'title' => 'Forum Topics',
],
'forum-posts' => [
'column' => '`user_count_posts`',
'default-dir' => 'desc',
'title' => 'Forum Posts',
],
];
if(empty($orderBy)) {
$orderBy = $defaultOrder;
} elseif(!array_key_exists($orderBy, $orderFields)) {
echo render_error(400);
return;
}
if(empty($orderDir)) {
$orderDir = $orderFields[$orderBy]['default-dir'];
} elseif(!array_key_exists($orderDir, $orderDirs)) {
echo render_error(400);
return;
}
$canManageUsers = perms_check_user(MSZ_PERMS_USER, User::hasCurrent() ? User::getCurrent()->getId() : 0, MSZ_PERM_USER_MANAGE_USERS);
try {
$roleInfo = UserRole::byId($roleId);
} catch(UserRoleNotFoundException $ex) {
echo render_error(404);
return;
}
$pagination = new Pagination($roleInfo->getUserCount(), 15);
$roles = UserRole::all();
$getUsers = DB::prepare(sprintf(
'
SELECT
:current_user_id AS `current_user_id`,
u.`user_id`, u.`username`, u.`user_country`,
u.`user_created`, u.`user_active`, r.`role_id`,
COALESCE(u.`user_title`, r.`role_title`) AS `user_title`,
COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `user_id` = u.`user_id`
AND `topic_deleted` IS NULL
) AS `user_count_topics`,
(
SELECT COUNT(`post_Id`)
FROM `msz_forum_posts`
WHERE `user_id` = u.`user_id`
AND `post_deleted` IS NULL
) AS `user_count_posts`
FROM `msz_users` AS u
LEFT JOIN `msz_roles` AS r
ON r.`role_id` = u.`display_role`
LEFT JOIN `msz_user_roles` AS ur
ON ur.`user_id` = u.`user_id`
WHERE ur.`role_id` = :role_id
%1$s
ORDER BY %2$s %3$s
LIMIT %4$d, %5$d
',
$canManageUsers ? '' : 'AND u.`user_deleted` IS NULL',
$orderFields[$orderBy]['column'],
$orderDir,
$pagination->getOffset(),
$pagination->getRange()
));
$getUsers->bind('role_id', $roleInfo->getId());
$getUsers->bind('current_user_id', User::hasCurrent() ? User::getCurrent()->getId() : 0);
$users = $getUsers->fetchAll();
if(empty($users))
http_response_code(404);
Template::render('user.listing', [
'roles' => $roles,
'role' => $roleInfo,
'users' => $users,
'order_fields' => $orderFields,
'order_directions' => $orderDirs,
'order_field' => $orderBy,
'order_direction' => $orderDir,
'order_default' => $defaultOrder,
'can_manage_users' => $canManageUsers,
'users_pagination' => $pagination,
]);

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/../index.php';

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/../index.php';

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/../index.php';

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/../index.php';

View file

@ -1,386 +0,0 @@
<?php
namespace Misuzu;
use InvalidArgumentException;
use Index\ByteFormat;
use Misuzu\Parsers\Parser;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserSession;
use Misuzu\Users\Assets\UserBackgroundAsset;
use Misuzu\Users\Assets\UserImageAssetException;
use Misuzu\Users\Assets\UserImageAssetInvalidImageException;
use Misuzu\Users\Assets\UserImageAssetInvalidTypeException;
use Misuzu\Users\Assets\UserImageAssetInvalidDimensionsException;
use Misuzu\Users\Assets\UserImageAssetFileTooLargeException;
require_once '../misuzu.php';
$userId = !empty($_GET['u']) && is_string($_GET['u']) ? trim($_GET['u']) : 0;
$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);
try {
$profileUser = User::findForProfile($userId);
} catch(UserNotFoundException $ex) {
http_response_code(404);
Template::render('profile.index');
return;
}
if($profileUser->isDeleted()) {
http_response_code(404);
Template::render('profile.index');
return;
}
$notices = [];
$currentUser = User::getCurrent();
$viewingAsGuest = $currentUser === null;
$currentUserId = $viewingAsGuest ? 0 : $currentUser->getId();
$viewingOwnProfile = $currentUserId === $profileUser->getId();
$isBanned = $profileUser->hasActiveWarning();
$userPerms = perms_get_user($currentUserId)[MSZ_PERMS_USER];
$canManageWarnings = perms_check($userPerms, MSZ_PERM_USER_MANAGE_WARNINGS);
$canEdit = !$isBanned
&& UserSession::hasCurrent()
&& ($viewingOwnProfile || $currentUser->isSuper() || (
perms_check($userPerms, MSZ_PERM_USER_MANAGE_USERS)
&& $currentUser->hasAuthorityOver($profileUser)
));
if($isEditing) {
if(!$canEdit) {
echo render_error(403);
return;
}
$perms = perms_check_bulk($userPerms, [
'edit_profile' => MSZ_PERM_USER_EDIT_PROFILE,
'edit_avatar' => MSZ_PERM_USER_CHANGE_AVATAR,
'edit_background' => MSZ_PERM_USER_CHANGE_BACKGROUND,
'edit_about' => MSZ_PERM_USER_EDIT_ABOUT,
'edit_birthdate' => MSZ_PERM_USER_EDIT_BIRTHDATE,
'edit_signature' => MSZ_PERM_USER_EDIT_SIGNATURE,
]);
Template::set([
'perms' => $perms,
'background_attachments' => UserBackgroundAsset::getAttachmentStringOptions(),
]);
if(!empty($_POST) && is_array($_POST)) {
if(!CSRF::validateRequest()) {
$notices[] = 'Couldn\'t verify you, please refresh the page and retry.';
} else {
if(!empty($_POST['profile']) && is_array($_POST['profile'])) {
if(!$perms['edit_profile']) {
$notices[] = 'You\'re not allowed to edit your profile';
} else {
$profileFields = $profileUser->profileFields(false);
foreach($profileFields as $profileField) {
if(isset($_POST['profile'][$profileField->field_key])
&& $profileField->field_value !== $_POST['profile'][$profileField->field_key]
&& !$profileField->setFieldValue($_POST['profile'][$profileField->field_key])) {
$notices[] = sprintf('%s was formatted incorrectly!', $profileField->field_title);
}
}
}
}
if(!empty($_POST['about']) && is_array($_POST['about'])) {
if(!$perms['edit_about']) {
$notices[] = 'You\'re not allowed to edit your about page.';
} else {
$aboutText = (string)($_POST['about']['text'] ?? '');
$aboutParse = (int)($_POST['about']['parser'] ?? Parser::PLAIN);
$aboutValid = User::validateProfileAbout($aboutParse, $aboutText);
if($aboutValid === '')
$currentUser->setProfileAboutText($aboutText)->setProfileAboutParser($aboutParse);
else switch($aboutValid) {
case 'parser':
$notices[] = 'The selected about section parser is invalid.';
break;
case 'long':
$notices[] = sprintf('Please keep the length of your about section below %d characters.', User::PROFILE_ABOUT_MAX_LENGTH);
break;
default:
$notices[] = 'Failed to update about section, contact an administator.';
break;
}
}
}
if(!empty($_POST['signature']) && is_array($_POST['signature'])) {
if(!$perms['edit_signature']) {
$notices[] = 'You\'re not allowed to edit your forum signature.';
} else {
$sigText = (string)($_POST['signature']['text'] ?? '');
$sigParse = (int)($_POST['signature']['parser'] ?? Parser::PLAIN);
$sigValid = User::validateForumSignature($sigParse, $sigText);
if($sigValid === '')
$currentUser->setForumSignatureText($sigText)->setForumSignatureParser($sigParse);
else switch($sigValid) {
case 'parser':
$notices[] = 'The selected forum signature parser is invalid.';
break;
case 'long':
$notices[] = sprintf('Please keep the length of your signature below %d characters.', User::FORUM_SIGNATURE_MAX_LENGTH);
break;
default:
$notices[] = 'Failed to update signature, contact an administator.';
break;
}
}
}
if(!empty($_POST['birthdate']) && is_array($_POST['birthdate'])) {
if(!$perms['edit_birthdate']) {
$notices[] = "You aren't allow to change your birthdate.";
} else {
$birthYear = (int)($_POST['birthdate']['year'] ?? 0);
$birthMonth = (int)($_POST['birthdate']['month'] ?? 0);
$birthDay = (int)($_POST['birthdate']['day'] ?? 0);
$birthValid = User::validateBirthdate($birthYear, $birthMonth, $birthDay);
if($birthValid === '')
$currentUser->setBirthdate($birthYear, $birthMonth, $birthDay);
else switch($birthValid) {
case 'year':
$notices[] = 'The given birth year is invalid.';
break;
case 'date':
$notices[] = 'The given birthdate is invalid.';
break;
default:
$notices[] = 'Something unexpected happened while setting your birthdate.';
break;
}
}
}
if(!empty($_FILES['avatar'])) {
$avatarInfo = $profileUser->getAvatarInfo();
if(!empty($_POST['avatar']['delete'])) {
$avatarInfo->delete();
} else {
if(!$perms['edit_avatar']) {
$notices[] = 'You aren\'t allow to change your avatar.';
} elseif(!empty($_FILES['avatar'])
&& is_array($_FILES['avatar'])
&& !empty($_FILES['avatar']['name']['file'])) {
if($_FILES['avatar']['error']['file'] !== UPLOAD_ERR_OK) {
switch($_FILES['avatar']['error']['file']) {
case UPLOAD_ERR_NO_FILE:
$notices[] = 'Select a file before hitting upload!';
break;
case UPLOAD_ERR_PARTIAL:
$notices[] = 'The upload was interrupted, please try again!';
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$notices[] = sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarInfo->getMaxBytes()));
break;
default:
$notices[] = 'Unable to save your avatar, contact an administator!';
break;
}
} else {
try {
$avatarInfo->setFromPath($_FILES['avatar']['tmp_name']['file']);
} catch(UserImageAssetInvalidImageException $ex) {
$notices[] = 'The file you uploaded was not an image!';
} catch(UserImageAssetInvalidTypeException $ex) {
$notices[] = 'This type of image is not supported, keep to PNG, JPG or GIF!';
} catch(UserImageAssetInvalidDimensionsException $ex) {
$notices[] = sprintf('Your avatar can\'t be larger than %dx%d!', $avatarInfo->getMaxWidth(), $avatarInfo->getMaxHeight());
} catch(UserImageAssetFileTooLargeException $ex) {
$notices[] = sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarInfo->getMaxBytes()));
} catch(UserImageAssetException $ex) {
$notices[] = 'Unable to save your avatar, contact an administator!';
}
}
}
}
}
if(!empty($_FILES['background'])) {
$backgroundInfo = $profileUser->getBackgroundInfo();
if((int)($_POST['background']['attach'] ?? -1) === 0) {
$backgroundInfo->delete();
} else {
if(!$perms['edit_background']) {
$notices[] = 'You aren\'t allow to change your background.';
} elseif(!empty($_FILES['background']) && is_array($_FILES['background'])) {
if(!empty($_FILES['background']['name']['file'])) {
if($_FILES['background']['error']['file'] !== UPLOAD_ERR_OK) {
switch($_FILES['background']['error']['file']) {
case UPLOAD_ERR_NO_FILE:
$notices[] = 'Select a file before hitting upload!';
break;
case UPLOAD_ERR_PARTIAL:
$notices[] = 'The upload was interrupted, please try again!';
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$notices[] = sprintf('Your background is not allowed to be larger in file size than %s!', ByteFormat::format($backgroundProps['max_size']));
break;
default:
$notices[] = 'Unable to save your background, contact an administator!';
break;
}
} else {
try {
$backgroundInfo->setFromPath($_FILES['background']['tmp_name']['file']);
} catch(UserImageAssetInvalidImageException $ex) {
$notices[] = 'The file you uploaded was not an image!';
} catch(UserImageAssetInvalidTypeException $ex) {
$notices[] = 'This type of image is not supported, keep to PNG, JPG or GIF!';
} catch(UserImageAssetInvalidDimensionsException $ex) {
$notices[] = sprintf('Your background can\'t be larger than %dx%d!', $backgroundInfo->getMaxWidth(), $backgroundInfo->getMaxHeight());
} catch(UserImageAssetFileTooLargeException $ex) {
$notices[] = sprintf('Your background is not allowed to be larger in file size than %2$s!', ByteFormat::format($backgroundInfo->getMaxBytes()));
} catch(UserImageAssetException $ex) {
$notices[] = 'Unable to save your background, contact an administator!';
}
}
}
$backgroundInfo->setAttachment((int)($_POST['background']['attach'] ?? 0))
->setBlend(!empty($_POST['background']['attr']['blend']))
->setSlide(!empty($_POST['background']['attr']['slide']));
}
}
}
$profileUser->saveProfile();
}
// Unset $isEditing and hope the user doesn't refresh their profile!
if(empty($notices))
$isEditing = false;
}
}
$profileStats = DB::prepare('
SELECT (
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `user_id` = u.`user_id`
AND `topic_deleted` IS NULL
) AS `forum_topic_count`,
(
SELECT COUNT(`post_id`)
FROM `msz_forum_posts`
WHERE `user_id` = u.`user_id`
AND `post_deleted` IS NULL
) AS `forum_post_count`,
(
SELECT COUNT(`change_id`)
FROM `msz_changelog_changes`
WHERE `user_id` = u.`user_id`
) AS `changelog_count`,
(
SELECT COUNT(`comment_id`)
FROM `msz_comments_posts`
WHERE `user_id` = u.`user_id`
AND `comment_deleted` IS NULL
) AS `comments_count`
FROM `msz_users` AS u
WHERE `user_id` = :user_id
')->bind('user_id', $profileUser->getId())->fetch();
switch($profileMode) {
default:
echo render_error(404);
return;
case 'forum-topics':
$template = 'profile.topics';
$topicsCount = forum_topic_count_user($profileUser->getId(), $currentUserId);
$topicsPagination = new Pagination($topicsCount, 20);
if(!$topicsPagination->hasValidOffset()) {
echo render_error(404);
return;
}
$topics = forum_topic_listing_user(
$profileUser->getId(), $currentUserId,
$topicsPagination->getOffset(), $topicsPagination->getRange()
);
Template::set([
'title' => $profileUser->getUsername() . ' / topics',
'canonical_url' => url('user-profile-forum-topics', ['user' => $profileUser->getId(), 'page' => Pagination::param()]),
'profile_topics' => $topics,
'profile_topics_pagination' => $topicsPagination,
]);
break;
case 'forum-posts':
$template = 'profile.posts';
$postsCount = forum_post_count_user($profileUser->getId());
$postsPagination = new Pagination($postsCount, 20);
if(!$postsPagination->hasValidOffset()) {
echo render_error(404);
return;
}
$posts = forum_post_listing(
$profileUser->getId(),
$postsPagination->getOffset(),
$postsPagination->getRange(),
false,
true
);
Template::set([
'title' => $profileUser->getUsername() . ' / posts',
'canonical_url' => url('user-profile-forum-posts', ['user' => $profileUser->getId(), 'page' => Pagination::param()]),
'profile_posts' => $posts,
'profile_posts_pagination' => $postsPagination,
]);
break;
case '':
$template = 'profile.index';
$warnings = $profileUser->getProfileWarnings($currentUser);
$activeCategoryStats = $viewingAsGuest ? null : forum_get_user_most_active_category_info($profileUser->getId());
$activeCategoryInfo = empty($activeCategoryStats->forum_id) ? null : forum_get($activeCategoryStats->forum_id);
$activeTopicStats = $viewingAsGuest ? null : forum_get_user_most_active_topic_info($profileUser->getId());
$activeTopicInfo = empty($activeTopicStats->topic_id) ? null : forum_topic_get($activeTopicStats->topic_id);
Template::set([
'profile_warnings' => $warnings,
'profile_warnings_view_private' => $canManageWarnings,
'profile_warnings_can_manage' => $canManageWarnings,
'profile_active_category_stats' => $activeCategoryStats,
'profile_active_category_info' => $activeCategoryInfo,
'profile_active_topic_stats' => $activeTopicStats,
'profile_active_topic_info' => $activeTopicInfo,
]);
break;
}
if(!empty($template)) {
Template::render($template, [
'profile_viewer' => $currentUser,
'profile_user' => $profileUser,
'profile_stats' => $profileStats,
'profile_mode' => $profileMode,
'profile_notices' => $notices,
'profile_can_edit' => $canEdit,
'profile_is_editing' => $isEditing,
'profile_is_banned' => $isBanned,
]);
}

View file

@ -1,91 +0,0 @@
<?php
namespace Misuzu;
use RuntimeException;
use Misuzu\Comments\CommentsCategory;
use Misuzu\Users\User;
require_once '../misuzu.php';
$searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
if(!empty($searchQuery)) {
$forumTopics = forum_topic_listing_search($searchQuery, User::hasCurrent() ? User::getCurrent()->getId() : 0);
$forumPosts = forum_post_search($searchQuery);
// this sure is an expansion
$news = $msz->getNews();
$comments = $msz->getComments();
$newsPosts = [];
$newsPostInfos = $news->getPostsBySearchQuery($searchQuery);
$newsUserInfos = [];
$newsCategoryInfos = [];
foreach($newsPostInfos as $postInfo) {
$userId = $postInfo->getUserId();
$categoryId = $postInfo->getCategoryId();
if(array_key_exists($userId, $newsUserInfos)) {
$userInfo = $newsUserInfos[$userId];
} else {
try {
$userInfo = User::byId($userId);
} catch(UserNotFoundException $ex) {
$userInfo = null;
}
$newsUserInfos[$userId] = $userInfo;
}
if(array_key_exists($categoryId, $newsCategoryInfos))
$categoryInfo = $newsCategoryInfos[$categoryId];
else
$newsCategoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId()
? $comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0;
$newsPosts[] = [
'post' => $postInfo,
'category' => $categoryInfo,
'user' => $userInfo,
'comments_count' => $commentsCount,
];
}
$findUsers = DB::prepare('
SELECT u.`user_id`, u.`username`, u.`user_country`,
u.`user_created`, u.`user_active`, r.`role_id`,
COALESCE(u.`user_title`, r.`role_title`) AS `user_title`,
COALESCE(u.`user_colour`, r.`role_colour`) AS `user_colour`,
(
SELECT COUNT(`topic_id`)
FROM `msz_forum_topics`
WHERE `user_id` = u.`user_id`
AND `topic_deleted` IS NULL
) AS `user_count_topics`,
(
SELECT COUNT(`post_Id`)
FROM `msz_forum_posts`
WHERE `user_id` = u.`user_id`
AND `post_deleted` IS NULL
) AS `user_count_posts`
FROM `msz_users` AS u
LEFT JOIN `msz_roles` AS r
ON r.`role_id` = u.`display_role`
LEFT JOIN `msz_user_roles` AS ur
ON ur.`user_id` = u.`user_id`
WHERE LOWER(u.`username`) LIKE CONCAT("%%", LOWER(:query), "%%")
GROUP BY u.`user_id`
');
$findUsers->bind('query', $searchQuery);
$users = $findUsers->fetchAll();
}
Template::render('home.search', [
'search_query' => $searchQuery,
'forum_topics' => $forumTopics ?? [],
'forum_posts' => $forumPosts ?? [],
'users' => $users ?? [],
'news_posts' => $newsPosts ?? [],
]);

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';

View file

@ -1,136 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserRole;
use Misuzu\Users\UserRoleNotFoundException;
use Misuzu\Users\UserSession;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
require_once '../../misuzu.php';
if(!UserSession::hasCurrent()) {
echo render_error(401);
return;
}
$errors = [];
$currentUser = User::getCurrent();
$currentUserId = $currentUser->getId();
$isRestricted = $currentUser->hasActiveWarning();
$isVerifiedRequest = CSRF::validateRequest();
if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) {
try {
$roleInfo = UserRole::byId((int)($_POST['role']['id'] ?? 0));
} catch(UserRoleNotFoundException $ex) {}
if(empty($roleInfo) || !$currentUser->hasRole($roleInfo))
$errors[] = "You're trying to modify a role that hasn't been assigned to you.";
else {
switch($_POST['role']['mode'] ?? '') {
case 'display':
$currentUser->setDisplayRole($roleInfo);
break;
case 'leave':
if($roleInfo->getCanLeave())
$currentUser->removeRole($roleInfo);
else
$errors[] = "You're not allow to leave this role, an administrator has to remove it for you.";
break;
}
}
}
if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $currentUser->hasTOTP() !== (bool)$_POST['tfa']['enable']) {
if((bool)$_POST['tfa']['enable']) {
$tfaKey = TOTP::generateKey();
$tfaIssuer = $cfg->getString('site.name', 'Misuzu');
$tfaQrcode = (new QRCode(new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_IMAGE_JPG,
'eccLevel' => QRCode::ECC_L,
])))->render(sprintf('otpauth://totp/%s:%s?%s', $tfaIssuer, $currentUser->getUsername(), http_build_query([
'secret' => $tfaKey,
'issuer' => $tfaIssuer,
])));
Template::set([
'settings_2fa_code' => $tfaKey,
'settings_2fa_image' => $tfaQrcode,
]);
$currentUser->setTOTPKey($tfaKey);
} else {
$currentUser->removeTOTPKey();
}
}
if($isVerifiedRequest && !empty($_POST['current_password'])) {
if(!$currentUser->checkPassword($_POST['current_password'] ?? '')) {
$errors[] = 'Your password was incorrect.';
} else {
// Changing e-mail
if(!empty($_POST['email']['new'])) {
if(empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) {
$errors[] = 'The addresses you entered did not match each other.';
} elseif($currentUser->getEMailAddress() === mb_strtolower($_POST['email']['confirm'])) {
$errors[] = 'This is already your e-mail address!';
} else {
$checkMail = User::validateEMailAddress($_POST['email']['new'], true);
if($checkMail !== '') {
switch($checkMail) {
case 'dns':
$errors[] = 'No valid MX record exists for this domain.';
break;
case 'format':
$errors[] = 'The given e-mail address was incorrectly formatted.';
break;
case 'in-use':
$errors[] = 'This e-mail address is already in use.';
break;
default:
$errors[] = 'Unknown e-mail validation error.';
}
} else {
$currentUser->setEMailAddress($_POST['email']['new']);
$msz->createAuditLog('PERSONAL_EMAIL_CHANGE', [
$_POST['email']['new'],
]);
}
}
}
// Changing password
if(!empty($_POST['password']['new'])) {
if(empty($_POST['password']['confirm']) || $_POST['password']['new'] !== $_POST['password']['confirm']) {
$errors[] = 'The new passwords you entered did not match each other.';
} else {
$checkPassword = User::validatePassword($_POST['password']['new']);
if($checkPassword !== '') {
$errors[] = 'The given passwords was too weak.';
} else {
$currentUser->setPassword($_POST['password']['new']);
$msz->createAuditLog('PERSONAL_PASSWORD_CHANGE');
}
}
}
}
}
// THIS FUCKING SUCKS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
if($_SERVER['REQUEST_METHOD'] === 'POST' && $isVerifiedRequest)
$currentUser->save();
Template::render('settings.account', [
'errors' => $errors,
'settings_user' => $currentUser,
'is_restricted' => $isRestricted,
]);

View file

@ -1,174 +0,0 @@
<?php
namespace Misuzu;
use ZipArchive;
use Index\XString;
use Index\IO\FileStream;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
require_once '../../misuzu.php';
if(!UserSession::hasCurrent()) {
echo render_error(401);
return;
}
$dbConn = $msz->getDbConn();
function db_to_zip(ZipArchive $archive, int $userId, string $baseName, array $fieldInfos, string $userIdField = 'user_id'): string {
global $dbConn;
$fields = [];
foreach($fieldInfos as $key => $fieldInfo) {
$fieldInfo = explode(':', $fieldInfo);
$fieldInfo = [
'name' => $fieldInfo[0],
'type' => $fieldInfo[1] ?? 'n',
'nullable' => ($fieldInfo[2] ?? '') === 'n',
];
$fieldInfo['is_null'] = $fieldInfo['type'] === 'n';
if(!$fieldInfo['is_null']) {
$selectField = $fieldInfo['name'];
if($fieldInfo['type'] === 'a')
$selectField = sprintf('INET6_NTOA(%s)', $selectField);
elseif($fieldInfo['type'] === 't')
$selectField = sprintf('UNIX_TIMESTAMP(%s)', $selectField);
$fields[] = $selectField;
}
$fieldInfos[$key] = $fieldInfo;
}
$tmpName = sys_get_temp_dir() . DIRECTORY_SEPARATOR . sprintf('msz-user-data-%d-%s-%s.tmp', $userId, $baseName, XString::random(8));
$tmpStream = FileStream::newWrite($tmpName);
try {
$stmt = $dbConn->prepare(sprintf('SELECT %s FROM msz_%s WHERE %s = ?', implode(', ', $fields), $baseName, $userIdField));
$stmt->addParameter(1, $userId);
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$args = -1;
$row = [];
foreach($fieldInfos as $fieldInfo) {
$fieldValue = null;
if(!$fieldInfo['is_null']) {
++$args;
if(!$fieldInfo['nullable'] || !$result->isNull($args)) {
if($fieldInfo['type'] === 's' || $fieldInfo['type'] === 'a' || $fieldInfo['type'] === 'j') {
$fieldValue = $result->getString($args);
if($fieldInfo['type'] === 'j')
$fieldValue = json_decode($fieldValue);
} elseif($fieldInfo['type'] === 't' || $fieldInfo['type'] === 'b' || $fieldInfo['type'] === 'i') {
$fieldValue = $result->getInteger($args);
if($fieldInfo['type'] === 'b')
$fieldValue = $fieldValue !== 0;
elseif($fieldInfo['type'] === 't') {
$fieldValue = date(DATE_ATOM, $fieldValue);
if(str_ends_with($fieldValue, '+00:00'))
$fieldValue = substr($fieldValue, 0, -6) . 'Z';
}
} else {
$fieldValue = '?';
}
}
}
$row[$fieldInfo['name']] = $fieldValue;
}
$tmpStream->write(json_encode($row, JSON_INVALID_UTF8_SUBSTITUTE));
$tmpStream->write("\n");
}
} finally {
$tmpStream->flush();
$tmpStream->close();
}
$archive->addFile($tmpName, $baseName . '.jsonl');
return $tmpName;
}
$errors = [];
$currentUser = User::getCurrent();
$currentUserId = $currentUser->getId();
if(isset($_POST['action']) && is_string($_POST['action'])) {
if(isset($_POST['password']) && is_string($_POST['password'])
&& ($currentUser->checkPassword($_POST['password'] ?? ''))) {
switch($_POST['action']) {
case 'data':
$msz->createAuditLog('PERSONAL_DATA_DOWNLOAD');
$timeStamp = floor(time() / 3600) * 3600;
$fileName = sprintf('msz-user-data-%d-%d.zip', $currentUserId, $timeStamp);
$filePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName;
$archive = new ZipArchive;
if(!is_file($filePath)) {
if($archive->open($filePath, ZipArchive::CREATE | ZIPARCHIVE::OVERWRITE) === true) {
$tmpFiles = [];
try {
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'audit_log', ['user_id:s:n', 'log_action:s', 'log_params:j', 'log_created:t', 'log_ip:a:n', 'log_country:s']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'auth_tfa', ['user_id:s', 'tfa_token:n', 'tfa_created:t']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'changelog_changes', ['change_id:s', 'user_id:s:n', 'change_action:s:n', 'change_created:t', 'change_log:s', 'change_text:s:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'comments_categories', ['category_id:s', 'category_name:s', 'owner_id:s:n', 'category_created:t', 'category_locked:t:n'], 'owner_id');
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'comments_posts', ['comment_id:s', 'category_id:s', 'user_id:s:n', 'comment_reply_to:s:n', 'comment_text:s', 'comment_created:t', 'comment_pinned:t:n', 'comment_edited:t:n', 'comment_deleted:t:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'comments_votes', ['comment_id:s', 'user_id:s', 'comment_vote:i']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_permissions', ['user_id:s:n', 'role_id:s:n', 'forum_id:s', 'forum_perms_allow:i', 'forum_perms_deny:i']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_posts', ['post_id:s', 'topic_id:s', 'forum_id:s', 'user_id:s:n', 'post_ip:a', 'post_text:s', 'post_parse:i', 'post_display_signature:b', 'post_created:t', 'post_edited:t:n', 'post_deleted:t:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_topics', ['topic_id:s', 'forum_id:s', 'user_id:s:n', 'topic_type:i', 'topic_title:s', 'topic_count_views:i', 'topic_created:t', 'topic_bumped:t', 'topic_deleted:t:n', 'topic_locked:t:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_topics_redirects', ['topic_id:s', 'user_id:s:n', 'topic_redir_url:s', 'topic_redir_created:t']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'forum_topics_track', ['user_id:s', 'topic_id:s', 'forum_id:s', 'track_last_read:t']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'login_attempts', ['user_id:s:n', 'attempt_success:b', 'attempt_ip:a', 'attempt_country:s', 'attempt_created:t', 'attempt_user_agent:s']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'news_posts', ['post_id:s', 'category_id:s', 'user_id:s:n', 'comment_section_id:s:n', 'post_is_featured:b', 'post_title:s', 'post_text:s', 'post_scheduled:t', 'post_created:t', 'post_updated:t', 'post_deleted:t:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'permissions', ['user_id:s:n', 'role_id:s:n', 'general_perms_allow:i', 'general_perms_deny:i', 'user_perms_allow:i', 'user_perms_deny:i', 'changelog_perms_allow:i', 'changelog_perms_deny:i', 'news_perms_allow:i', 'news_perms_deny:i', 'forum_perms_allow:i', 'forum_perms_deny:i', 'comments_perms_allow:i', 'comments_perms_deny:i']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'profile_fields_values', ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'sessions', ['session_id:s', 'user_id:s', 'session_key:n', 'session_ip:a', 'session_ip_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'users', ['user_id:s', 'username:s', 'password:n', 'email:s', 'register_ip:a', 'last_ip:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'display_role:s:n', 'user_totp_key:n', 'user_about_content:s:n', 'user_about_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_birthdate:s:n', 'user_background_settings:i:n', 'user_title:s:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'users_password_resets', ['user_id:s', 'reset_ip:a', 'reset_requested:t', 'verification_code:n']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'user_roles', ['user_id:s', 'role_id:s']);
$tmpFiles[] = db_to_zip($archive, $currentUserId, 'user_warnings', ['warning_id:s', 'user_id:s', 'user_ip:a', 'issuer_id:n', 'issuer_ip:n', 'warning_created:t', 'warning_duration:t:n', 'warning_type:i', 'warning_note:s', 'warning_note_private:s:n']);
$archive->close();
} finally {
foreach($tmpFiles as $tmpFile)
if(is_file($tmpFile))
unlink($tmpFile);
}
} else {
$errors[] = 'Something went wrong while creating your account archive.';
break;
}
}
header('Content-Type: application/zip');
header(sprintf('Content-Disposition: inline; filename="%s"', $fileName));
echo file_get_contents($filePath);
return;
case 'deactivate':
// deactivation
break;
}
} else {
$errors[] = 'Incorrect password.';
}
}
Template::render('settings.data', [
'errors' => $errors,
]);

View file

@ -1,13 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\UserSession;
require_once '../../misuzu.php';
if(!UserSession::hasCurrent()) {
echo render_error(401);
return;
}
url_redirect('settings-account');

View file

@ -1,29 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Pagination;
use Misuzu\Users\User;
use Misuzu\Users\UserLoginAttempt;
require_once '../../misuzu.php';
$currentUser = User::getCurrent();
if($currentUser === null) {
echo render_error(401);
return;
}
$auditLog = $msz->getAuditLog();
$loginHistoryPagination = new Pagination(UserLoginAttempt::countAll($currentUser), 15, 'hp');
$accountLogPagination = new Pagination($auditLog->countLogs(userInfo: $currentUser), 15, 'ap');
$auditLogs = $auditLog->getLogs(userInfo: $currentUser, pagination: $accountLogPagination);
Template::render('settings.logs', [
'login_history_list' => UserLoginAttempt::all($loginHistoryPagination, $currentUser),
'login_history_pagination' => $loginHistoryPagination,
'account_log_list' => $auditLogs,
'account_log_pagination' => $accountLogPagination,
]);

View file

@ -1,61 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Users\User;
use Misuzu\Users\UserSession;
use Misuzu\Users\UserSessionNotFoundException;
require_once '../../misuzu.php';
if(!User::hasCurrent()) {
echo render_error(401);
return;
}
$errors = [];
$currentUser = User::getCurrent();
$currentSession = UserSession::getCurrent();
$currentUserId = $currentUser->getId();
$sessionActive = $currentSession->getId();;
if(!empty($_POST['session']) && CSRF::validateRequest()) {
$currentSessionKilled = false;
if(is_array($_POST['session'])) {
foreach($_POST['session'] as $sessionId) {
$sessionId = (int)$sessionId;
try {
$sessionInfo = UserSession::byId($sessionId);
} catch(UserSessionNotFoundException $ex) {}
if(empty($sessionInfo) || $sessionInfo->getUserId() !== $currentUser->getId()) {
$errors[] = "Session #{$sessionId} does not exist.";
continue;
} elseif($sessionInfo->getId() === $sessionActive) {
$currentSessionKilled = true;
}
$sessionInfo->delete();
$msz->createAuditLog('PERSONAL_SESSION_DESTROY', [$sessionInfo->getId()]);
}
} elseif($_POST['session'] === 'all') {
$currentSessionKilled = true;
UserSession::purgeUser($currentUser);
$msz->createAuditLog('PERSONAL_SESSION_DESTROY_ALL');
}
if($currentSessionKilled) {
url_redirect('index');
return;
}
}
$pagination = new Pagination(UserSession::countAll($currentUser), 15);
Template::render('settings.sessions', [
'errors' => $errors,
'session_list' => UserSession::all($pagination, $currentUser),
'session_current' => $currentSession,
'session_pagination' => $pagination,
]);

View file

@ -1,2 +0,0 @@
<?php
require_once __DIR__ . '/index.php';