ytkns/public/index.php

1126 lines
37 KiB
PHP

<?php
namespace YTKNS;
use Exception;
require_once __DIR__ . '/../startup.php';
function page_url(string $path, array $params = []): string {
if(isset($params['p']))
unset($params['p']);
$mainDomain = Config::get('domain.main');
$url = '';
if($_SERVER['HTTP_HOST'] !== $mainDomain)
$url .= 'https://' . $mainDomain;
$url .= $path;
if(!empty($params))
$url .= '?' . http_build_query($params);
return $url;
}
function html_header(array $vars = []): string {
$vars['title'] ??= 'You\'re the kid now, squid';
$vars['head'] ??= '';
if(!empty($vars['redirect'])) {
$timeout = (int)($vars['redirect_timeout'] ?? 0);
$vars['head'] .= '<meta http-equiv="refresh" content="' . $timeout . '; url=' . $vars['redirect'] . '">';
}
if(!empty($vars['styles']) && is_array($vars['styles'])) {
foreach($vars['styles'] as $style)
$vars['head'] .= sprintf('<link href="%s" type="text/css" rel="stylesheet"/>', $style);
}
$vars['menu_site'] = Template::renderSet('menu-site-item', [
['text' => 'Home', 'link' => page_url('/')],
['text' => 'Create a Zone', 'link' => page_url('/zones/create')],
['text' => 'Zones', 'link' => page_url('/zones')],
['text' => 'Random', 'link' => page_url('/zones/random')],
]);
$userMenu = [];
if(UserSession::hasInstance()) {
$userName = UserSession::instance()->getUser()->getUsername();
$userMenu = [
['text' => "@{$userName}", 'link' => page_url("/@{$userName}")],
['text' => 'My Zones', 'link' => page_url('/zones?f=my')],
['text' => 'Settings', 'link' => page_url('/settings')],
];
if(Config::get('user.invite_only', Config::TYPE_BOOL))
$userMenu[] = ['text' => 'Invites', 'link' => page_url('/settings/invites')];
$userMenu[] = ['text' => 'Log out', 'link' => page_url('/auth/logout', ['s' => UserSession::instance()->getSmallToken()])];
} else {
$userMenu = [
['text' => 'Log in', 'link' => page_url('/auth/login')],
['text' => 'Register', 'link' => page_url('/auth/register')],
];
}
$vars['menu_user'] = Template::renderSet('menu-user-item', $userMenu);
$vars['maintenance'] = YTKNS_MAINTENANCE ? Template::renderRaw('maintenance') : '';
return Template::renderRaw('header', $vars);
}
function html_footer(array $vars = []): string {
$vars['footer_took'] = number_format(microtime(true) - YTKNS_STARTUP, 5);
$vars['footer_year'] = date('Y');
$scripts = $vars['scripts'] ?? null;
$vars['scripts'] = '';
if(!empty($scripts) && is_array($scripts)) {
foreach($scripts as $script)
$vars['scripts'] .= sprintf('<script type="text/javascript" charset="utf-8" src="%s"></script>', $script);
}
return Template::renderRaw('footer', $vars);
}
function html_information(string $message, string $title = 'Information', ?string $redirect = null, int $redirectTimeout = 2): string {
$html = html_header([
'title' => $title . ' - YTKNS',
'redirect' => $redirect,
'redirect_timeout' => $redirectTimeout,
]);
if(!empty($redirect))
$message .= Template::renderRaw('information-redirect', [
'info_redirect' => $redirect,
]);
$html .= Template::renderRaw('information', [
'info_title' => $title,
'info_content' => $message,
]);
$html .= html_footer();
return $html;
}
function html_pagination(int $pages, int $current, string $urlFormat): string {
$html = '<div class="pagination">';
for($i = 1; $i <= $pages; $i++)
$html .= sprintf('<a href="%s" class="pagination-page%s">%d</a>', sprintf($urlFormat, $i), ($current === $i ? ' pagination-page-current' : ''), $i);
return $html . '</div>';
}
if(!YTKNS_MAINTENANCE && !empty($_COOKIE['ytkns_login']) && is_string($_COOKIE['ytkns_login'])) {
try {
$session = UserSession::byToken($_COOKIE['ytkns_login']);
$session->update();
$session->setInstance();
if($session->getBump())
setcookie('ytkns_login', $session->getToken(), $session->getExpires(), '/', '.' . Config::get('domain.main'), false, true);
unset($session);
} catch(UserSessionNotFoundException $ex) {}
}
$zoneName = strtolower(substr($_SERVER['HTTP_HOST'], 0, -strlen(Config::get('domain.main')) - 1));
if(!empty($zoneName)) {
$redirect = ZoneRedirect::find($zoneName);
if($redirect !== null) {
$redirect->execute();
return;
}
try {
$zoneInfo = Zone::byName($zoneName);
} catch(ZoneNotFoundException $ex) {
http_response_code(404);
echo html_header(['title' => 'Zone not found!']);
Template::render('zones/none', [
'zone_create_url' => page_url('/zones/create', ['name' => $zoneName]),
]);
echo html_footer();
return;
}
if(!YTKNS_MAINTENANCE) {
if(!empty($_GET['_refresh_screenshot'])) {
header('Location: /');
$zoneInfo->takeScreenshot();
return;
}
ZoneView::increment($zoneInfo, $_SERVER['REMOTE_ADDR']);
}
echo (string)$zoneInfo->getPageBuilder(true);
return;
}
$reqMethod = $_SERVER['REQUEST_METHOD'];
$reqPath = '/' . trim(parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/');
if($reqPath === '/') {
echo html_header();
Template::render('home');
echo html_footer();
return;
}
if(substr($reqPath, 0, 4) === '/ss/') {
header('Content-Type: image/jpeg');
header('X-Accel-Redirect: /assets/no-screenshot.jpg');
return;
}
if(preg_match('#^/@([A-Za-z0-9-_]+)$#', $reqPath, $matches)) {
try {
$profile = User::forProfile($matches[1]);
} catch(Exception $ex) {
http_response_code(404);
echo html_header(['title' => 'User not found - YTKNS']);
Template::render('profile/notfound');
echo html_footer();
return;
}
$zones = Zone::byUser($profile, 'zone_views', false);
if(count($zones) < 1) {
$profileZones = Template::renderRaw('profile/zone-none');
} else {
$profileZones = [];
foreach($zones as $zone)
$profileZones[] = [
'zone_id' => $zone->getId(),
'zone_name' => $zone->getName(),
'zone_title' => $zone->getTitle(),
'zone_views' => number_format($zone->getViews()),
'zone_url' => $zone->getUrl(),
'zone_screenshot' => $zone->getScreenshotUrl(),
];
$profileZones = Template::renderRaw('profile/zone-list', [
'zone_items' => Template::renderSet('profile/zone-item', $profileZones),
]);
}
echo html_header(['title' => $profile->username . ' @ YTKNS']);
Template::render('profile/index', [
'profile_username' => $profile->username,
'profile_zones' => $profileZones,
]);
echo html_footer();
return;
}
if($reqPath === '/zones') {
$zoneFilter = filter_input(INPUT_GET, 'f');
if($zoneFilter === 'my' && !UserSession::hasInstance()) {
echo html_information('You must be logged in to do this.');
return;
}
$zoneTake = 20;
$zonePage = max(filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT) - 1, 0);
$zoneOffset = $zonePage * $zoneTake;
$zoneSort = filter_input(INPUT_GET, 'sort');
$zoneSortDesc = filter_input(INPUT_GET, 'asc', FILTER_SANITIZE_NUMBER_INT) < 1;
$zoneOrderings = [
'creation' => 'zone_created',
'updated' => 'zone_updated',
'views' => 'zone_views',
'user' => 'user_id',
];
if(!array_key_exists($zoneSort, $zoneOrderings)) {
switch($zoneFilter) {
case 'my':
$zoneSort = 'creation';
break;
default:
$zoneSort = 'views';
$zoneSortDesc = false;
break;
}
}
switch($zoneFilter) {
case 'my':
$zones = Zone::byUser(UserSession::instance()->getUser(), $zoneOrderings[$zoneSort], $zoneSortDesc, $zoneTake, $zoneOffset);
$zoneListTemplate = 'zones/my-item';
$zoneListTitle = 'My Zones';
break;
default:
$zones = Zone::all($zoneOrderings[$zoneSort], $zoneSortDesc, $zoneTake, $zoneOffset);
$zoneListTemplate = 'zones/item';
$zoneListTitle = 'Zones';
$zoneFilter = '';
break;
}
$zoneCount = Zone::count();
$zonePages = ceil($zoneCount / $zoneTake);
$zoneList = [];
foreach($zones as $zone)
$zoneList[] = [
'zone_id' => $zone->getId(),
'zone_name' => $zone->getName(),
'zone_title' => $zone->getTitle(),
'zone_views' => number_format($zone->getViews()),
'zone_url' => $zone->getUrl(),
'zone_edit_url' => page_url(sprintf('/zones/%d', $zone->getId())),
'zone_delete_url' => page_url(sprintf('/zones/%d/delete', $zone->getId())),
'zone_screenshot' => $zone->getScreenshotUrl(),
'zone_user_url' => page_url('/@' . $zone->getUser()->getUsername()),
'zone_user_name' => $zone->getUser()->getUsername(),
];
$zoneSortings = '';
foreach(array_keys($zoneOrderings) as $order) {
$orderCurrent = $zoneSort === $order;
$zoneSortings .= sprintf(
'<a href="?f=%6$s&sort=%1$s&asc=%2$d" class="zones-sorts-sort%4$s">%3$s%5$s</a>',
$order, $orderCurrent ? $zoneSortDesc : !$zoneSortDesc, ucfirst($order),
$orderCurrent ? ' zones-sorts-current' : '',
$orderCurrent ? sprintf(
' <img src="/assets/arrow_%s.png" alt="%s"/>',
$zoneSortDesc ? 'down' : 'up',
$zoneSortDesc ? 'Descending' : 'Ascending'
) : '', $zoneFilter
);
}
echo html_header(['title' => $zoneListTitle . ' - YTKNS']);
Template::render('zones/list', [
'zone_list_title' => $zoneListTitle,
'zone_list' => Template::renderSet($zoneListTemplate, $zoneList),
'zone_sortings' => $zoneSortings,
]);
echo html_pagination($zonePages, $zonePage + 1, sprintf('/zones?f=%s&sort=%s&asc=%d&page=%%s', $zoneFilter, $zoneSort, !$zoneSortDesc));
echo html_footer();
return;
}
if($reqPath === '/zones/random') {
$zoneInfo = Zone::byRandom();
header('Location: ' . $zoneInfo->getUrl());
return;
}
if($reqPath === '/zones/create') {
if(!UserSession::hasInstance()) {
http_response_code(403);
echo html_information('You must be logged in to do this.');
return;
}
$createToken = UserSession::instance()->getSmallToken(4);
if($reqMethod === 'POST') {
$zoneToken = filter_input(INPUT_POST, 'zone_token');
$zoneName = filter_input(INPUT_POST, 'zone_subdomain');
$zoneTitle = filter_input(INPUT_POST, 'zone_title');
if($zoneToken !== $createToken) {
$createError = 'Invalid request.';
} else {
if(empty($zoneName) || empty($zoneTitle)) {
$createError = 'Please fill in all fields.';
} else {
if(!Zone::validName($zoneName)) {
$createError = 'Name contains invalid characters or is too long.';
} elseif(ZoneRedirect::exists($zoneName) || Zone::exists($zoneName)) {
$createError = 'A Zone with this name already exists.';
} elseif(!ctype_alpha($zoneName)
|| mb_strlen($zoneTitle) > 255) {
$createError = 'Invalid data.';
} else {
$createZone = Zone::create(UserSession::instance()->getUser(), $zoneName, $zoneTitle);
$createZone->addEffect(new \YTKNS\Effects\NewlyCreatedPageEffect);
echo html_information('Zone created!', 'Information - YTKNS', "/zones/{$createZone->getId()}");
return;
}
}
}
} elseif($reqMethod === 'GET') {
$zoneName = filter_input(INPUT_GET, 'name');
}
if(isset($createError)) {
$createError = Template::renderRaw('error', [
'error_text' => $createError,
]);
}
echo html_header(['title' => 'Create a Zone - YTKNS']);
Template::render('zones/create', [
'create_action' => page_url('/zones/create'),
'create_domain' => Config::get('domain.main'),
'create_token' => $createToken,
'create_error' => $createError ?? '',
'create_subdomain' => $zoneName ?? '',
'create_title' => $zoneTitle ?? '',
]);
echo html_footer();
return;
}
if($reqPath === '/zones/_effects') {
header('Content-Type: application/json; charset=utf-8');
if(!UserSession::hasInstance()) {
http_response_code(403);
echo json_encode([
'err' => 'You must be logged in.',
]);
return;
}
$effects = [];
foreach(SITE_EFFECTS as $effectClass) {
$instance = new $effectClass;
$effects[] = [
'type' => trim(substr($effectClass, 14, -6), '\\'),
'name' => $instance->getEffectName(),
'props' => $instance->getEffectProperties(),
];
}
echo json_encode($effects);
return;
}
if($reqPath === '/zones/_preview') {
if(!UserSession::hasInstance()) {
http_response_code(403);
echo html_information('You must be logged in to do this.');
return;
}
if($reqMethod !== 'POST') {
http_response_code(405);
echo html_information('Must be a POST request.');
return;
}
$zoneToken = filter_input(INPUT_POST, 'zone_token');
if($zoneToken !== UserSession::instance()->getSmallToken(10)) {
http_response_code(403);
echo html_information('Invalid token.');
return;
}
$zoneTitle = filter_input(INPUT_POST, 'zone_title');
$zoneEffects = filter_input(INPUT_POST, 'zone_effect', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
try {
$zoneInfo = new Zone;
$zoneInfo->setTitle($zoneTitle);
$zoneInfo->setPassiveMode();
foreach($zoneEffects as $effectName => $effectValues) {
$effectInfo = ZoneEffect::effectClass($effectName);
$effectInfo->setEffectParams($effectValues);
$zoneInfo->addEffect($effectInfo);
}
} catch(ZoneInvalidTitleException $ex) {
http_response_code(400);
echo html_information('Invalid title.');
return;
} catch(ZoneEffectClassNotFoundException $ex) {
http_response_code(400);
echo html_information(sprintf('Invalid effect name: %s.', $ex->getMessage()));
return;
} catch(PageEffectException $ex) {
http_response_code(400);
echo html_information(sprintf('%s: %s', $effectName ?? '', $ex->getMessage()));
return;
} catch(Exception $ex) {
http_response_code(500);
echo html_information(sprintf('Failed to generate preview.<br/>%s: %s', get_class($ex), $ex->getMessage()));
return;
}
echo (string)$zoneInfo->getPageBuilder();
return;
}
if(preg_match('#^/zones/([0-9]+)/delete$#', $reqPath, $matches)) {
if(!UserSession::hasInstance()) {
http_response_code(403);
echo html_information('You must be logged in to do this.');
return;
}
try {
$zoneInfo = Zone::byId($matches[1]);
} catch(ZoneNotFoundException $ex) {
http_response_code(404);
echo html_information('This zone doesn\'t exist.');
return;
}
if($zoneInfo->getUserId() !== UserSession::instance()->getUserId()) {
http_response_code(403);
echo html_information('You aren\'t allowed to touch this zone.');
return;
}
$deleteToken = UserSession::instance()->getSmallToken(20);
if($reqMethod === 'POST') {
if(filter_input(INPUT_POST, 'zone_token') !== $deleteToken) {
http_response_code(403);
echo html_information('Invalid token.');
return;
}
header('Location: /zones?f=my');
$zoneInfo->delete();
return;
}
echo html_header(['title' => sprintf('Deleting zone %s - YTKNS', $zoneInfo->getName())]);
Template::render('zones/delete', [
'delete_zone_id' => $zoneInfo->getId(),
'delete_zone_name' => $zoneInfo->getName(),
'delete_zone_token' => $deleteToken,
]);
echo html_footer();
return;
}
if(preg_match('#^/zones/([0-9]+)/sbs$#', $reqPath, $matches)) {
echo '<!doctype html>';
echo '<meta charset="utf-8"/><title>YTKNS Side By Side Editor</title>';
echo '<iframe name="preview" style="position: absolute; top: 0; left: 0; bottom: 0; height: 100%; width: 70%; border-width: 0;"></iframe>';
echo '<iframe name="editor" src="/zones/' . $matches[1] . '" style="position: absolute; top: 0; right: 0; bottom: 0; height: 100%; width: 30%; border-width: 0;"></iframe>';
return;
}
if(preg_match('#^/zones/([0-9]+)$#', $reqPath, $matches)) {
if(!UserSession::hasInstance()) {
http_response_code(403);
echo html_information('You must be logged in to do this.');
return;
}
try {
$zoneInfo = Zone::byId($matches[1]);
} catch(ZoneNotFoundException $ex) {
http_response_code(404);
echo html_information('This zone doesn\'t exist.');
return;
}
if(UserSession::instance()->getUserId() !== $zoneInfo->getUserId()
&& UserSession::instance()->getUserId() !== 1) {
http_response_code(403);
echo html_information('You aren\'t allowed to touch this zone.');
return;
}
$isSBS = !empty($_GET['sbs']);
$cssHash = hash_file('sha256', YTKNS_PUB . '/assets/editor.css');
$jsHash = hash_file('sha256', YTKNS_PUB . '/assets/editor.js');
if($isSBS) {
echo '<!doctype html>';
echo '<link href="/assets/style.css" type="text/css" rel="stylesheet"/>';
echo '<link href="/assets/editor.css?v=' . $cssHash . '" type="text/css" rel="stylesheet"/>';
echo '<style>.ye { height: 100%; width: 100%; } .ye-sidebar { min-width: 200px; }</style>';
} else {
echo html_header([
'title' => 'Editing Zone - YTKNS',
'styles' => [
page_url('/assets/editor.css', ['v' => $cssHash]),
],
]);
}
Template::render('zones/edit', [
'edit_id' => $zoneInfo->getId(),
'edit_token' => UserSession::instance()->getSmallToken(10),
'edit_css_ver' => substr($cssHash, 0, 16),
'edit_js_ver' => substr($jsHash, 0, 16),
'upload_token' => UserSession::instance()->getSmallToken(6),
]);
if($isSBS) {
echo '<script type="text/javascript" charset="utf-8" src="/assets/editor.js?v=' . $jsHash . '"></script>';
} else {
echo <<<HTML
<script>
window.addEventListener('DOMContentLoaded', function() {
if(window.location !== window.parent.location)
location.assign(location.toString() + '?sbs=1');
});
</script>
HTML;
echo html_footer([
'scripts' => [
page_url('/assets/editor.js', ['v' => $jsHash]),
],
]);
}
return;
}
if(preg_match('#^/zones/([0-9]+).json$#', $reqPath, $matches)) {
header('Content-Type: application/json; charset=utf-8');
if(!UserSession::hasInstance()) {
http_response_code(403);
echo json_encode([
'err' => 'You must be logged in to do this.',
]);
return;
}
try {
$zoneInfo = Zone::byId($matches[1]);
} catch(ZoneNotFoundException $ex) {
http_response_code(404);
echo json_encode([
'err' => 'This zone doesn\'t exist',
]);
return;
}
if(UserSession::instance()->getUserId() !== $zoneInfo->getUserId()
&& UserSession::instance()->getUserId() !== 1) {
http_response_code(403);
echo json_encode([
'err' => 'You aren\'t allowed to touch this zone.',
]);
return;
}
if($reqMethod === 'GET') {
echo $zoneInfo->toJson(true);
return;
}
if($reqMethod !== 'POST') {
http_response_code(405);
echo json_encode([
'err' => 'Invalid request method.',
]);
return;
}
$zoneToken = filter_input(INPUT_POST, 'zone_token');
if($zoneToken !== UserSession::instance()->getSmallToken(10)) {
http_response_code(403);
echo json_encode([
'err' => 'Invalid token.',
]);
return;
}
$zoneId = filter_input(INPUT_POST, 'zone_id', FILTER_VALIDATE_INT);
if($zoneId !== $zoneInfo->getId()) {
http_response_code(400);
echo json_encode([
'err' => 'Zone ID in POST data does not match the target zone ID.',
]);
return;
}
$zoneTitle = filter_input(INPUT_POST, 'zone_title');
$zoneEffects = filter_input(INPUT_POST, 'zone_effect', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
try {
$zoneInfo->setTitle($zoneTitle);
$zoneInfo->setPassiveMode();
if(is_array($zoneEffects)) {
foreach($zoneEffects as $effectName => $effectValues) {
$effectInfo = ZoneEffect::effectClass($effectName);
$effectInfo->setEffectParams($effectValues);
$zoneInfo->addEffect($effectInfo);
}
}
$zoneInfo->update(['zone_title']);
// Schedule a screenshot to be taken
$zoneInfo->queueTask('screenshot');
} catch(ZoneInvalidTitleException $ex) {
http_response_code(400);
echo json_encode([
'err' => 'Invalid title.',
]);
return;
} catch(ZoneEffectClassNotFoundException $ex) {
http_response_code(400);
echo json_encode([
'err' => sprintf('Invalid effect name: %s.', $ex->getMessage()),
]);
return;
} catch(PageEffectException $ex) {
http_response_code(400);
echo json_encode([
'err' => sprintf('%s: %s', $effectName ?? '', $ex->getMessage()),
]);
return;
} catch(Exception $ex) {
http_response_code(500);
echo json_encode([
'err' => sprintf("An unexpected error occurred.\r\n%s: %s", get_class($ex), $ex->getMessage()),
]);
return;
}
echo json_encode([
'msg' => 'Saved!',
]);
return;
}
if($reqPath === '/uploads') {
header('Content-Type: application/json; charset=utf-8');
if($reqMethod !== 'POST') {
http_response_code(405);
echo json_encode([
'err' => 'Unsupported request method.',
]);
return;
}
if(!UserSession::hasInstance()) {
http_response_code(403);
echo json_encode([
'err' => 'You must be logged in to upload files.',
]);
return;
}
if(filter_input(INPUT_POST, 'upload_token') !== UserSession::instance()->getSmallToken(6)) {
http_response_code(403);
echo json_encode([
'err' => 'Invalid upload token.',
]);
return;
}
if(empty($_FILES['upload_file']['tmp_name'])) {
http_response_code(400);
echo json_encode([
'err' => 'Missing file.',
]);
return;
}
$hash = hash_file('sha256', $_FILES['upload_file']['tmp_name']);
$existing = Upload::byHash($hash);
if($existing !== null) {
if($existing->getDMCA() > 0) {
http_response_code(451);
echo json_encode([
'err' => 'This file has been removed in response to a DMCA takedown request. It may not be used.',
]);
return;
}
if($existing->getDeleted() > 0) {
http_response_code(404);
echo json_encode([
'err' => 'This file has been flagged for deletion, it cannot be reuploaded at this time.',
]);
return;
}
http_response_code(200);
echo json_encode([
'err' => 'File has been uploaded already.',
'file' => $existing->getId(),
]);
return;
}
if($_FILES['upload_file']['size'] > 5242880) {
http_response_code(413);
echo json_encode([
'err' => 'Upload is too large.',
]);
return;
}
$contentType = mime_content_type($_FILES['upload_file']['tmp_name']);
if(!in_array($contentType, ALLOWED_UPLOADS)) {
http_response_code(400);
echo json_encode([
'err' => sprintf('File type not allowed. (Must be %s)', implode(', ', ALLOWED_UPLOADS)),
]);
return;
}
try {
$upload = Upload::create(UserSession::instance()->getUser(), $_FILES['upload_file']['name'], $contentType, $hash);
} catch(UploadCreationFailedException $ex) {
http_response_code(500);
echo json_encode([
'err' => 'Failed to create upload record.',
]);
return;
}
if(!move_uploaded_file($_FILES['upload_file']['tmp_name'], $upload->getPath())) {
http_response_code(500);
echo json_encode([
'err' => 'Upload failed.',
]);
return;
}
http_response_code(201);
echo json_encode([
'file' => $upload->getId(),
]);
return;
}
if(preg_match('#^/uploads/([a-zA-Z0-9-_]{16})$#', $reqPath, $matches)) {
try {
$uploadInfo = Upload::byId($matches[1]);
} catch(UploadNotFoundException $ex) {
http_response_code(404);
return;
}
if(!empty($_SERVER['HTTP_ORIGIN'])) {
$zoneDomain = sprintf(Config::get('domain.zone'), '');
$originPart = substr($_SERVER['HTTP_ORIGIN'], strlen($_SERVER['HTTP_ORIGIN']) - strlen($zoneDomain));
if($originPart === $zoneDomain) {
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
}
}
if($_SERVER['REQUEST_METHOD'] !== 'HEAD' && $_SERVER['REQUEST_METHOD'] !== 'OPTIONS')
header('X-Accel-Redirect: /raw-uploads/' . $uploadInfo->getId());
header('Content-Type: ' . $uploadInfo->getType());
header('Content-Disposition: inline; filename="' . $uploadInfo->getName() . '"');
return;
}
if(preg_match('#^/uploads/([a-zA-Z0-9-_]{16}).json$#', $reqPath, $matches)) {
header('Content-Type: application/json; charset=utf-8');
if(!UserSession::hasInstance()) {
http_response_code(403);
echo json_encode([
'err' => 'You must be logged in to do this.',
]);
return;
}
try {
$uploadInfo = Upload::byId($matches[1]);
} catch(UploadNotFoundException $ex) {
http_response_code(404);
echo json_encode([
'err' => 'Upload not found.',
]);
return;
}
echo $uploadInfo->toJson(true);
return;
}
if($reqPath === '/settings') {
if(!UserSession::hasInstance()) {
http_response_code(404);
echo html_information('You must be logged in to access this page.');
return;
}
echo html_header(['title' => 'Settings - YTKNS']);
Template::render('settings/index');
echo html_footer();
return;
}
if($reqPath === '/settings/invites') {
if(!UserSession::hasInstance()) {
http_response_code(404);
echo html_information('You must be logged in to access this page.');
return;
}
$currentUser = UserSession::instance()->getUser();
$createdInvites = UserInvite::fromCreator($currentUser);
$createToken = $currentUser->getId() !== 1 /*&& count($createdInvites) >= 5*/ ? '' : UserSession::instance()->getSmallToken(11);
if($reqMethod === 'POST') {
if(empty($createToken)) {
$inviteError = 'You\'ve reached the maximum amount of invites you can generate.';
} elseif($createToken !== filter_input(INPUT_POST, 'invite_token')) {
$inviteError = 'Cannot create invite.';
} else {
try {
$createdInvites[] = $createdInvite = UserInvite::create($currentUser);
$inviteError = $createdInvite->getToken();
} catch(UserInviteCreationFailedException $ex) {
$inviteError = 'Invite creation failed.';
}
}
}
if(isset($inviteError))
$inviteError = Template::renderRaw('error', [
'error_text' => $inviteError,
]);
if(!empty($createToken))
$inviteCreate = Template::renderRaw('settings/invites-create', [
'create_token' => $createToken,
]);
$invitesItems = [];
foreach($createdInvites as $inviteItem) {
$invitesItems[] = [
'invite_created' => date('c', $inviteItem->getCreated()),
'invite_used' => (($_used = $inviteItem->getUsed()) === null ? 'Unused' : date('c', $_used)),
'invite_user' => (($_user = $inviteItem->getUser()) === null ? 'Unused' : sprintf('<a href="/@%1$s">%1$s</a>', $_user->getUsername())),
'invite_token' => $inviteItem->getToken(),
];
}
echo html_header(['title' => 'Invites - YTKNS']);
Template::render('settings/invites', [
'invite_error' => $inviteError ?? '',
'invite_create' => $inviteCreate ?? '',
'invite_list' => Template::renderSet('settings/invites-item', $invitesItems),
]);
echo html_footer();
return;
}
if($reqPath === '/auth/login') {
if(YTKNS_MAINTENANCE) {
http_response_code(503);
echo html_information('You cannot log in during maintenance.');
return;
}
if(UserSession::hasInstance()) {
http_response_code(404);
echo html_information('You are logged in already.');
return;
}
if($reqMethod === 'POST') {
$loginUsername = filter_input(INPUT_POST, 'username');
$loginPassword = filter_input(INPUT_POST, 'password');
$loginRemember = !empty(filter_input(INPUT_POST, 'remember'));
if(empty($loginUsername) || empty($loginPassword)) {
$authError = 'Username or password missing.';
} else {
try {
$loginUser = User::forLogin($loginUsername);
} catch(\Exception $ex) {}
if(empty($loginUser) || empty($loginUser->password) || !password_verify($loginPassword, $loginUser->password)) {
$authError = 'Username or password was invalid.';
} else {
$session = UserSession::create($loginUser, $loginRemember);
$session->setInstance();
setcookie('ytkns_login', $session->getToken(), $session->getExpires(), '/', '.' . Config::get('domain.main'), false, true);
echo html_information('You are now logged in!', 'Welcome', '/');
return;
}
}
}
if(isset($authError))
$authError = Template::renderRaw('error', [
'error_text' => $authError,
]);
$authFields = [
[
'field_title' => 'Username or E-Mail Address',
'field_type' => 'text',
'field_name' => 'username',
'field_value' => ($loginUsername ?? ''),
],
[
'field_title' => 'Password',
'field_type' => 'password',
'field_name' => 'password',
'field_value' => '',
],
];
echo html_header(['title' => 'Log in - YTKNS']);
Template::render('auth/login' . (isset($_GET['new']) ? '2' : ''), [
'auth_error' => $authError ?? '',
'auth_fields' => Template::renderSet('auth/field', $authFields),
'auth_remember' => ($loginRemember ?? false) ? ' checked' : '',
]);
echo html_footer();
return;
}
if($reqPath === '/auth/register') {
if(YTKNS_MAINTENANCE) {
http_response_code(503);
echo html_information('You cannot register during maintenance.');
return;
}
if(UserSession::hasInstance()) {
http_response_code(404);
echo html_information('You are logged in already.');
return;
}
$inviteOnly = Config::get('user.invite_only', Config::TYPE_BOOL);
if($reqMethod === 'POST') {
$registerUsername = filter_input(INPUT_POST, 'username');
$registerPassword = filter_input(INPUT_POST, 'password');
$registerEMail = filter_input(INPUT_POST, 'email');
$registerInvite = filter_input(INPUT_POST, 'invite');
if(empty($registerUsername) || empty($registerPassword) || empty($registerEMail) || ($inviteOnly && empty($registerInvite))) {
$authError = 'You must fill in all fields.';
} else {
if($inviteOnly) {
try {
$userInvite = UserInvite::byToken($registerInvite);
if($userInvite->isUsed()) {
$authError = 'Invalid invite token.';
}
} catch(UserInviteNotFoundException $ex) {
$authError = 'Invalid invite token.';
}
}
if(!isset($authError)) {
if(!preg_match('#^([a-zA-Z0-9-_]{1,20})$#', $registerUsername)) {
}
try {
$createdUser = User::create(
$registerUsername,
$registerPassword,
$registerEMail
);
if(isset($userInvite))
$userInvite->markUsed($createdUser);
} catch(UserCreationInvalidNameException $ex) {
$authError = 'Your username contains invalid characters or is too short or long.<br/>Must be between 1 and 20 characters and may only contains alphanumeric characters as well as - and _.';
} catch(UserCreationInvalidPasswordException $ex) {
$authError = 'Your password must have at least 6 unique characters.';
} catch(UserCreationInvalidMailException $ex) {
$authError = 'Your e-mail address isn\'t real.';
} catch(UserCreationFailedException $ex) {
$authError = 'Failed to create user.';
}
}
}
} elseif($reqMethod === 'GET') {
$registerInvite = filter_input(INPUT_GET, 'inv', FILTER_SANITIZE_STRING);
}
$authFields = [
[
'field_title' => 'Username',
'field_type' => 'text',
'field_name' => 'username',
'field_value' => ($registerUsername ?? ''),
],
[
'field_title' => 'Password',
'field_type' => 'password',
'field_name' => 'password',
'field_value' => '',
],
[
'field_title' => 'E-Mail Address',
'field_type' => 'email',
'field_name' => 'email',
'field_value' => ($registerEMail ?? ''),
],
];
if($inviteOnly)
$authFields[] = [
'field_title' => 'Invitation',
'field_type' => 'password',
'field_name' => 'invite',
'field_value' => ($registerInvite ?? ''),
];
if(isset($authError))
$authError = Template::renderRaw('error', [
'error_text' => $authError,
]);
echo html_header(['title' => 'Register - YTKNS']);
Template::render('auth/register', [
'auth_error' => $authError ?? '',
'auth_fields' => Template::renderSet('auth/field', $authFields),
]);
echo html_footer();
return;
}
if($reqPath === '/auth/logout') {
if(!UserSession::hasInstance()) {
http_response_code(404);
echo html_information('You are not logged in.');
return;
}
if(filter_input(INPUT_GET, 's') !== UserSession::instance()->getSmallToken()) {
http_response_code(403);
echo html_information('Log out verification failed, please try again.');
return;
}
UserSession::instance()->destroy();
UserSession::unsetInstance();
echo html_information('You have been logged out.', 'Log out', '/');
return;
}
http_response_code(404);
echo html_information('The requested page does not exist.', 'Page not found');