2020-06-10 16:03:13 +00:00
< ? 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' )],
2020-10-16 19:24:40 +00:00
[ 'text' => 'Random' , 'link' => page_url ( '/zones/random' )],
2020-06-10 16:03:13 +00:00
]);
$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 );
2021-07-28 20:52:46 +00:00
$vars [ 'maintenance' ] = YTKNS_MAINTENANCE ? Template :: renderRaw ( 'maintenance' ) : '' ;
2020-06-10 16:03:13 +00:00
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>' ;
}
2021-07-28 20:52:46 +00:00
if ( ! YTKNS_MAINTENANCE && ! empty ( $_COOKIE [ 'ytkns_login' ]) && is_string ( $_COOKIE [ 'ytkns_login' ])) {
2020-06-10 16:03:13 +00:00
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 ;
}
2021-07-28 20:52:46 +00:00
if ( ! YTKNS_MAINTENANCE ) {
if ( ! empty ( $_GET [ '_refresh_screenshot' ])) {
header ( 'Location: /' );
$zoneInfo -> takeScreenshot ();
return ;
}
2020-06-10 16:03:13 +00:00
2021-07-28 20:52:46 +00:00
ZoneView :: increment ( $zoneInfo , $_SERVER [ 'REMOTE_ADDR' ]);
}
2020-06-10 16:03:13 +00:00
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 ( '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 ;
}
2020-10-16 19:24:40 +00:00
if ( $reqPath === '/zones/random' ) {
$zoneInfo = Zone :: byRandom ();
header ( 'Location: ' . $zoneInfo -> getUrl ());
return ;
}
2020-06-10 16:03:13 +00:00
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 ;
}
2020-10-16 19:24:40 +00:00
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 ;
}
2020-06-10 16:03:13 +00:00
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 ;
}
2020-10-16 19:24:40 +00:00
$isSBS = ! empty ( $_GET [ 'sbs' ]);
2020-06-10 16:03:13 +00:00
$cssHash = hash_file ( 'sha256' , YTKNS_PUB . '/assets/editor.css' );
$jsHash = hash_file ( 'sha256' , YTKNS_PUB . '/assets/editor.js' );
2020-10-16 19:24:40 +00:00
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 ]),
],
]);
}
2020-06-10 16:03:13 +00:00
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 ),
]);
2020-10-16 19:24:40 +00:00
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 ]),
],
]);
}
2020-06-10 16:03:13 +00:00
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' ) {
2021-07-28 20:52:46 +00:00
if ( YTKNS_MAINTENANCE ) {
http_response_code ( 503 );
echo html_information ( 'You cannot log in during maintenance.' );
return ;
}
2020-06-10 16:03:13 +00:00
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' ) {
2021-07-28 20:52:46 +00:00
if ( YTKNS_MAINTENANCE ) {
http_response_code ( 503 );
echo html_information ( 'You cannot register during maintenance.' );
return ;
}
2020-06-10 16:03:13 +00:00
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' );