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/
'],
'search-index' => ['/search.php'],
'search-query' => ['/search.php', ['q' => '']],
'auth-login' => ['/auth/login.php', ['username' => '', 'redirect' => '']],
'auth-login-welcome' => ['/auth/login.php', ['welcome' => '1', 'username' => '']],
'auth-register' => ['/auth/register.php'],
'auth-forgot' => ['/auth/password.php'],
'auth-reset' => ['/auth/password.php', ['user' => '']],
'auth-logout' => ['/auth/logout.php', ['csrf' => '{csrf}']],
'auth-resolve-user' => ['/auth/login.php', ['resolve' => '1', 'name' => '']],
'auth-two-factor' => ['/auth/twofactor.php', ['token' => '']],
'changelog-index' => ['/changelog', ['date' => '', 'user' => '', 'tags' => '', 'p' => '']],
'changelog-feed-rss' => ['/changelog.rss'],
'changelog-feed-atom' => ['/changelog.atom'],
'changelog-change' => ['/changelog/change/'],
'changelog-change-comments' => ['/changelog/change/', [], 'comments'],
'news-index' => ['/news', ['p' => '']],
'news-category' => ['/news/', ['p' => '']],
'news-post' => ['/news/post/'],
'news-post-comments' => ['/news/post/', [], 'comments'],
'news-feed-rss' => ['/news.rss'],
'news-category-feed-rss' => ['/news/.rss'],
'news-feed-atom' => ['/news.atom'],
'news-category-feed-atom' => ['/news/.atom'],
'forum-index' => ['/forum'],
'forum-leaderboard' => ['/forum/leaderboard.php', ['id' => '', 'mode' => '']],
'forum-mark-global' => ['/forum/mark-as-read'],
'forum-mark-single' => ['/forum/mark-as-read', ['forum' => '']],
'forum-topic-new' => ['/forum/posting.php', ['f' => '']],
'forum-reply-new' => ['/forum/posting.php', ['t' => '']],
'forum-category' => ['/forum/forum.php', ['f' => '', 'p' => '']],
'forum-topic' => ['/forum/topic.php', ['t' => '', 'page' => '']],
'forum-topic-create' => ['/forum/posting.php', ['f' => '']],
'forum-topic-bump' => ['/forum/topic.php', ['t' => '', 'm' => 'bump', 'csrf' => '{csrf}']],
'forum-topic-lock' => ['/forum/topic.php', ['t' => '', 'm' => 'lock', 'csrf' => '{csrf}']],
'forum-topic-unlock' => ['/forum/topic.php', ['t' => '', 'm' => 'unlock', 'csrf' => '{csrf}']],
'forum-topic-delete' => ['/forum/topic.php', ['t' => '', 'm' => 'delete', 'csrf' => '{csrf}']],
'forum-topic-restore' => ['/forum/topic.php', ['t' => '', 'm' => 'restore', 'csrf' => '{csrf}']],
'forum-topic-nuke' => ['/forum/topic.php', ['t' => '', 'm' => 'nuke', 'csrf' => '{csrf}']],
'forum-post' => ['/forum/topic.php', ['p' => ''], ''],
'forum-post-create' => ['/forum/posting.php', ['t' => '']],
'forum-post-delete' => ['/forum/post.php', ['p' => '', 'm' => 'delete']],
'forum-post-restore' => ['/forum/post.php', ['p' => '', 'm' => 'restore']],
'forum-post-nuke' => ['/forum/post.php', ['p' => '', 'm' => 'nuke']],
'forum-post-quote' => ['/forum/posting.php', ['q' => '']],
'forum-post-edit' => ['/forum/posting.php', ['p' => '', 'm' => 'edit']],
'user-list' => ['/members.php', ['r' => '', 'ss' => '', 'sd' => '', 'p' => '']],
'user-profile' => ['/profile.php', ['u' => '']],
'user-profile-forum-topics' => ['/profile.php', ['u' => '', 'm' => 'forum-topics']],
'user-profile-forum-posts' => ['/profile.php', ['u' => '', 'm' => 'forum-posts']],
'user-profile-edit' => ['/profile.php', ['u' => '', 'edit' => '1']],
'user-account-standing' => ['/profile.php', ['u' => ''], 'account-standing'],
'user-avatar' => ['/assets/avatar/', ['res' => '']],
'user-background' => ['/assets/profile-background/'],
'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' => '', 'csrf' => '{csrf}', 'm' => 'vote', 'v' => '']],
'comment-delete' => ['/comments.php', ['c' => '', 'csrf' => '{csrf}', 'm' => 'delete']],
'comment-restore' => ['/comments.php', ['c' => '', 'csrf' => '{csrf}', 'm' => 'restore']],
'comment-pin' => ['/comments.php', ['c' => '', 'csrf' => '{csrf}', 'm' => 'pin']],
'comment-unpin' => ['/comments.php', ['c' => '', 'csrf' => '{csrf}', 'm' => 'unpin']],
'manage-index' => ['/manage'],
'manage-general-overview' => ['/manage/general'],
'manage-general-logs' => ['/manage/general/logs.php'],
'manage-general-emoticons' => ['/manage/general/emoticons.php'],
'manage-general-emoticon' => ['/manage/general/emoticon.php', ['e' => '']],
'manage-general-emoticon-order-up' => ['/manage/general/emoticons.php', ['emote' => '', 'order' => 'd', 'csrf' => '{token}']],
'manage-general-emoticon-order-down'=> ['/manage/general/emoticons.php', ['emote' => '', 'order' => 'i', 'csrf' => '{token}']],
'manage-general-emoticon-delete' => ['/manage/general/emoticons.php', ['emote' => '', 'delete' => '1', 'csrf' => '{token}']],
'manage-general-emoticon-alias' => ['/manage/general/emoticons.php', ['emote' => '', 'alias' => '', 'csrf' => '{token}']],
'manage-general-settings' => ['/manage/general/settings.php'],
'manage-general-setting' => ['/manage/general/setting.php', ['name' => '', 'type' => '']],
'manage-general-setting-delete' => ['/manage/general/setting-delete.php', ['name' => '']],
'manage-forum-categories' => ['/manage/forum/index.php'],
'manage-forum-category' => ['/manage/forum/category.php', ['f' => '']],
'manage-changelog-changes' => ['/manage/changelog'],
'manage-changelog-change' => ['/manage/changelog/change.php', ['c' => '']],
'manage-changelog-tags' => ['/manage/changelog/tags.php'],
'manage-changelog-tag' => ['/manage/changelog/tag.php', ['t' => '']],
'manage-news-categories' => ['/manage/news/categories.php'],
'manage-news-category' => ['/manage/news/category.php', ['c' => '']],
'manage-news-posts' => ['/manage/news/posts.php'],
'manage-news-post' => ['/manage/news/post.php', ['p' => '']],
'manage-users' => ['/manage/users'],
'manage-user' => ['/manage/users/user.php', ['u' => '']],
'manage-users-warnings' => ['/manage/users/warnings.php', ['u' => '']],
'manage-users-warning-delete' => ['/manage/users/warnings.php', ['w' => '', 'delete' => '1', 'csrf' => '{csrf}']],
'manage-roles' => ['/manage/users/roles.php'],
'manage-role' => ['/manage/users/role.php', ['r' => '']],
]);
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());
}