diff --git a/public/index.php b/public/index.php index a93c08b..7005825 100644 --- a/public/index.php +++ b/public/index.php @@ -125,6 +125,8 @@ CSRF::init( ($authInfo->isLoggedIn() ? $sessionInfo->getToken() : $_SERVER['REMOTE_ADDR']) ); +// order for these two currently matters i think: it shouldn't. +$router = $msz->createRouting(); $msz->startTemplating(); $mszRequestPath = $request->getPath(); @@ -144,5 +146,4 @@ if(!empty($mszLegacyPath) && str_starts_with($mszLegacyPath, $mszLegacyPathPrefi } } -$msz->startRouter(); -$msz->dispatchRouter($request); +$router->dispatch($request); diff --git a/src/Changelog/ChangelogRoutes.php b/src/Changelog/ChangelogRoutes.php index 22fc78d..505d670 100644 --- a/src/Changelog/ChangelogRoutes.php +++ b/src/Changelog/ChangelogRoutes.php @@ -15,6 +15,7 @@ use Misuzu\Feeds\Feed; use Misuzu\Feeds\FeedItem; use Misuzu\Feeds\AtomFeedSerializer; use Misuzu\Feeds\RssFeedSerializer; +use Misuzu\URLs\URLInfo; use Misuzu\URLs\URLRegistry; use Misuzu\Users\UsersContext; @@ -34,6 +35,7 @@ final class ChangelogRoutes extends RouteHandler { } #[Route('GET', '/changelog')] + #[URLInfo('changelog-index', '/changelog', ['date' => '', 'user' => '', 'tags' => '', 'p' => ''])] public function getIndex($response, $request) { $filterDate = (string)$request->getParam('date'); $filterUser = (string)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT); @@ -98,6 +100,8 @@ final class ChangelogRoutes extends RouteHandler { } #[Route('GET', '/changelog/change/:id')] + #[URLInfo('changelog-change', '/changelog/change/')] + #[URLInfo('changelog-change-comments', '/changelog/change/', fragment: 'comments')] public function getChange($response, $request, string $changeId) { try { $changeInfo = $this->changelog->getChange($changeId); @@ -145,12 +149,14 @@ final class ChangelogRoutes extends RouteHandler { } #[Route('GET', '/changelog.rss')] + #[URLInfo('changelog-feed-rss', '/changelog.rss')] public function getFeedRSS($response) { $response->setContentType('application/rss+xml; charset=utf-8'); return (new RssFeedSerializer)->serializeFeed($this->createFeed('rss')); } #[Route('GET', '/changelog.atom')] + #[URLInfo('changelog-feed-atom', '/changelog.atom')] public function getFeedAtom($response) { $response->setContentType('application/atom+xml; charset=utf-8'); return (new AtomFeedSerializer)->serializeFeed($this->createFeed('atom')); diff --git a/src/Home/HomeRoutes.php b/src/Home/HomeRoutes.php index 6af05cf..af254c1 100644 --- a/src/Home/HomeRoutes.php +++ b/src/Home/HomeRoutes.php @@ -16,6 +16,7 @@ use Misuzu\Comments\Comments; use Misuzu\Config\IConfig; use Misuzu\Counters\Counters; use Misuzu\News\News; +use Misuzu\URLs\URLInfo; use Misuzu\Users\UsersContext; class HomeRoutes extends RouteHandler { @@ -154,6 +155,7 @@ class HomeRoutes extends RouteHandler { } #[Route('GET', '/')] + #[URLInfo('index', '/')] public function getIndex(...$args) { return $this->authInfo->isLoggedIn() ? $this->getHome(...$args) diff --git a/src/Info/InfoRoutes.php b/src/Info/InfoRoutes.php index 8177978..c0ee806 100644 --- a/src/Info/InfoRoutes.php +++ b/src/Info/InfoRoutes.php @@ -5,6 +5,7 @@ use Index\Routing\Route; use Index\Routing\RouteHandler; use Misuzu\Template; use Misuzu\Parsers\Parser; +use Misuzu\URLs\URLInfo; class InfoRoutes extends RouteHandler { private const DOCS_PATH = MSZ_ROOT . '/docs'; @@ -22,11 +23,14 @@ class InfoRoutes extends RouteHandler { } #[Route('GET', '/info')] + #[URLInfo('info-index', '/info')] public function getIndex() { return Template::renderRaw('info.index'); } #[Route('GET', '/info/:name')] + #[URLInfo('info', '/info/')] + #[URLInfo('info-doc', '/info/<title>')] public function getDocsPage($response, $request, string $name) { if(!self::checkName($name)) return 404; @@ -75,6 +79,7 @@ class InfoRoutes extends RouteHandler { } #[Route('GET', '/info/:project/:name')] + #[URLInfo('info-project-doc', '/info/<project>/<title>')] public function getProjectPage($response, $request, string $project, string $name) { if(!array_key_exists($project, self::PROJECT_PATHS)) return 404; diff --git a/src/LegacyRoutes.php b/src/LegacyRoutes.php index 2189c11..089ce58 100644 --- a/src/LegacyRoutes.php +++ b/src/LegacyRoutes.php @@ -3,13 +3,127 @@ namespace Misuzu; use Index\Routing\Route; use Index\Routing\RouteHandler; +use Misuzu\URLs\IURLSource; +use Misuzu\URLs\URLInfo; use Misuzu\URLs\URLRegistry; -class LegacyRoutes extends RouteHandler { +class LegacyRoutes extends RouteHandler implements IURLSource { public function __construct( private URLRegistry $urls ) {} + public function registerURLs(URLRegistry $urls): void { + // eventually this should be handled by context classes + $urls->register('search-index', '/search.php'); + $urls->register('search-query', '/search.php', ['q' => '<query>'], '<section>'); + + $urls->register('auth-login', '/auth/login.php', ['username' => '<username>', 'redirect' => '<redirect>']); + $urls->register('auth-login-welcome', '/auth/login.php', ['welcome' => '1', 'username' => '<username>']); + $urls->register('auth-register', '/auth/register.php'); + $urls->register('auth-forgot', '/auth/password.php'); + $urls->register('auth-reset', '/auth/password.php', ['user' => '<user>']); + $urls->register('auth-logout', '/auth/logout.php', ['csrf' => '<csrf>']); + $urls->register('auth-resolve-user', '/auth/login.php', ['resolve' => '1', 'name' => '<username>']); + $urls->register('auth-two-factor', '/auth/twofactor.php', ['token' => '<token>']); + $urls->register('auth-revert', '/auth/revert.php', ['csrf' => '<csrf>']); + + $urls->register('forum-index', '/forum'); + $urls->register('forum-leaderboard', '/forum/leaderboard.php', ['id' => '<id>', 'mode' => '<mode>']); + $urls->register('forum-mark-global', '/forum/index.php', ['m' => 'mark']); + $urls->register('forum-mark-single', '/forum/index.php', ['m' => 'mark', 'f' => '<forum>']); + $urls->register('forum-topic-new', '/forum/posting.php', ['f' => '<forum>']); + $urls->register('forum-reply-new', '/forum/posting.php', ['t' => '<topic>']); + $urls->register('forum-category', '/forum/forum.php', ['f' => '<forum>', 'p' => '<page>']); + $urls->register('forum-category-root', '/forum/index.php', fragment: '<forum>'); + $urls->register('forum-topic', '/forum/topic.php', ['t' => '<topic>', 'page' => '<page>']); + $urls->register('forum-topic-create', '/forum/posting.php', ['f' => '<forum>']); + $urls->register('forum-topic-bump', '/forum/topic.php', ['t' => '<topic>', 'm' => 'bump', 'csrf' => '<csrf>']); + $urls->register('forum-topic-lock', '/forum/topic.php', ['t' => '<topic>', 'm' => 'lock', 'csrf' => '<csrf>']); + $urls->register('forum-topic-unlock', '/forum/topic.php', ['t' => '<topic>', 'm' => 'unlock', 'csrf' => '<csrf>']); + $urls->register('forum-topic-delete', '/forum/topic.php', ['t' => '<topic>', 'm' => 'delete', 'csrf' => '<csrf>']); + $urls->register('forum-topic-restore', '/forum/topic.php', ['t' => '<topic>', 'm' => 'restore', 'csrf' => '<csrf>']); + $urls->register('forum-topic-nuke', '/forum/topic.php', ['t' => '<topic>', 'm' => 'nuke', 'csrf' => '<csrf>']); + $urls->register('forum-post', '/forum/topic.php', ['p' => '<post>'], 'p<post>'); + $urls->register('forum-post-create', '/forum/posting.php', ['t' => '<topic>']); + $urls->register('forum-post-delete', '/forum/post.php', ['p' => '<post>', 'm' => 'delete']); + $urls->register('forum-post-restore', '/forum/post.php', ['p' => '<post>', 'm' => 'restore']); + $urls->register('forum-post-nuke', '/forum/post.php', ['p' => '<post>', 'm' => 'nuke']); + $urls->register('forum-post-quote', '/forum/posting.php', ['q' => '<post>']); + $urls->register('forum-post-edit', '/forum/posting.php', ['p' => '<post>', 'm' => 'edit']); + + $urls->register('user-list', '/members.php', ['r' => '<role>', 'ss' => '<sort>', 'sd' => '<direction>', 'p' => '<page>']); + + $urls->register('user-profile', '/profile.php', ['u' => '<user>']); + $urls->register('user-profile-forum-topics', '/profile.php', ['u' => '<user>', 'm' => 'forum-topics']); + $urls->register('user-profile-forum-posts', '/profile.php', ['u' => '<user>', 'm' => 'forum-posts']); + $urls->register('user-profile-edit', '/profile.php', ['u' => '<user>', 'edit' => '1']); + $urls->register('user-account-standing', '/profile.php', ['u' => '<user>'], 'account-standing'); + + $urls->register('settings-index', '/settings'); + $urls->register('settings-account', '/settings/account.php'); + $urls->register('settings-sessions', '/settings/sessions.php', ['p' => '<page>']); + $urls->register('settings-logs', '/settings/logs.php'); + $urls->register('settings-logs-logins', '/settings/logs.php', ['ap' => '<account-page>', 'hp' => '<page>'], 'login-history'); + $urls->register('settings-logs-account', '/settings/logs.php', ['hp' => '<logins-page>', 'ap' => '<page>'], 'account-log'); + $urls->register('settings-data', '/settings/data.php'); + + $urls->register('comment-create', '/comments.php', ['m' => 'create', 'return' => '<return>']); + $urls->register('comment-vote', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'vote', 'v' => '<vote>', 'return' => '<return>']); + $urls->register('comment-delete', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'delete', 'return' => '<return>']); + $urls->register('comment-restore', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'restore', 'return' => '<return>']); + $urls->register('comment-pin', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'pin', 'return' => '<return>']); + $urls->register('comment-unpin', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'unpin', 'return' => '<return>']); + + $urls->register('manage-general-overview', '/manage/general'); + $urls->register('manage-general-logs', '/manage/general/logs.php', ['p' => '<page>']); + + $urls->register('manage-general-emoticons', '/manage/general/emoticons.php'); + $urls->register('manage-general-emoticon', '/manage/general/emoticon.php', ['e' => '<emote>']); + $urls->register('manage-general-emoticon-order-up', '/manage/general/emoticons.php', ['emote' => '<emote>', 'order' => 'd', 'csrf' => '<csrf>']); + $urls->register('manage-general-emoticon-order-down', '/manage/general/emoticons.php', ['emote' => '<emote>', 'order' => 'i', 'csrf' => '<csrf>']); + $urls->register('manage-general-emoticon-delete', '/manage/general/emoticons.php', ['emote' => '<emote>', 'delete' => '1', 'csrf' => '<csrf>']); + $urls->register('manage-general-emoticon-alias', '/manage/general/emoticons.php', ['emote' => '<emote>', 'alias' => '<string>', 'csrf' => '<csrf>']); + + $urls->register('manage-general-settings', '/manage/general/settings.php'); + $urls->register('manage-general-setting', '/manage/general/setting.php', ['name' => '<name>', 'type' => '<type>']); + $urls->register('manage-general-setting-delete', '/manage/general/setting-delete.php', ['name' => '<name>']); + + $urls->register('manage-forum-categories', '/manage/forum/index.php'); + $urls->register('manage-forum-category', '/manage/forum/category.php', ['f' => '<forum>']); + $urls->register('manage-forum-topic-redirs', '/manage/forum/redirs.php', ['p' => '<page>']); + $urls->register('manage-forum-topic-redirs-create', '/manage/forum/redirs.php'); + $urls->register('manage-forum-topic-redirs-nuke', '/manage/forum/redirs.php', ['m' => 'explode', 't' => '<topic>', 'csrf' => '<csrf>']); + + $urls->register('manage-changelog-changes', '/manage/changelog', ['p' => '<page>']); + $urls->register('manage-changelog-change', '/manage/changelog/change.php', ['c' => '<change>']); + $urls->register('manage-changelog-change-delete', '/manage/changelog/change.php', ['c' => '<change>', 'delete' => '1', 'csrf' => '<csrf>']); + $urls->register('manage-changelog-tags', '/manage/changelog/tags.php'); + $urls->register('manage-changelog-tag', '/manage/changelog/tag.php', ['t' => '<tag>']); + $urls->register('manage-changelog-tag-delete', '/manage/changelog/tag.php', ['t' => '<tag>', 'delete' => '1', 'csrf' => '<csrf>']); + + $urls->register('manage-news-categories', '/manage/news/categories.php', ['p' => '<page>']); + $urls->register('manage-news-category', '/manage/news/category.php', ['c' => '<category>']); + $urls->register('manage-news-category-delete', '/manage/news/category.php', ['c' => '<category>', 'delete' => '1', 'csrf' => '<csrf>']); + $urls->register('manage-news-posts', '/manage/news/posts.php', ['p' => '<page>']); + $urls->register('manage-news-post', '/manage/news/post.php', ['p' => '<post>']); + $urls->register('manage-news-post-delete', '/manage/news/post.php', ['p' => '<post>', 'delete' => '1', 'csrf' => '<csrf>']); + + $urls->register('manage-users', '/manage/users', ['p' => '<page>']); + $urls->register('manage-user', '/manage/users/user.php', ['u' => '<user>']); + $urls->register('manage-users-warnings', '/manage/users/warnings.php', ['u' => '<user>', 'p' => '<page>']); + $urls->register('manage-users-warning', '/manage/users/warning.php', ['u' => '<user>']); + $urls->register('manage-users-warning-delete', '/manage/users/warning.php', ['w' => '<warning>', 'delete' => '1', 'csrf' => '<csrf>']); + $urls->register('manage-users-notes', '/manage/users/notes.php', ['u' => '<user>', 'p' => '<page>']); + $urls->register('manage-users-note', '/manage/users/note.php', ['n' => '<note>', 'u' => '<user>']); + $urls->register('manage-users-note-delete', '/manage/users/note.php', ['n' => '<note>', 'delete' => '1', 'csrf' => '<csrf>']); + $urls->register('manage-users-bans', '/manage/users/bans.php', ['u' => '<user>', 'p' => '<page>']); + $urls->register('manage-users-ban', '/manage/users/ban.php', ['u' => '<user>']); + $urls->register('manage-users-ban-delete', '/manage/users/ban.php', ['b' => '<ban>', 'delete' => '1', 'csrf' => '<csrf>']); + + $urls->register('manage-roles', '/manage/users/roles.php', ['p' => '<page>']); + $urls->register('manage-role', '/manage/users/role.php', ['r' => '<role>']); + } + #[Route('GET', '/index.php')] public function getIndexPHP($response): void { $response->redirect($this->urls->format('index'), true); @@ -151,6 +265,7 @@ class LegacyRoutes extends RouteHandler { } #[Route('GET', '/manage')] + #[URLInfo('manage-index', '/manage')] public function getManageIndex($response): void { $response->redirect($this->urls->format('manage-general-overview')); } diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 2c12d75..4b356c1 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -6,9 +6,6 @@ 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\IRouter; use Sasae\SasaeEnvironment; use Misuzu\Template; use Misuzu\Auth\AuthContext; @@ -43,18 +40,14 @@ use Misuzu\Users\Assets\AssetsRoutes; class MisuzuContext { private IDbConnection $dbConn; private IConfig $config; - private HttpFx $router; private SasaeEnvironment $templating; - private URLRegistry $urls; private AuditLog $auditLog; private Counters $counters; + private Emotes $emotes; - private Changelog $changelog; - private News $news; - private Comments $comments; private AuthContext $authCtx; @@ -67,13 +60,13 @@ class MisuzuContext { private AuthInfo $authInfo; private SiteInfo $siteInfo; + // this probably shouldn't be available + private URLRegistry $urls; + public function __construct(IDbConnection $dbConn, IConfig $config) { $this->dbConn = $dbConn; $this->config = $config; - $this->urls = new URLRegistry; - $this->registerURLs(); - $this->perms = new Permissions($dbConn); $this->authInfo = new AuthInfo($this->perms); $this->siteInfo = new SiteInfo($config->scopeTo('site')); @@ -116,10 +109,6 @@ class MisuzuContext { return $this->config; } - public function getRouter(): IRouter { - return $this->router; - } - public function getEmotes(): Emotes { return $this->emotes; } @@ -227,40 +216,13 @@ class MisuzuContext { Template::init($this->templating); } - public function startRouter(): void { - $this->router = new HttpFx; - $this->router->use('/', function($response) { - $response->setPoweredBy('Misuzu'); - }); + public function createRouting(): RoutingContext { + $routingCtx = new RoutingContext(); - $this->registerErrorPages(); - $this->registerHttpRoutes(); - } + $this->urls = $routingCtx->getURLs(); + $routingCtx->registerDefaultErrorPages(); - public function dispatchRouter(?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 { - $this->router->register(new HomeRoutes( + $routingCtx->register(new HomeRoutes( $this->config, $this->dbConn, $this->siteInfo, @@ -272,15 +234,15 @@ class MisuzuContext { $this->usersCtx )); - $this->router->register(new AssetsRoutes( + $routingCtx->register(new AssetsRoutes( $this->authInfo, $this->urls, $this->usersCtx )); - $this->router->register(new InfoRoutes); + $routingCtx->register(new InfoRoutes); - $this->router->register(new NewsRoutes( + $routingCtx->register(new NewsRoutes( $this->siteInfo, $this->authInfo, $this->urls, @@ -289,7 +251,7 @@ class MisuzuContext { $this->comments )); - $this->router->register(new ChangelogRoutes( + $routingCtx->register(new ChangelogRoutes( $this->siteInfo, $this->urls, $this->changelog, @@ -298,7 +260,7 @@ class MisuzuContext { $this->comments )); - $this->router->register(new SharpChatRoutes( + $routingCtx->register(new SharpChatRoutes( $this->config->scopeTo('sockChat'), $this->urls, $this->usersCtx, @@ -308,148 +270,15 @@ class MisuzuContext { $this->authInfo )); - $this->router->register(new SatoriRoutes( + $routingCtx->register(new SatoriRoutes( $this->config->scopeTo('satori'), $this->usersCtx, $this->forumCtx, $this->profileFields )); - $this->router->register(new LegacyRoutes($this->urls)); - } + $routingCtx->register(new LegacyRoutes($this->urls)); - public function registerURLs(): void { - // eventually this should be handled by context classes - $this->urls->register('index', '/'); - $this->urls->register('info', '/info/<title>'); - - $this->urls->register('search-index', '/search.php'); - $this->urls->register('search-query', '/search.php', ['q' => '<query>'], '<section>'); - - $this->urls->register('auth-login', '/auth/login.php', ['username' => '<username>', 'redirect' => '<redirect>']); - $this->urls->register('auth-login-welcome', '/auth/login.php', ['welcome' => '1', 'username' => '<username>']); - $this->urls->register('auth-register', '/auth/register.php'); - $this->urls->register('auth-forgot', '/auth/password.php'); - $this->urls->register('auth-reset', '/auth/password.php', ['user' => '<user>']); - $this->urls->register('auth-logout', '/auth/logout.php', ['csrf' => '<csrf>']); - $this->urls->register('auth-resolve-user', '/auth/login.php', ['resolve' => '1', 'name' => '<username>']); - $this->urls->register('auth-two-factor', '/auth/twofactor.php', ['token' => '<token>']); - $this->urls->register('auth-revert', '/auth/revert.php', ['csrf' => '<csrf>']); - - $this->urls->register('changelog-index', '/changelog', ['date' => '<date>', 'user' => '<user>', 'tags' => '<tags>', 'p' => '<page>']); - $this->urls->register('changelog-feed-rss', '/changelog.rss'); - $this->urls->register('changelog-feed-atom', '/changelog.atom'); - $this->urls->register('changelog-change', '/changelog/change/<change>'); - $this->urls->register('changelog-change-comments', '/changelog/change/<change>', fragment: 'comments'); - - $this->urls->register('news-index', '/news', ['p' => '<page>']); - $this->urls->register('news-category', '/news/<category>', ['p' => '<page>']); - $this->urls->register('news-post', '/news/post/<post>'); - $this->urls->register('news-post-comments', '/news/post/<post>', fragment: 'comments'); - $this->urls->register('news-feed-rss', '/news.rss'); - $this->urls->register('news-category-feed-rss', '/news/<category>.rss'); - $this->urls->register('news-feed-atom', '/news.atom'); - $this->urls->register('news-category-feed-atom', '/news/<category>.atom'); - - $this->urls->register('forum-index', '/forum'); - $this->urls->register('forum-leaderboard', '/forum/leaderboard.php', ['id' => '<id>', 'mode' => '<mode>']); - $this->urls->register('forum-mark-global', '/forum/index.php', ['m' => 'mark']); - $this->urls->register('forum-mark-single', '/forum/index.php', ['m' => 'mark', 'f' => '<forum>']); - $this->urls->register('forum-topic-new', '/forum/posting.php', ['f' => '<forum>']); - $this->urls->register('forum-reply-new', '/forum/posting.php', ['t' => '<topic>']); - $this->urls->register('forum-category', '/forum/forum.php', ['f' => '<forum>', 'p' => '<page>']); - $this->urls->register('forum-category-root', '/forum/index.php', fragment: '<forum>'); - $this->urls->register('forum-topic', '/forum/topic.php', ['t' => '<topic>', 'page' => '<page>']); - $this->urls->register('forum-topic-create', '/forum/posting.php', ['f' => '<forum>']); - $this->urls->register('forum-topic-bump', '/forum/topic.php', ['t' => '<topic>', 'm' => 'bump', 'csrf' => '<csrf>']); - $this->urls->register('forum-topic-lock', '/forum/topic.php', ['t' => '<topic>', 'm' => 'lock', 'csrf' => '<csrf>']); - $this->urls->register('forum-topic-unlock', '/forum/topic.php', ['t' => '<topic>', 'm' => 'unlock', 'csrf' => '<csrf>']); - $this->urls->register('forum-topic-delete', '/forum/topic.php', ['t' => '<topic>', 'm' => 'delete', 'csrf' => '<csrf>']); - $this->urls->register('forum-topic-restore', '/forum/topic.php', ['t' => '<topic>', 'm' => 'restore', 'csrf' => '<csrf>']); - $this->urls->register('forum-topic-nuke', '/forum/topic.php', ['t' => '<topic>', 'm' => 'nuke', 'csrf' => '<csrf>']); - $this->urls->register('forum-post', '/forum/topic.php', ['p' => '<post>'], 'p<post>'); - $this->urls->register('forum-post-create', '/forum/posting.php', ['t' => '<topic>']); - $this->urls->register('forum-post-delete', '/forum/post.php', ['p' => '<post>', 'm' => 'delete']); - $this->urls->register('forum-post-restore', '/forum/post.php', ['p' => '<post>', 'm' => 'restore']); - $this->urls->register('forum-post-nuke', '/forum/post.php', ['p' => '<post>', 'm' => 'nuke']); - $this->urls->register('forum-post-quote', '/forum/posting.php', ['q' => '<post>']); - $this->urls->register('forum-post-edit', '/forum/posting.php', ['p' => '<post>', 'm' => 'edit']); - - $this->urls->register('user-list', '/members.php', ['r' => '<role>', 'ss' => '<sort>', 'sd' => '<direction>', 'p' => '<page>']); - - $this->urls->register('user-profile', '/profile.php', ['u' => '<user>']); - $this->urls->register('user-profile-forum-topics', '/profile.php', ['u' => '<user>', 'm' => 'forum-topics']); - $this->urls->register('user-profile-forum-posts', '/profile.php', ['u' => '<user>', 'm' => 'forum-posts']); - $this->urls->register('user-profile-edit', '/profile.php', ['u' => '<user>', 'edit' => '1']); - $this->urls->register('user-account-standing', '/profile.php', ['u' => '<user>'], 'account-standing'); - - $this->urls->register('user-avatar', '/assets/avatar/<user>', ['res' => '<res>']); - $this->urls->register('user-background', '/assets/profile-background/<user>'); - - $this->urls->register('settings-index', '/settings'); - $this->urls->register('settings-account', '/settings/account.php'); - $this->urls->register('settings-sessions', '/settings/sessions.php', ['p' => '<page>']); - $this->urls->register('settings-logs', '/settings/logs.php'); - $this->urls->register('settings-logs-logins', '/settings/logs.php', ['ap' => '<account-page>', 'hp' => '<page>'], 'login-history'); - $this->urls->register('settings-logs-account', '/settings/logs.php', ['hp' => '<logins-page>', 'ap' => '<page>'], 'account-log'); - $this->urls->register('settings-data', '/settings/data.php'); - - $this->urls->register('comment-create', '/comments.php', ['m' => 'create', 'return' => '<return>']); - $this->urls->register('comment-vote', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'vote', 'v' => '<vote>', 'return' => '<return>']); - $this->urls->register('comment-delete', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'delete', 'return' => '<return>']); - $this->urls->register('comment-restore', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'restore', 'return' => '<return>']); - $this->urls->register('comment-pin', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'pin', 'return' => '<return>']); - $this->urls->register('comment-unpin', '/comments.php', ['c' => '<comment>', 'csrf' => '<csrf>', 'm' => 'unpin', 'return' => '<return>']); - - $this->urls->register('manage-index', '/manage'); - - $this->urls->register('manage-general-overview', '/manage/general'); - $this->urls->register('manage-general-logs', '/manage/general/logs.php', ['p' => '<page>']); - - $this->urls->register('manage-general-emoticons', '/manage/general/emoticons.php'); - $this->urls->register('manage-general-emoticon', '/manage/general/emoticon.php', ['e' => '<emote>']); - $this->urls->register('manage-general-emoticon-order-up', '/manage/general/emoticons.php', ['emote' => '<emote>', 'order' => 'd', 'csrf' => '<csrf>']); - $this->urls->register('manage-general-emoticon-order-down', '/manage/general/emoticons.php', ['emote' => '<emote>', 'order' => 'i', 'csrf' => '<csrf>']); - $this->urls->register('manage-general-emoticon-delete', '/manage/general/emoticons.php', ['emote' => '<emote>', 'delete' => '1', 'csrf' => '<csrf>']); - $this->urls->register('manage-general-emoticon-alias', '/manage/general/emoticons.php', ['emote' => '<emote>', 'alias' => '<string>', 'csrf' => '<csrf>']); - - $this->urls->register('manage-general-settings', '/manage/general/settings.php'); - $this->urls->register('manage-general-setting', '/manage/general/setting.php', ['name' => '<name>', 'type' => '<type>']); - $this->urls->register('manage-general-setting-delete', '/manage/general/setting-delete.php', ['name' => '<name>']); - - $this->urls->register('manage-forum-categories', '/manage/forum/index.php'); - $this->urls->register('manage-forum-category', '/manage/forum/category.php', ['f' => '<forum>']); - $this->urls->register('manage-forum-topic-redirs', '/manage/forum/redirs.php', ['p' => '<page>']); - $this->urls->register('manage-forum-topic-redirs-create', '/manage/forum/redirs.php'); - $this->urls->register('manage-forum-topic-redirs-nuke', '/manage/forum/redirs.php', ['m' => 'explode', 't' => '<topic>', 'csrf' => '<csrf>']); - - $this->urls->register('manage-changelog-changes', '/manage/changelog', ['p' => '<page>']); - $this->urls->register('manage-changelog-change', '/manage/changelog/change.php', ['c' => '<change>']); - $this->urls->register('manage-changelog-change-delete', '/manage/changelog/change.php', ['c' => '<change>', 'delete' => '1', 'csrf' => '<csrf>']); - $this->urls->register('manage-changelog-tags', '/manage/changelog/tags.php'); - $this->urls->register('manage-changelog-tag', '/manage/changelog/tag.php', ['t' => '<tag>']); - $this->urls->register('manage-changelog-tag-delete', '/manage/changelog/tag.php', ['t' => '<tag>', 'delete' => '1', 'csrf' => '<csrf>']); - - $this->urls->register('manage-news-categories', '/manage/news/categories.php', ['p' => '<page>']); - $this->urls->register('manage-news-category', '/manage/news/category.php', ['c' => '<category>']); - $this->urls->register('manage-news-category-delete', '/manage/news/category.php', ['c' => '<category>', 'delete' => '1', 'csrf' => '<csrf>']); - $this->urls->register('manage-news-posts', '/manage/news/posts.php', ['p' => '<page>']); - $this->urls->register('manage-news-post', '/manage/news/post.php', ['p' => '<post>']); - $this->urls->register('manage-news-post-delete', '/manage/news/post.php', ['p' => '<post>', 'delete' => '1', 'csrf' => '<csrf>']); - - $this->urls->register('manage-users', '/manage/users', ['p' => '<page>']); - $this->urls->register('manage-user', '/manage/users/user.php', ['u' => '<user>']); - $this->urls->register('manage-users-warnings', '/manage/users/warnings.php', ['u' => '<user>', 'p' => '<page>']); - $this->urls->register('manage-users-warning', '/manage/users/warning.php', ['u' => '<user>']); - $this->urls->register('manage-users-warning-delete', '/manage/users/warning.php', ['w' => '<warning>', 'delete' => '1', 'csrf' => '<csrf>']); - $this->urls->register('manage-users-notes', '/manage/users/notes.php', ['u' => '<user>', 'p' => '<page>']); - $this->urls->register('manage-users-note', '/manage/users/note.php', ['n' => '<note>', 'u' => '<user>']); - $this->urls->register('manage-users-note-delete', '/manage/users/note.php', ['n' => '<note>', 'delete' => '1', 'csrf' => '<csrf>']); - $this->urls->register('manage-users-bans', '/manage/users/bans.php', ['u' => '<user>', 'p' => '<page>']); - $this->urls->register('manage-users-ban', '/manage/users/ban.php', ['u' => '<user>']); - $this->urls->register('manage-users-ban-delete', '/manage/users/ban.php', ['b' => '<ban>', 'delete' => '1', 'csrf' => '<csrf>']); - - $this->urls->register('manage-roles', '/manage/users/roles.php', ['p' => '<page>']); - $this->urls->register('manage-role', '/manage/users/role.php', ['r' => '<role>']); + return $routingCtx; } } diff --git a/src/News/NewsRoutes.php b/src/News/NewsRoutes.php index 97074d7..d3a1ffa 100644 --- a/src/News/NewsRoutes.php +++ b/src/News/NewsRoutes.php @@ -19,6 +19,7 @@ use Misuzu\Feeds\FeedItem; use Misuzu\Feeds\AtomFeedSerializer; use Misuzu\Feeds\RssFeedSerializer; use Misuzu\Parsers\Parser; +use Misuzu\URLs\URLInfo; use Misuzu\URLs\URLRegistry; use Misuzu\Users\UsersContext; @@ -91,6 +92,7 @@ class NewsRoutes extends RouteHandler { } #[Route('GET', '/news')] + #[URLInfo('news-index', '/news', ['p' => '<page>'])] public function getIndex() { $categories = $this->news->getCategories(hidden: false); @@ -108,16 +110,19 @@ class NewsRoutes extends RouteHandler { } #[Route('GET', '/news.rss')] + #[URLInfo('news-feed-rss', '/news.rss')] public function getFeedRss($response) { return $this->getFeed($response, 'rss'); } #[Route('GET', '/news.atom')] + #[URLInfo('news-feed-atom', '/news.atom')] public function getFeedAtom($response) { return $this->getFeed($response, 'atom'); } #[Route('GET', '/news/:category')] + #[URLInfo('news-category', '/news/<category>', ['p' => '<page>'])] public function getCategory($response, $request, string $fileName) { $categoryId = pathinfo($fileName, PATHINFO_FILENAME); $type = pathinfo($fileName, PATHINFO_EXTENSION); @@ -148,15 +153,19 @@ class NewsRoutes extends RouteHandler { ]); } + #[URLInfo('news-category-feed-rss', '/news/<category>.rss')] private function getCategoryFeedRss($response, $request, NewsCategoryInfo $categoryInfo) { return $this->getFeed($response, 'rss', $categoryInfo); } + #[URLInfo('news-category-feed-atom', '/news/<category>.atom')] private function getCategoryFeedAtom($response, $request, NewsCategoryInfo $categoryInfo) { return $this->getFeed($response, 'atom', $categoryInfo); } #[Route('GET', '/news/post/:id')] + #[URLInfo('news-post', '/news/post/<post>')] + #[URLInfo('news-post-comments', '/news/post/<post>', fragment: 'comments')] public function getPost($response, $request, string $postId) { try { $postInfo = $this->news->getPost($postId); diff --git a/src/RoutingContext.php b/src/RoutingContext.php new file mode 100644 index 0000000..d1f35f8 --- /dev/null +++ b/src/RoutingContext.php @@ -0,0 +1,60 @@ +<?php +namespace Misuzu; + +use Index\Http\HttpFx; +use Index\Http\HttpRequest; +use Index\Routing\IRouter; +use Index\Routing\IRouteHandler; +use Misuzu\URLs\IURLSource; +use Misuzu\URLs\URLInfo; +use Misuzu\URLs\URLRegistry; + +class RoutingContext { + private URLRegistry $urls; + private HttpFx $router; + + public function __construct() { + $this->urls = new URLRegistry; + $this->router = new HttpFx; + $this->router->use('/', fn($resp) => $resp->setPoweredBy('Misuzu')); + } + + public function getURLs(): URLRegistry { + return $this->urls; + } + + public function getRouter(): IRouter { + return $this->router; + } + + public function registerDefaultErrorPages(): void { + $this->router->addErrorHandler(400, fn($resp) => $resp->setContent(Template::renderRaw('errors.400'))); + $this->router->addErrorHandler(403, fn($resp) => $resp->setContent(Template::renderRaw('errors.403'))); + $this->router->addErrorHandler(404, fn($resp) => $resp->setContent(Template::renderRaw('errors.404'))); + $this->router->addErrorHandler(500, fn($resp) => $resp->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html'))); + $this->router->addErrorHandler(503, fn($resp) => $resp->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html'))); + } + + public static function registerSimpleErrorPages(IRouter $router, string $path): void { + if($router instanceof HttpFx) + $router->use($path, function() use($router) { + $router->addErrorHandler(400, fn($resp) => $resp->setContent('HTTP 400')); + $router->addErrorHandler(403, fn($resp) => $resp->setContent('HTTP 403')); + $router->addErrorHandler(404, fn($resp) => $resp->setContent('HTTP 404')); + $router->addErrorHandler(500, fn($resp) => $resp->setContent('HTTP 500')); + $router->addErrorHandler(503, fn($resp) => $resp->setContent('HTTP 503')); + }); + } + + public function register(IRouteHandler|IURLSource $handler): void { + if($handler instanceof IRouteHandler) + $this->router->register($handler); + if($handler instanceof IURLSource) + $handler->registerURLs($this->urls); + URLInfo::handleAttributes($this->urls, $handler); + } + + public function dispatch(?HttpRequest $request = null): void { + $this->router->dispatch($request); + } +} diff --git a/src/Satori/SatoriRoutes.php b/src/Satori/SatoriRoutes.php index c82942a..31a27e0 100644 --- a/src/Satori/SatoriRoutes.php +++ b/src/Satori/SatoriRoutes.php @@ -7,7 +7,7 @@ use Index\Routing\IRouter; use Index\Routing\IRouteHandler; use Index\Routing\Route; use Misuzu\Pagination; -use Misuzu\Tools; +use Misuzu\RoutingContext; use Misuzu\Config\IConfig; use Misuzu\Forum\ForumContext; use Misuzu\Profile\ProfileFields; @@ -22,7 +22,7 @@ final class SatoriRoutes implements IRouteHandler { ) {} public function registerRoutes(IRouter $router): void { - Tools::simplifyErrorPages($router, '/_satori'); + RoutingContext::registerSimpleErrorPages($router, '/_satori'); Route::handleAttributes($router, $this); } diff --git a/src/SharpChat/SharpChatRoutes.php b/src/SharpChat/SharpChatRoutes.php index bd5b5f3..18ca988 100644 --- a/src/SharpChat/SharpChatRoutes.php +++ b/src/SharpChat/SharpChatRoutes.php @@ -6,7 +6,7 @@ use Index\Colour\Colour; use Index\Routing\IRouter; use Index\Routing\IRouteHandler; use Index\Routing\Route; -use Misuzu\Tools; +use Misuzu\RoutingContext; use Misuzu\Auth\AuthContext; use Misuzu\Auth\AuthInfo; use Misuzu\Auth\Sessions; @@ -33,7 +33,7 @@ final class SharpChatRoutes implements IRouteHandler { } public function registerRoutes(IRouter $router): void { - Tools::simplifyErrorPages($router, '/_sockchat'); + RoutingContext::registerSimpleErrorPages($router, '/_sockchat'); Route::handleAttributes($router, $this); } diff --git a/src/Tools.php b/src/Tools.php index a5bc94c..af778a8 100644 --- a/src/Tools.php +++ b/src/Tools.php @@ -68,17 +68,6 @@ final class Tools { return $day <= $days; } - public static function simplifyErrorPages(IRouter $router, string $path): void { - if($router instanceof HttpFx) - $router->use($path, function() use($router) { - $router->addErrorHandler(400, fn($resp) => $resp->setContent('HTTP 400')); - $router->addErrorHandler(403, fn($resp) => $resp->setContent('HTTP 403')); - $router->addErrorHandler(404, fn($resp) => $resp->setContent('HTTP 404')); - $router->addErrorHandler(500, fn($resp) => $resp->setContent('HTTP 500')); - $router->addErrorHandler(503, fn($resp) => $resp->setContent('HTTP 503')); - }); - } - // for your own sanity and the sanity of others, keep this one at the bottom public static function countryName(string $code): string { return match(strtoupper($code)) { diff --git a/src/URLs/IURLSource.php b/src/URLs/IURLSource.php new file mode 100644 index 0000000..c01884b --- /dev/null +++ b/src/URLs/IURLSource.php @@ -0,0 +1,6 @@ +<?php +namespace Misuzu\URLs; + +interface IURLSource { + public function registerURLs(URLRegistry $urls): void; +} diff --git a/src/URLs/URLInfo.php b/src/URLs/URLInfo.php index b82734d..176d7cc 100644 --- a/src/URLs/URLInfo.php +++ b/src/URLs/URLInfo.php @@ -1,13 +1,23 @@ <?php namespace Misuzu\URLs; +use Attribute; +use InvalidArgumentException; +use ReflectionObject; + +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] class URLInfo { public function __construct( private string $name, private string $path, - private array $query, - private string $fragment - ) {} + private array $query = [], + private string $fragment = '' + ) { + if($path === '') + throw new InvalidArgumentException('$path may not be empty.'); + if($path[0] !== '/') + throw new InvalidArgumentException('$path must begin with /.'); + } public function getName(): string { return $this->name; @@ -32,4 +42,15 @@ class URLInfo { public function getFragment(): string { return $this->fragment; } + + public static function handleAttributes(URLRegistry $urls, object $source): void { + $objectInfo = new ReflectionObject($source); + $methodInfos = $objectInfo->getMethods(); + + foreach($methodInfos as $methodInfo) { + $attrInfos = $methodInfo->getAttributes(URLInfo::class); + foreach($attrInfos as $attrInfo) + $urls->registerInfo($attrInfo->newInstance()); + } + } } diff --git a/src/URLs/URLRegistry.php b/src/URLs/URLRegistry.php index e031b06..88188ae 100644 --- a/src/URLs/URLRegistry.php +++ b/src/URLs/URLRegistry.php @@ -9,14 +9,17 @@ class URLRegistry { public function register(string $name, string $path, array $query = [], string $fragment = ''): void { if(array_key_exists($name, $this->infos)) throw new InvalidArgumentException('A URL with $name has already been registered.'); - if($path === '') - throw new InvalidArgumentException('$path may not be empty.'); - if($path[0] !== '/') - throw new InvalidArgumentException('$path must begin with /.'); $this->infos[$name] = new URLInfo($name, $path, $query, $fragment); } + public function registerInfo(URLInfo $info): void { + if(array_key_exists($info->getName(), $this->infos)) + throw new InvalidArgumentException('A URL with $name has already been registered.'); + + $this->infos[$info->getName()] = $info; + } + public function format(string $name, array $vars = [], bool $spacesAsPlus = false): string { if(!array_key_exists($name, $this->infos)) return ''; diff --git a/src/Users/Assets/AssetsRoutes.php b/src/Users/Assets/AssetsRoutes.php index 296caf9..d14a230 100644 --- a/src/Users/Assets/AssetsRoutes.php +++ b/src/Users/Assets/AssetsRoutes.php @@ -7,6 +7,7 @@ use Index\Routing\Route; use Index\Routing\RouteHandler; use Misuzu\Perm; use Misuzu\Auth\AuthInfo; +use Misuzu\URLs\URLInfo; use Misuzu\URLs\URLRegistry; use Misuzu\Users\UsersContext; use Misuzu\Users\UserInfo; @@ -30,6 +31,7 @@ class AssetsRoutes extends RouteHandler { #[Route('GET', '/assets/avatar')] #[Route('GET', '/assets/avatar/:filename')] + #[URLInfo('user-avatar', '/assets/avatar/<user>', ['res' => '<res>'])] public function getAvatar($response, $request, string $fileName = '') { $userId = pathinfo($fileName, PATHINFO_FILENAME); $assetInfo = new StaticUserImageAsset(MSZ_PUBLIC . '/images/no-avatar.png', MSZ_PUBLIC); @@ -52,6 +54,7 @@ class AssetsRoutes extends RouteHandler { #[Route('GET', '/assets/profile-background')] #[Route('GET', '/assets/profile-background/:filename')] + #[URLInfo('user-background', '/assets/profile-background/<user>')] public function getProfileBackground($response, $request, string $fileName = '') { $userId = pathinfo($fileName, PATHINFO_FILENAME);