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); $this->authInfo = new AuthInfo; } 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; } public function createAuthTokenPacker(): AuthTokenPacker { return new AuthTokenPacker($this->config->getString('auth.secret', 'meow')); } public function getAuthInfo(): AuthInfo { return $this->authInfo; } // isLoggedIn and getActiveUser are proxied for convenience, supply authInfo to things in the future public function isLoggedIn(): bool { return $this->authInfo->isLoggedIn(); } public function getActiveUser(): ?UserInfo { return $this->authInfo->getUserInfo(); } 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->bans, $this->emotes, $this->users, $this->sessions, $this->authInfo, $this->createAuthTokenPacker(...)); 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); }); } }