misuzu/src/url.php

234 lines
13 KiB
PHP

<?php
// URL FORMATTING
// [0] => Path part of URL.
// [1] => Query part of URL.
// [2] => Fragment part of URL.
//
// text surrounded by < and > will be replaced accordingly to an array of variables supplied to the format function
// text surrounded by [ and ] will be replaced by the constant/define of that name
// text surrounded by { and } will be replaced by a CSRF token with the given text as its realm, this will have no effect in a sessionless environment
define('MSZ_URLS', [
'index' => ['/'],
'info' => ['/info/<title>'],
'search-index' => ['/search.php'],
'search-query' => ['/search.php', ['q' => '<query>']],
'auth-login' => ['/auth/login.php', ['username' => '<username>', 'redirect' => '<redirect>']],
'auth-login-welcome' => ['/auth/login.php', ['welcome' => '1', 'username' => '<username>']],
'auth-register' => ['/auth/register.php'],
'auth-forgot' => ['/auth/password.php'],
'auth-reset' => ['/auth/password.php', ['user' => '<user>']],
'auth-logout' => ['/auth/logout.php', ['csrf' => '{csrf}']],
'auth-resolve-user' => ['/auth/login.php', ['resolve' => '1', 'name' => '<username>']],
'auth-two-factor' => ['/auth/twofactor.php', ['token' => '<token>']],
'changelog-index' => ['/changelog', ['date' => '<date>', 'user' => '<user>', 'tags' => '<tags>', 'p' => '<page>']],
'changelog-feed-rss' => ['/changelog.rss'],
'changelog-feed-atom' => ['/changelog.atom'],
'changelog-change' => ['/changelog/change/<change>'],
'changelog-change-comments' => ['/changelog/change/<change>', [], 'comments'],
'news-index' => ['/news', ['p' => '<page>']],
'news-category' => ['/news/<category>', ['p' => '<page>']],
'news-post' => ['/news/post/<post>'],
'news-post-comments' => ['/news/post/<post>', [], 'comments'],
'news-feed-rss' => ['/news.rss'],
'news-category-feed-rss' => ['/news/<category>.rss'],
'news-feed-atom' => ['/news.atom'],
'news-category-feed-atom' => ['/news/<category>.atom'],
'forum-index' => ['/forum'],
'forum-leaderboard' => ['/forum/leaderboard.php', ['id' => '<id>', 'mode' => '<mode>']],
'forum-mark-global' => ['/forum/mark-as-read'],
'forum-mark-single' => ['/forum/mark-as-read', ['forum' => '<forum>']],
'forum-topic-new' => ['/forum/posting.php', ['f' => '<forum>']],
'forum-reply-new' => ['/forum/posting.php', ['t' => '<topic>']],
'forum-category' => ['/forum/forum.php', ['f' => '<forum>', 'p' => '<page>']],
'forum-topic' => ['/forum/topic.php', ['t' => '<topic>', 'page' => '<page>']],
'forum-topic-create' => ['/forum/posting.php', ['f' => '<forum>']],
'forum-topic-bump' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'bump', 'csrf' => '{csrf}']],
'forum-topic-lock' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'lock', 'csrf' => '{csrf}']],
'forum-topic-unlock' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'unlock', 'csrf' => '{csrf}']],
'forum-topic-delete' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'delete', 'csrf' => '{csrf}']],
'forum-topic-restore' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'restore', 'csrf' => '{csrf}']],
'forum-topic-nuke' => ['/forum/topic.php', ['t' => '<topic>', 'm' => 'nuke', 'csrf' => '{csrf}']],
'forum-post' => ['/forum/topic.php', ['p' => '<post>'], '<post_fragment>'],
'forum-post-create' => ['/forum/posting.php', ['t' => '<topic>']],
'forum-post-delete' => ['/forum/post.php', ['p' => '<post>', 'm' => 'delete']],
'forum-post-restore' => ['/forum/post.php', ['p' => '<post>', 'm' => 'restore']],
'forum-post-nuke' => ['/forum/post.php', ['p' => '<post>', 'm' => 'nuke']],
'forum-post-quote' => ['/forum/posting.php', ['q' => '<post>']],
'forum-post-edit' => ['/forum/posting.php', ['p' => '<post>', 'm' => 'edit']],
'user-list' => ['/members.php', ['r' => '<role>', 'ss' => '<sort>', 'sd' => '<direction>', 'p' => '<page>']],
'user-profile' => ['/profile.php', ['u' => '<user>']],
'user-profile-forum-topics' => ['/profile.php', ['u' => '<user>', 'm' => 'forum-topics']],
'user-profile-forum-posts' => ['/profile.php', ['u' => '<user>', 'm' => 'forum-posts']],
'user-profile-edit' => ['/profile.php', ['u' => '<user>', 'edit' => '1']],
'user-account-standing' => ['/profile.php', ['u' => '<user>'], 'account-standing'],
'user-avatar' => ['/assets/avatar/<user>', ['res' => '<res>']],
'user-background' => ['/assets/profile-background/<user>'],
'settings-index' => ['/settings'],
'settings-account' => ['/settings/account.php'],
'settings-sessions' => ['/settings/sessions.php'],
'settings-logs' => ['/settings/logs.php'],
'settings-data' => ['/settings/data.php'],
'comment-create' => ['/comments.php', ['m' => 'create']],
'comment-vote' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'vote', 'v' => '<vote>']],
'comment-delete' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'delete']],
'comment-restore' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'restore']],
'comment-pin' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'pin']],
'comment-unpin' => ['/comments.php', ['c' => '<comment>', 'csrf' => '{csrf}', 'm' => 'unpin']],
'manage-index' => ['/manage'],
'manage-general-overview' => ['/manage/general'],
'manage-general-logs' => ['/manage/general/logs.php'],
'manage-general-blacklist' => ['/manage/general/blacklist.php'],
'manage-general-emoticons' => ['/manage/general/emoticons.php'],
'manage-general-emoticon' => ['/manage/general/emoticon.php', ['e' => '<emote>']],
'manage-general-emoticon-order-up' => ['/manage/general/emoticons.php', ['emote' => '<emote>', 'order' => 'd', 'csrf' => '{token}']],
'manage-general-emoticon-order-down'=> ['/manage/general/emoticons.php', ['emote' => '<emote>', 'order' => 'i', 'csrf' => '{token}']],
'manage-general-emoticon-delete' => ['/manage/general/emoticons.php', ['emote' => '<emote>', 'delete' => '1', 'csrf' => '{token}']],
'manage-general-emoticon-alias' => ['/manage/general/emoticons.php', ['emote' => '<emote>', 'alias' => '<string>', 'csrf' => '{token}']],
'manage-general-settings' => ['/manage/general/settings.php'],
'manage-general-setting' => ['/manage/general/setting.php', ['name' => '<name>', 'type' => '<type>']],
'manage-general-setting-delete' => ['/manage/general/setting-delete.php', ['name' => '<name>']],
'manage-forum-categories' => ['/manage/forum/index.php'],
'manage-forum-category' => ['/manage/forum/category.php', ['f' => '<forum>']],
'manage-changelog-changes' => ['/manage/changelog'],
'manage-changelog-change' => ['/manage/changelog/change.php', ['c' => '<change>']],
'manage-changelog-tags' => ['/manage/changelog/tags.php'],
'manage-changelog-tag' => ['/manage/changelog/tag.php', ['t' => '<tag>']],
'manage-news-categories' => ['/manage/news/categories.php'],
'manage-news-category' => ['/manage/news/category.php', ['c' => '<category>']],
'manage-news-posts' => ['/manage/news/posts.php'],
'manage-news-post' => ['/manage/news/post.php', ['p' => '<post>']],
'manage-users' => ['/manage/users'],
'manage-user' => ['/manage/users/user.php', ['u' => '<user>']],
'manage-users-reports' => ['/manage/users/reports.php', ['u' => '<user>']],
'manage-users-report' => ['/manage/users/report.php', ['r' => '<report>']],
'manage-users-warnings' => ['/manage/users/warnings.php', ['u' => '<user>']],
'manage-users-warning-delete' => ['/manage/users/warnings.php', ['w' => '<warning>', 'delete' => '1', 'csrf' => '{csrf}']],
'manage-roles' => ['/manage/users/roles.php'],
'manage-role' => ['/manage/users/role.php', ['r' => '<role>']],
]);
function url(string $name, array $variables = []): string {
if(!array_key_exists($name, MSZ_URLS)) {
return '';
}
$info = MSZ_URLS[$name];
if(!isset($info[0]) || !is_string($info[0])) {
return '';
}
$splitUrl = explode('/', $info[0]);
for($i = 0; $i < count($splitUrl); $i++) {
$splitUrl[$i] = url_variable($splitUrl[$i], $variables);
}
$url = implode('/', $splitUrl);
if(!empty($info[1]) && is_array($info[1])) {
$url .= '?';
foreach($info[1] as $key => $value) {
$value = url_variable($value, $variables);
if(empty($value) || ($key === 'page' && $value < 2))
continue;
$url .= sprintf('%s=%s&', $key, $value);
}
$url = trim($url, '?&');
}
if(!empty($info[2]) && is_string($info[2])) {
$url .= rtrim(sprintf('#%s', url_variable($info[2], $variables)), '#');
}
return $url;
}
function redirect(string $url): void {
header('Location: ' . $url);
}
function url_redirect(string $name, array $variables = []): void {
redirect(url($name, $variables));
}
function url_variable(string $value, array $variables): string {
if(str_starts_with($value, '<') && str_ends_with($value, '>'))
return $variables[trim($value, '<>')] ?? '';
if(str_starts_with($value, '[') && str_ends_with($value, ']'))
return constant(trim($value, '[]'));
if(str_starts_with($value, '{') && str_ends_with($value, '}'))
return \Misuzu\CSRF::token();
// Hack that allows variables with file extensions
$pathInfo = pathinfo($value);
if($value !== $pathInfo['filename']) {
$fallback = url_variable($pathInfo['filename'], $variables);
if($fallback !== $pathInfo['filename'])
return $fallback . '.' . $pathInfo['extension'];
}
return $value;
}
function url_construct(string $url, array $query = [], ?string $fragment = null): string {
if(count($query)) {
$url .= mb_strpos($url, '?') !== false ? '&' : '?';
foreach($query as $key => $value) {
if($value) {
$url .= rawurlencode($key) . '=' . rawurlencode($value) . '&';
}
}
$url = mb_substr($url, 0, -1);
}
if(!empty($fragment)) {
$url .= "#{$fragment}";
}
return $url;
}
function url_prefix(bool $trailingSlash = true): string {
return 'http' . (empty($_SERVER['HTTPS']) ? '' : 's') . '://' . $_SERVER['HTTP_HOST'] . ($trailingSlash ? '/' : '');
}
function is_local_url(string $url): bool {
$length = mb_strlen($url);
if($length < 1)
return false;
if($url[0] === '/' && ($length > 1 ? $url[1] !== '/' : true))
return true;
return str_starts_with($url, url_prefix());
}