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

548 lines
19 KiB
PHP

<?php
namespace Misuzu;
use Misuzu\Template;
use Misuzu\Auth\LoginAttempts;
use Misuzu\Auth\RecoveryTokens;
use Misuzu\Auth\Sessions;
use Misuzu\Auth\TwoFactorAuthSessions;
use Misuzu\AuditLog\AuditLog;
use Misuzu\Changelog\Changelog;
use Misuzu\Comments\Comments;
use Misuzu\Config\IConfig;
use Misuzu\Counters\Counters;
use Misuzu\Emoticons\Emotes;
use Misuzu\News\News;
use Misuzu\Profile\ProfileFields;
use Misuzu\Satori\SatoriRoutes;
use Misuzu\SharpChat\SharpChatRoutes;
use Misuzu\Users\Bans;
use Misuzu\Users\BanInfo;
use Misuzu\Users\ModNotes;
use Misuzu\Users\Roles;
use Misuzu\Users\Users;
use Misuzu\Users\UserInfo;
use Misuzu\Users\Warnings;
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigrationRepo;
use Index\Data\Migration\DbMigrationManager;
use Index\Data\Migration\FsDbMigrationRepo;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\Router;
// this class should function as the root for everything going forward
// no more magical static classes that are just kind of assumed to exist
// it currently looks Pretty Messy, but most everything else will be holding instances of other classes
// instances of certain classes should only be made as needed,
// dunno if i want null checks some maybe some kind of init func should be called first like is the case
// with the http shit
class MisuzuContext {
private IDbConnection $dbConn;
private IConfig $config;
private HttpFx $router;
private AuditLog $auditLog;
private Emotes $emotes;
private Changelog $changelog;
private News $news;
private Comments $comments;
private LoginAttempts $loginAttempts;
private RecoveryTokens $recoveryTokens;
private ModNotes $modNotes;
private Bans $bans;
private Warnings $warnings;
private TwoFactorAuthSessions $tfaSessions;
private Roles $roles;
private Users $users;
private Sessions $sessions;
private Counters $counters;
private ProfileFields $profileFields;
public function __construct(IDbConnection $dbConn, IConfig $config) {
$this->dbConn = $dbConn;
$this->config = $config;
$this->auditLog = new AuditLog($this->dbConn);
$this->emotes = new Emotes($this->dbConn);
$this->changelog = new Changelog($this->dbConn);
$this->news = new News($this->dbConn);
$this->comments = new Comments($this->dbConn);
$this->loginAttempts = new LoginAttempts($this->dbConn);
$this->recoveryTokens = new RecoveryTokens($this->dbConn);
$this->modNotes = new ModNotes($this->dbConn);
$this->bans = new Bans($this->dbConn);
$this->warnings = new Warnings($this->dbConn);
$this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
$this->roles = new Roles($this->dbConn);
$this->users = new Users($this->dbConn);
$this->sessions = new Sessions($this->dbConn);
$this->counters = new Counters($this->dbConn);
$this->profileFields = new ProfileFields($this->dbConn);
}
public function getDbConn(): IDbConnection {
return $this->dbConn;
}
public function getDbQueryCount(): int {
$result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"');
return $result->next() ? $result->getInteger(1) : 0;
}
public function createMigrationManager(): DbMigrationManager {
return new DbMigrationManager($this->dbConn, 'msz_' . DbMigrationManager::DEFAULT_TABLE);
}
public function createMigrationRepo(): IDbMigrationRepo {
return new FsDbMigrationRepo(MSZ_MIGRATIONS);
}
public function getConfig(): IConfig {
return $this->config;
}
public function getRouter(): Router {
return $this->router->getRouter();
}
public function getEmotes(): Emotes {
return $this->emotes;
}
public function getChangelog(): Changelog {
return $this->changelog;
}
public function getNews(): News {
return $this->news;
}
public function getComments(): Comments {
return $this->comments;
}
public function getAuditLog(): AuditLog {
return $this->auditLog;
}
public function getLoginAttempts(): LoginAttempts {
return $this->loginAttempts;
}
public function getRecoveryTokens(): RecoveryTokens {
return $this->recoveryTokens;
}
public function getModNotes(): ModNotes {
return $this->modNotes;
}
public function getBans(): Bans {
return $this->bans;
}
public function getWarnings(): Warnings {
return $this->warnings;
}
public function getTFASessions(): TwoFactorAuthSessions {
return $this->tfaSessions;
}
public function getRoles(): Roles {
return $this->roles;
}
public function getUsers(): Users {
return $this->users;
}
public function getSessions(): Sessions {
return $this->sessions;
}
public function getCounters(): Counters {
return $this->counters;
}
public function getProfileFields(): ProfileFields {
return $this->profileFields;
}
private ?AuthToken $authToken = null;
private ?UserInfo $activeUser = null;
private ?UserInfo $activeUserReal = null;
public function setAuthInfo(AuthToken $authToken, ?UserInfo $userInfo, ?UserInfo $realUserInfo): void {
$this->authToken = $authToken;
$this->activeUser = $userInfo;
$this->activeUserReal = $realUserInfo;
}
public function removeAuthInfo(): void {
$this->authToken = null;
$this->activeUser = null;
$this->activeUserReal = null;
}
public function hasAuthToken(): bool {
return $this->authToken !== null;
}
public function getAuthToken(): ?AuthToken {
return $this->authToken;
}
public function isLoggedIn(): bool {
return $this->authToken !== null && $this->activeUser !== null;
}
public function isImpersonating(): bool {
return $this->activeUser !== null && $this->activeUserReal !== null
&& $this->activeUser->getId() !== $this->activeUserReal->getId();
}
public function hasActiveUser(): bool {
return $this->activeUser !== null;
}
public function getActiveUser(): ?UserInfo {
return $this->activeUser;
}
public function hasRealActiveUser(): bool {
return $this->activeUserReal !== null;
}
public function getRealActiveUser(): ?UserInfo {
return $this->activeUserReal;
}
private array $activeBansCache = [];
public function tryGetActiveBan(UserInfo|string|null $userInfo = null): ?BanInfo {
if($userInfo === null) {
if($this->isLoggedIn())
$userInfo = $this->getActiveUser();
else return null;
}
$userId = (string)$userInfo->getId();
if(array_key_exists($userId, $this->activeBansCache))
return $this->activeBansCache[$userId];
return $this->activeBansCache[$userId] = $this->bans->tryGetActiveBan($userId);
}
public function hasActiveBan(UserInfo|string|null $userInfo = null): bool {
return $this->tryGetActiveBan($userInfo) !== null;
}
public function createAuditLog(string $action, array $params = [], UserInfo|string|null $userInfo = null): void {
if($userInfo === null && $this->isLoggedIn())
$userInfo = $this->getActiveUser();
$this->auditLog->createLog(
$userInfo,
$action,
$params,
$_SERVER['REMOTE_ADDR'] ?? '::1',
$_SERVER['COUNTRY_CODE'] ?? 'XX'
);
}
public function getHeaderMenu(?UserInfo $userInfo): array {
$hasUserInfo = $userInfo?->isDeleted() === false;
$menu = [];
$home = [
'title' => 'Home',
'url' => url('index'),
'menu' => [],
];
if($hasUserInfo)
$home['menu'][] = [
'title' => 'Members',
'url' => url('user-list'),
];
$home['menu'][] = [
'title' => 'Changelog',
'url' => url('changelog-index'),
];
$home['menu'][] = [
'title' => 'Contact',
'url' => url('info', ['title' => 'contact']),
];
$home['menu'][] = [
'title' => 'Rules',
'url' => url('info', ['title' => 'rules']),
];
$menu[] = $home;
$menu[] = [
'title' => 'News',
'url' => url('news-index'),
];
$forum = [
'title' => 'Forum',
'url' => url('forum-index'),
'menu' => [],
];
if($hasUserInfo && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD))
$forum['menu'][] = [
'title' => 'Leaderboard',
'url' => url('forum-leaderboard'),
];
$menu[] = $forum;
$chatPath = $this->config->getString('sockChat.chatPath.normal');
if(!empty($chatPath))
$menu[] = [
'title' => 'Chat',
'url' => $chatPath,
];
return $menu;
}
public function getUserMenu(?UserInfo $userInfo, bool $inBroomCloset): array {
$menu = [];
if($userInfo === null) {
$menu[] = [
'title' => 'Register',
'url' => url('auth-register'),
'icon' => 'fas fa-user-plus fa-fw',
];
$menu[] = [
'title' => 'Log in',
'url' => url('auth-login'),
'icon' => 'fas fa-sign-in-alt fa-fw',
];
} else {
$menu[] = [
'title' => 'Profile',
'url' => url('user-profile', ['user' => $userInfo->getId()]),
'icon' => 'fas fa-user fa-fw',
];
$menu[] = [
'title' => 'Settings',
'url' => url('settings-index'),
'icon' => 'fas fa-cog fa-fw',
];
$menu[] = [
'title' => 'Search',
'url' => url('search-index'),
'icon' => 'fas fa-search fa-fw',
];
if(!$this->hasActiveBan($userInfo) && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_GENERAL_CAN_MANAGE)) {
// restore behaviour where clicking this button switches between
// site version and broom version
if($inBroomCloset)
$menu[] = [
'title' => 'Exit Broom Closet',
'url' => url('index'),
'icon' => 'fas fa-door-open fa-fw',
];
else
$menu[] = [
'title' => 'Enter Broom Closet',
'url' => url('manage-index'),
'icon' => 'fas fa-door-closed fa-fw',
];
}
$menu[] = [
'title' => 'Log out',
'url' => url('auth-logout'),
'icon' => 'fas fa-sign-out-alt fa-fw',
];
}
return $menu;
}
public function setUpHttp(bool $legacy = false): void {
$this->router = new HttpFx;
$this->router->use('/', function($response) {
$response->setPoweredBy('Misuzu');
});
$this->registerErrorPages();
if($legacy)
$this->registerLegacyRedirects();
else
$this->registerHttpRoutes();
}
public function dispatchHttp(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}
private function registerErrorPages(): void {
$this->router->addErrorHandler(400, function($response) {
$response->setContent(Template::renderRaw('errors.400'));
});
$this->router->addErrorHandler(403, function($response) {
$response->setContent(Template::renderRaw('errors.403'));
});
$this->router->addErrorHandler(404, function($response) {
$response->setContent(Template::renderRaw('errors.404'));
});
$this->router->addErrorHandler(500, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html'));
});
$this->router->addErrorHandler(503, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html'));
});
}
private function registerHttpRoutes(): void {
$mszCompatHandler = fn($className, $method) => fn(...$args) => (new ("\\Misuzu\\Http\\Handlers\\{$className}Handler")($this))->{$method}(...$args);
$this->router->get('/', $mszCompatHandler('Home', 'index'));
$this->router->get('/assets/avatar/:filename', $mszCompatHandler('Assets', 'serveAvatar'));
$this->router->get('/assets/profile-background/:filename', $mszCompatHandler('Assets', 'serveProfileBackground'));
$this->router->get('/info', $mszCompatHandler('Info', 'index'));
$this->router->get('/info/:name', $mszCompatHandler('Info', 'page'));
$this->router->get('/info/:project/:name', $mszCompatHandler('Info', 'page'));
$this->router->get('/changelog', $mszCompatHandler('Changelog', 'index'));
$this->router->get('/changelog.rss', $mszCompatHandler('Changelog', 'feedRss'));
$this->router->get('/changelog.atom', $mszCompatHandler('Changelog', 'feedAtom'));
$this->router->get('/changelog/change/:id', $mszCompatHandler('Changelog', 'change'));
$this->router->get('/news', $mszCompatHandler('News', 'index'));
$this->router->get('/news.rss', $mszCompatHandler('News', 'feedIndexRss'));
$this->router->get('/news.atom', $mszCompatHandler('News', 'feedIndexAtom'));
$this->router->get('/news/:category', $mszCompatHandler('News', 'viewCategory'));
$this->router->get('/news/post/:id', $mszCompatHandler('News', 'viewPost'));
$this->router->get('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadGET'));
$this->router->post('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadPOST'));
new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'), $this, $this->bans, $this->emotes, $this->users, $this->sessions);
new SatoriRoutes($this->dbConn, $this->config->scopeTo('satori'), $this->router, $this->users, $this->profileFields);
}
private function registerLegacyRedirects(): void {
$this->router->get('/index.php', function($response) {
$response->redirect(url('index'), true);
});
$this->router->get('/info.php', function($response) {
$response->redirect(url('info'), true);
});
$this->router->get('/settings.php', function($response) {
$response->redirect(url('settings-index'), true);
});
$this->router->get('/changelog.php', function($response, $request) {
$changeId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
if($changeId) {
$response->redirect(url('changelog-change', ['change' => $changeId]), true);
return;
}
$response->redirect(url('changelog-index', [
'date' => $request->getParam('d'),
'user' => $request->getParam('u', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$infoRedirect = function($response, $request, string ...$parts) {
$response->redirect(url('info', ['title' => implode('/', $parts)]), true);
};
$this->router->get('/info.php/:name', $infoRedirect);
$this->router->get('/info.php/:project/:name', $infoRedirect);
$this->router->get('/auth.php', function($response, $request) {
$response->redirect(url([
'logout' => 'auth-logout',
'reset' => 'auth-reset',
'forgot' => 'auth-forgot',
'register' => 'auth-register',
][$request->getParam('m')] ?? 'auth-login'), true);
});
$this->router->get('/news.php', function($response, $request) {
$postId = $request->getParam('n', FILTER_SANITIZE_NUMBER_INT) ?? $request->getParam('p', FILTER_SANITIZE_NUMBER_INT);
if($postId > 0)
$location = url('news-post', ['post' => $postId]);
else {
$catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$pageId = $request->getParam('page', FILTER_SANITIZE_NUMBER_INT);
$location = url($catId > 0 ? 'news-category' : 'news-index', ['category' => $catId, 'page' => $pageId]);
}
$response->redirect($location, true);
});
$this->router->get('/news.php/rss', function($response, $request) {
$catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$location = url($catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss', ['category' => $catId]);
$response->redirect($location, true);
});
$this->router->get('/news.php/atom', function($response, $request) {
$catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$location = url($catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom', ['category' => $catId]);
$response->redirect($location, true);
});
$this->router->get('/news/index.php', function($response, $request) {
$response->redirect(url('news-index', [
'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$this->router->get('/news/category.php', function($response, $request) {
$response->redirect(url('news-category', [
'category' => $request->getParam('c', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$this->router->get('/news/post.php', function($response, $request) {
$response->redirect(url('news-post', [
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$this->router->get('/news/feed.php', function() {
return 400;
});
$this->router->get('/news/feed.php/rss', function($response, $request) {
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$response->redirect(url(
$catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss',
['category' => $catId]
), true);
});
$this->router->get('/news/feed.php/atom', function($response, $request) {
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$response->redirect(url(
$catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom',
['category' => $catId]
), true);
});
$this->router->get('/user-assets.php', function($response, $request) {
return (new \Misuzu\Http\Handlers\AssetsHandler($this))->serveLegacy($response, $request);
});
}
}