'; } if(!empty($vars['styles']) && is_array($vars['styles'])) { foreach($vars['styles'] as $style) $vars['head'] .= sprintf('', $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); } 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 = ''; } 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( '%3$s%5$s', $order, $orderCurrent ? $zoneSortDesc : !$zoneSortDesc, ucfirst($order), $orderCurrent ? ' zones-sorts-current' : '', $orderCurrent ? sprintf( ' %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.
%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 ''; echo 'YTKNS Side By Side Editor'; echo ''; echo ''; 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 ''; echo ''; echo ''; echo ''; } 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 ''; } else { echo << window.addEventListener('DOMContentLoaded', function() { if(window.location !== window.parent.location) location.assign(location.toString() + '?sbs=1'); }); 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('%1$s', $_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.
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');