diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe0770..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/.gitignore b/.gitignore index 9de68b3..843c6ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/thumbs/assets/* -/thumbs/avatars/* -/asset/files/* -/api/private/config.php \ No newline at end of file +thumbs/assets/* +thumbs/avatars/* +asset/files/* +api/private/config.php \ No newline at end of file diff --git a/XD.php b/XD.php new file mode 100644 index 0000000..825566b --- /dev/null +++ b/XD.php @@ -0,0 +1,8 @@ + +Do not run game:HttpGet("http://polygon.pizzaboxer.xyz/XD") in studio +please no +Plz + "Moderator", + Users::STAFF_ADMINISTRATOR => "Administrator", + Users::STAFF_CATALOG => "Catalog Manager" +]; + +$servermemory = System::GetMemoryUsage(); +$usersOnline = Users::GetUsersOnline(); +$pendingRenders = Polygon::GetPendingRenders(); +$thumbPing = Polygon::GetServerPing(1); $usage = (object) [ "Memory" => (object) [ - "Total" => system::getFileSize($servermemory->total), - "SytemUsage" => system::getFileSize($servermemory->total-$servermemory->free), - "PHPUsage" => system::getFileSize(memory_get_usage(true)) + "Total" => System::GetFileSize($servermemory->total), + "SytemUsage" => System::GetFileSize($servermemory->total-$servermemory->free), + "PHPUsage" => System::GetFileSize(memory_get_usage(true)) ], "Disk" => (object) [ - "Total" => system::getFileSize(disk_total_space("/")), - "SystemUsage" => system::getFileSize(disk_total_space("/")-disk_free_space("/")), - "PolygonUsage" => system::getFolderSize("/var/www/pizzaboxer.xyz/polygon/"), - "SetupUsage" => system::getFileSize(getSetupUsage([2009, 2010, 2011, 2012])) + "Total" => System::GetFileSize(disk_total_space("/")), + "SystemUsage" => System::GetFileSize(disk_total_space("/")-disk_free_space("/")), + "PolygonUsage" => System::GetFolderSize("/var/www/pizzaboxer.xyz/polygon/"), + "ThumbnailUsage" => System::GetFolderSize("/var/www/pizzaboxer.xyz/polygoncdn/"), + "SetupUsage" => System::GetFileSize(getSetupUsage([2009, 2010, 2011, 2012])) ] ]; @@ -51,45 +60,51 @@ pageBuilder::buildHeader();

Administration

-
-
-

You are "Moderator", 2 => "Administrator"][SESSION["adminLevel"]])?> - Choose an action

-
-
+
+

You are

+
+ + -
+ + -
+ - = 2) { ?> -
+ + -
+ -
- Staff Logs + -
+ + + - -
+ + + - - -
-
-
+

Website / Server Info

@@ -106,7 +121,8 @@ pageBuilder::buildHeader();

Disk->SystemUsage?> / Disk->Total?> Used

- is using Disk->PolygonUsage?>
+ is using Disk->PolygonUsage?>
+ Thumbnail CDN is using Disk->ThumbnailUsage?>
Client setup (2009-2012) is using Disk->SetupUsage?> total
@@ -131,7 +147,6 @@ pageBuilder::buildHeader(); dead much?
-
diff --git a/api/account/character/get-assets.php b/api/account/character/get-assets.php index 5536461..f87f56d 100644 --- a/api/account/character/get-assets.php +++ b/api/account/character/get-assets.php @@ -1,5 +1,7 @@ - "POST", "logged" => true, "secure" => true]); $wearing = isset($_POST["wearing"]) && $_POST["wearing"] == "true"; @@ -14,8 +16,8 @@ if($wearing) } else { - $type_str = catalog::getTypeByNum($type); - if(!catalog::getTypeByNum($type)) api::respond(400, false, "Invalid asset type"); + $type_str = Catalog::GetTypeByNum($type); + if(!Catalog::GetTypeByNum($type)) api::respond(400, false, "Invalid asset type"); $query = $pdo->prepare("SELECT COUNT(*) FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :uid AND assets.type = :type AND wearing = 0"); $query->bindParam(":type", $type, PDO::PARAM_INT); } @@ -23,6 +25,8 @@ $query->bindParam(":uid", $userId, PDO::PARAM_INT); $query->execute(); $pages = ceil($query->fetchColumn()/8); +if($page > $pages) $page = $pages; +if(!is_numeric($page) || $page < 1) $page = 1; $offset = ($page - 1)*8; if(!$pages) api::respond(200, true, $wearing ? 'You are not currently wearing anything' : 'You don\'t have any unequipped '.($type_str.(!str_ends_with($type_str, 's') ? 's' : '').' to wear')); diff --git a/api/account/character/paint-body.php b/api/account/character/paint-body.php index 9517a9f..bd17a12 100644 --- a/api/account/character/paint-body.php +++ b/api/account/character/paint-body.php @@ -8,7 +8,7 @@ $bodyPart = $_POST["BodyPart"] ?? false; $color = $_POST["Color"] ?? false; if(!$color || !in_array($bodyPart, ["Head", "Torso", "Left Arm", "Right Arm", "Left Leg", "Right Leg"])) api::respond(400, false, "Bad Request"); -$brickcolor = users::hex2bc(rgbtohex($color)); +$brickcolor = Users::hex2bc(rgbtohex($color)); if(!$brickcolor) api::respond(200, false, "Invalid body color #".rgbtohex($color)); $bodyColors->{$bodyPart} = $brickcolor; $bodyColors = json_encode($bodyColors); diff --git a/api/account/character/request-render.php b/api/account/character/request-render.php index 8f444ca..487e7ac 100644 --- a/api/account/character/request-render.php +++ b/api/account/character/request-render.php @@ -2,6 +2,6 @@ require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php'; api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]); -polygon::requestRender("Avatar", SESSION["userId"]); +Polygon::RequestRender("Avatar", SESSION["userId"]); api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/account/get-feed.php b/api/account/get-feed.php new file mode 100644 index 0000000..37bea5e --- /dev/null +++ b/api/account/get-feed.php @@ -0,0 +1,107 @@ + "POST", "logged_in" => true]); + +$FeedResults = db::run( + "SELECT feed.*, users.username FROM feed + INNER JOIN users ON users.id = feed.userId WHERE userId = :uid + OR groupId IS NULL AND userId IN + ( + SELECT (CASE WHEN requesterId = :uid THEN receiverId ELSE requesterId END) FROM friends + WHERE requesterId = :uid OR receiverId = :uid AND status = 1 + ) + OR groupId IN + ( + SELECT groups_members.GroupID FROM groups_members + INNER JOIN groups_ranks ON groups_ranks.GroupID = groups_members.GroupID AND groups_ranks.Rank = groups_members.Rank + WHERE groups_members.UserID = :uid AND groups_ranks.permissions LIKE '%\"CanViewGroupStatus\":true%' + ) + ORDER BY feed.id DESC LIMIT 15", + [":uid" => SESSION["userId"]] +); + +$feed = []; +$news = []; + +/*$news[] = +[ + "header" => '

lol

', + "message" => 'fucked your mom' +];*/ + +/*$news[] = +[ + "header" => '

this isn\'t dead!!!! (probably)

', + "message" => "ive been more inclined to work on polygon now after like 4 months, so i guess development has resumed

2fa has been implemented, and next on the roadmap is the catalog system. so yeah, stay tuned for that" +];*/ + +/* $news[] = +[ + "header" => "", + "img" => "https://media.discordapp.net/attachments/745025397749448814/835635922590629888/HDKolobok-256px-3.gif", + // "message" => "What you know about KOLONBOK. ™ " + "message" => "KOLONBOK. ™ Has fix 2009" +]; */ + +/* $news[] = +[ + "header" => "Groups have been released!", + "img" => "/img/ProjectPolygon.png", + "message" => "Groups have now been fully released, with more functionality than you could ever imagine. Groups don't cost anything to make, and you can join up to 20 of them.
If you haven't yet, come join the official group!" +]; */ + +while($row = $FeedResults->fetch(PDO::FETCH_OBJ)) +{ + $timestamp = timeSince($row->timestamp); + + if($row->groupId == NULL) + { + $feed[] = + [ + "userName" => $row->username, + "img" => Thumbnails::GetAvatar($row->userId, 100, 100), + "header" => "

userId}\">{$row->username} - {$timestamp}

", + "message" => Polygon::FilterText($row->text) + ]; + } + else + { + $GroupInfo = Groups::GetGroupInfo($row->groupId, true, true); + + $feed[] = + [ + "userName" => $GroupInfo->name, + "img" => Thumbnails::GetAssetFromID($GroupInfo->emblem, 420, 420), + "header" => "

id}\">{$GroupInfo->name} - posted by userId}\">{$row->username} - {$timestamp}

", + "message" => Polygon::FilterText($row->text) + ]; + } +} + +$FeedCount = $FeedResults->rowCount(); + +if($FeedCount < 15) +{ + $feed[] = + [ + "userName" => "Your feed is currently empty!", + "img" => "/img/feed/friends.png", + "header" => "

Looks like your feed's empty

", + "message" => "If you haven't made any friends yet, go make some!
If you already have some, why don't you kick off the discussion?" + ]; + + if($FeedCount < 14) + { + $feed[] = + [ + "userName" => "Customize your character", + "img" => "/img/feed/cart.png", + "header" => "

Customize your character

", + "message" => "Log in every day and earn 10 pizzas. Pizzas can be used to buy clothing in our catalog. You can also create your own clothing on the Build page." + ]; + } +} + +api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "feed" => $feed, "news" => $news]); \ No newline at end of file diff --git a/api/account/getRecentlyPlayed.php b/api/account/get-recentlyplayed.php similarity index 76% rename from api/account/getRecentlyPlayed.php rename to api/account/get-recentlyplayed.php index 484487e..9e2128a 100644 --- a/api/account/getRecentlyPlayed.php +++ b/api/account/get-recentlyplayed.php @@ -1,5 +1,8 @@ "POST", "logged_in" => true]); $userid = SESSION["userId"]; @@ -16,10 +19,10 @@ $query->execute(); while($game = $query->fetch(PDO::FETCH_OBJ)) $items[] = [ - "game_name" => polygon::filterText($game->name), + "game_name" => Polygon::FilterText($game->name), "game_id" => $game->id, "game_thumbnail" => Thumbnails::GetAvatar($game->hoster, 250, 250), - "playing" => games::getPlayersInServer($game->id)->rowCount() + "playing" => Games::GetPlayersInServer($game->id)->rowCount() ]; api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "items" => $items]); \ No newline at end of file diff --git a/api/account/transactions.php b/api/account/get-transactions.php similarity index 91% rename from api/account/transactions.php rename to api/account/get-transactions.php index 4970bec..524b40c 100644 --- a/api/account/transactions.php +++ b/api/account/get-transactions.php @@ -1,5 +1,6 @@ - "POST", "logged_in" => true, "secure" => true]); $userid = SESSION["userId"]; @@ -31,7 +32,7 @@ while($transaction = $query->fetch(PDO::FETCH_OBJ)) $transactions[] = [ "type" => $type == "Sales" ? "Sold" : "Purchased", - "date" => date('n/j/y', $transaction->timestamp), + "date" => date('j/n/y', $transaction->timestamp), "member_name" => $transaction->username, "member_id" => $memberID, "member_avatar" => Thumbnails::GetAvatar($memberID, 48, 48), diff --git a/api/account/getFeed.php b/api/account/getFeed.php deleted file mode 100644 index 4afc08c..0000000 --- a/api/account/getFeed.php +++ /dev/null @@ -1,60 +0,0 @@ - "POST", "logged_in" => true]); - -$userid = SESSION["userId"]; - -$query = $pdo->prepare(" - SELECT * FROM feed - WHERE userId = :uid - OR userId IN (SELECT receiverId FROM friends WHERE requesterId = :uid AND status = 1) - OR userId IN (SELECT requesterId FROM friends WHERE receiverId = :uid AND status = 1) - ORDER BY id DESC LIMIT 15"); -$query->bindParam(":uid", $userid, PDO::PARAM_INT); -$query->execute(); - -$feed = []; -$news = []; - -/*$news[] = -[ - "header" => '

lol

', - "message" => 'fucked your mom' -];*/ - -/*$news[] = -[ - "header" => '

this isn\'t dead!!!! (probably)

', - "message" => "ive been more inclined to work on polygon now after like 4 months, so i guess development has resumed

2fa has been implemented, and next on the roadmap is the catalog system. so yeah, stay tuned for that" -];*/ - -/* $news[] = -[ - "header" => "", - "img" => "https://media.discordapp.net/attachments/745025397749448814/835635922590629888/HDKolobok-256px-3.gif", - // "message" => "What you know about KOLONBOK. ™ " - "message" => "KOLONBOK. ™ Has fix 2009" -]; */ - -while($row = $query->fetch(PDO::FETCH_OBJ)) -{ - $feed[] = - [ - "userName" => users::getUserNameFromUid($row->userId), - "img" => Thumbnails::GetAvatar($row->userId, 100, 100), - "header" => '

'.users::getUserNameFromUid($row->userId).' - '.timeSince('@'.$row->timestamp).'

', - "message" => polygon::filterText($row->text) - ]; -} - -if($query->rowCount() < 15) -{ - $feed[] = - [ - "userName" => "Your feed is currently empty!", - "img" => "/img/feed-starter.png", - "header" => '

Looks like your feed\'s empty

', - "message" => "If you haven't made any friends yet, go make some!
If you already have some, why don't you kick off the discussion?" - ]; -} -api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "feed" => $feed, "news" => $news]); \ No newline at end of file diff --git a/api/account/uodate-password.php b/api/account/update-password.php similarity index 78% rename from api/account/uodate-password.php rename to api/account/update-password.php index 945f539..5ae65d7 100644 --- a/api/account/uodate-password.php +++ b/api/account/update-password.php @@ -1,20 +1,21 @@ - "POST", "logged_in" => true, "secure" => true]); if(!isset($_POST['currentpwd']) || !isset($_POST['newpwd']) || !isset($_POST['confnewpwd'])) api::respond(400, false, "Bad Request"); $userid = SESSION["userId"]; $row = (object)SESSION["userInfo"]; -$currentpwd = new auth($_POST['currentpwd']); -$newpwd = new auth($_POST['newpwd']); +$currentpwd = new Auth($_POST['currentpwd']); +$newpwd = new Auth($_POST['newpwd']); if($row->lastpwdchange+1800 > time()) api::respond(429, false, "Please wait ".ceil((($row->lastpwdchange+1800)-time())/60)." minutes before attempting to change your password again"); -if(!$currentpwd->verifyPassword($row->password)) api::respond(400, false, "Your current password does not match"); +if(!$currentpwd->VerifyPassword($row->password)) api::respond(400, false, "Your current password does not match"); if($_POST['currentpwd'] == $_POST['newpwd']) api::respond(400, false, "Your new password cannot be the same as your current one"); if(strlen(preg_replace('/[0-9]/', "", $_POST['newpwd'])) < 6) api::respond(400, false, "Your new password is too weak. Make sure it contains at least six non-numeric characters"); if(strlen(preg_replace('/[^0-9]/', "", $_POST['newpwd'])) < 2) api::respond(400, false, "Your new password is too weak. Make sure it contains at least two numbers"); if($_POST['newpwd'] != $_POST['confnewpwd']) api::respond(400, false, "Confirmation password does not match"); -$newpwd->updatePassword($userid); +$newpwd->UpdatePassword($userid); api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/account/ping.php b/api/account/update-ping.php similarity index 70% rename from api/account/ping.php rename to api/account/update-ping.php index 4f9abd2..a54094f 100644 --- a/api/account/ping.php +++ b/api/account/update-ping.php @@ -1,5 +1,4 @@ - "POST", "logged_in" => true, "secure" => true]); -users::updatePing(); +Users::UpdatePing(); api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "friendRequests" => (int)SESSION["friendRequests"]]); \ No newline at end of file diff --git a/api/account/update-settings.php b/api/account/update-settings.php index 2ba030b..edea276 100644 --- a/api/account/update-settings.php +++ b/api/account/update-settings.php @@ -8,10 +8,11 @@ $userid = SESSION["userId"]; $filter = (int)($_POST['filter'] == 'true'); $debugging = (int)(isset($_POST['debugging']) && $_POST['debugging'] == 'true'); -if(!in_array($_POST['theme'], ["light", "dark"])) api::respond(200, false, "Invalid theme"); +if(!in_array($_POST['theme'], ["light", "dark", "hitius", "2014"])) api::respond(200, false, "Invalid theme"); -if(!strlen($_POST['blurb'])) api::respond(200, false, "Your blurb can't be empty!"); -if(strlen($_POST['blurb']) > 1000) api::respond(200, false, "Your blurb is too large!"); +if(!strlen($_POST['blurb'])) api::respond(200, false, "Your blurb can't be empty"); +if(strlen($_POST['blurb']) > 1000) api::respond(200, false, "Your blurb is too large"); +if(Polygon::IsExplicitlyFiltered($_POST["blurb"])) api::respond(200, false, "Your blurb contains inappropriate text"); db::run( "UPDATE users SET blurb = :blurb, filter = :filter, theme = :theme, debugging = :debugging WHERE id = :uid", diff --git a/api/account/update-status.php b/api/account/update-status.php new file mode 100644 index 0000000..fbb1bf6 --- /dev/null +++ b/api/account/update-status.php @@ -0,0 +1,44 @@ + "POST", "logged_in" => true, "secure" => true]); + +$userId = SESSION["userId"]; +$status = $_POST['status'] ?? false; + +if(!strlen($status)) api::respond(200, false, "Your status cannot be empty"); +if(strlen($status) > 140) api::respond(200, false, "Your status cannot be more than 140 characters"); + +//ratelimit +$query = db::run( + "SELECT timestamp FROM feed WHERE userId = :uid AND groupID IS NULL AND timestamp+300 > UNIX_TIMESTAMP()", + [":uid" => $userId] +); + +if($query->rowCount()) + api::respond(200, false, "Please wait ".GetReadableTime($query->fetchColumn(), ["RelativeTime" => "5 minutes"])." before updating your status"); + +db::run("INSERT INTO feed (userId, timestamp, text) VALUES (:uid, UNIX_TIMESTAMP(), :status)", [":uid" => $userId, ":status" => $status]); + +db::run("UPDATE users SET status = :status WHERE id = :uid", [":uid" => $userId, ":status" => $status]); + +if(time() < strtotime("2021-09-07 00:00:00") && stripos($status, "#bezosgang") !== false && !Catalog::OwnsAsset(SESSION["userId"], 2802)) +{ + db::run( + "INSERT INTO ownedAssets (assetId, userId, timestamp) VALUES (2802, :uid, UNIX_TIMESTAMP())", + [":uid" => SESSION["userId"]] + ); +} + +Discord::SendToWebhook( + [ + "username" => SESSION["userName"], + "content" => $status, + "avatar_url" => Thumbnails::GetAvatar(SESSION["userId"], 420, 420) + ], + Discord::WEBHOOK_KUSH +); + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/account/updateStatus.php b/api/account/updateStatus.php deleted file mode 100644 index 8f6a587..0000000 --- a/api/account/updateStatus.php +++ /dev/null @@ -1,28 +0,0 @@ - "POST", "logged_in" => true, "secure" => true]); - -$userId = SESSION["userId"]; -$status = $_POST['status'] ?? false; - -if(!$status) api::respond(405, false, "Your status cannot be empty"); -if(strlen($status) > 140) api::respond(405, false, "Your status cannot be more than 140 characters"); - -//ratelimit -$query = db::run("SELECT timestamp FROM feed WHERE userId = :uid AND timestamp+60 > UNIX_TIMESTAMP()", [":uid" => $userId]); -if($query->rowCount()) api::respond(400, false, "Please wait ".(($query->fetchColumn()+60)-time())." seconds before updating your status"); - -db::run("INSERT INTO feed (userId, timestamp, text) VALUES (:uid, UNIX_TIMESTAMP(), :status)", [":uid" => $userId, ":status" => $status]); - -db::run("UPDATE users SET status = :status WHERE id = :uid", [":uid" => $userId, ":status" => $status]); - -// $status = str_ireplace("http://", "", $status); -// $status = str_ireplace("https://", "", $status); - -polygon::sendKushFeed([ - "username" => SESSION["userName"], - "content" => $status, - "avatar_url" => Thumbnails::GetAvatar(SESSION["userId"], 420, 420) -]); - -api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/admin/addBanner.php b/api/admin/addBanner.php deleted file mode 100644 index c9a14b4..0000000 --- a/api/admin/addBanner.php +++ /dev/null @@ -1,26 +0,0 @@ - "POST", "admin" => true, "admin_ratelimit" => true, "secure" => true]); - -if(!isset($_POST["text"]) || !isset($_POST["bg-color"]) || !isset($_POST["text-color"])){ api::respond(400, false, "Invalid Request"); } -if($_POST["text-color"] != "dark" && $_POST["text-color"] != "light"){ api::respond(400, false, "Invalid Request"); } -if(!trim($_POST["text"])){ api::respond(400, false, "You haven't set the banner text"); } -if(strlen($_POST["text"]) > 128){ api::respond(400, false, "The banner text must be less than 128 characters"); } -if(!trim($_POST["bg-color"])){ api::respond(400, false, "You haven't set a background color"); } -if(!ctype_xdigit(ltrim($_POST["bg-color"], "#")) || strlen($_POST["bg-color"]) != 7){ api::respond(400, false, "That doesn't appear to be a valid hex color"); } -if($pdo->query("SELECT COUNT(*) FROM announcements WHERE activated")->fetchColumn() > 5){ api::respond(400, false, "There's too many banners currently active!"); } - -$userId = SESSION["userId"]; -$text = trim($_POST["text"]); -$color = trim($_POST["bg-color"]); -$textcolor = "text-".trim($_POST["text-color"]); - -$query = $pdo->prepare("INSERT INTO announcements (createdBy, text, bgcolor, textcolor) VALUES (:uid, :text, :bgc, :tc)"); -$query->bindParam(":uid", $userId, PDO::PARAM_INT); -$query->bindParam(":text", $text, PDO::PARAM_STR); -$query->bindParam(":bgc", $color, PDO::PARAM_STR); -$query->bindParam(":tc", $textcolor, PDO::PARAM_STR); -$query->execute(); - -users::logStaffAction("[ Banners ] Created site banner with text: ".$text); -api::respond(200, true, "Banner has been created"); \ No newline at end of file diff --git a/api/admin/delete-post.php b/api/admin/delete-post.php index 579fffe..60421fb 100644 --- a/api/admin/delete-post.php +++ b/api/admin/delete-post.php @@ -1,6 +1,7 @@ - "POST", "admin" => true, "admin_ratelimit" => true, "secure" => true]); + "POST", "admin" => [Users::STAFF_MODERATOR, Users::STAFF_ADMINISTRATOR], "admin_ratelimit" => true, "secure" => true]); if(!isset($_POST['postType'])){ api::respond(400, false, "Bad Request"); } if(!in_array($_POST['postType'], ["thread", "reply"])){ api::respond(400, false, "Bad Request"); } @@ -9,12 +10,12 @@ if(!is_numeric($_POST['postId'])){ api::respond(400, false, "Bad Request"); } $userid = SESSION["userId"]; $isThread = $_POST['postType'] == "thread"; -$threadInfo = $isThread ? forum::getThreadInfo($_POST['postId']) : forum::getReplyInfo($_POST['postId']); +$threadInfo = $isThread ? Forum::GetThreadInfo($_POST['postId']) : Forum::GetReplyInfo($_POST['postId']); if(!$threadInfo){ api::respond(400, false, "Post does not exist"); } $query = $isThread ? $pdo->prepare("UPDATE forum_threads SET deleted = 1 WHERE id = :id") : $pdo->prepare("UPDATE forum_replies SET deleted = 1 WHERE id = :id"); $query->bindParam(":id", $_POST['postId'], PDO::PARAM_INT); -if($query->execute()){ users::logStaffAction("[ Forums ] Deleted forum ".($isThread?"thread":"reply")." ID ".$_POST['postId']); api::respond(200, true, "OK"); } +if($query->execute()){ Users::LogStaffAction("[ Forums ] Deleted forum ".($isThread?"thread":"reply")." ID ".$_POST['postId']); api::respond(200, true, "OK"); } else{ api::respond(500, false, "Internal Server Error"); } \ No newline at end of file diff --git a/api/admin/get-assets.php b/api/admin/get-assets.php index 7719eed..1ed694b 100644 --- a/api/admin/get-assets.php +++ b/api/admin/get-assets.php @@ -1,12 +1,14 @@ - "POST", "admin" => true, "logged_in" => true, "secure" => true]); + "POST", "admin" => [Users::STAFF_CATALOG, Users::STAFF_ADMINISTRATOR], "logged_in" => true, "secure" => true]); $type = $_POST["type"] ?? false; $page = $_POST["page"] ?? 1; $assets = []; -if(!catalog::getTypeByNum($type)) api::respond(400, false, "Invalid asset type"); +if(!Catalog::GetTypeByNum($type)) api::respond(400, false, "Invalid asset type"); $query = $pdo->prepare("SELECT COUNT(*) FROM assets WHERE creator = 2 AND type = :type ORDER BY id DESC"); $query->bindParam(":type", $type, PDO::PARAM_INT); @@ -21,7 +23,7 @@ $query->execute(); while($asset = $query->fetch(PDO::FETCH_OBJ)) { - $info = catalog::getItemInfo($asset->id); + $info = Catalog::GetAssetInfo($asset->id); $assets[] = [ diff --git a/api/admin/getUnapprovedAssets.php b/api/admin/getUnapprovedAssets.php index 6605a5d..3c5eca1 100644 --- a/api/admin/getUnapprovedAssets.php +++ b/api/admin/getUnapprovedAssets.php @@ -1,17 +1,24 @@ - "POST", "admin" => true, "secure" => true]); + "POST", "admin" => Users::STAFF, "secure" => true]); $page = $_POST["page"] ?? 1; $assets = []; -$query = $pdo->query("SELECT COUNT(*) FROM assets WHERE NOT approved AND type != 1"); +$query = $pdo->query("SELECT COUNT(*) FROM assets WHERE NOT approved AND (type != 1 || (SELECT COUNT(*) FROM polygon.groups WHERE emblem = assets.id))"); $pages = ceil($query->fetchColumn()/18); $offset = ($page - 1)*18; if(!$pages) api::respond(200, true, "There are no assets to approve"); -$query = $pdo->prepare("SELECT assets.*, users.username FROM assets INNER JOIN users ON creator = users.id WHERE NOT approved AND type != 1 LIMIT 18 OFFSET :offset"); +$query = $pdo->prepare( + "SELECT assets.*, users.username FROM assets + INNER JOIN users ON creator = users.id + WHERE NOT approved AND (type != 1 || (SELECT COUNT(*) FROM polygon.groups WHERE emblem = assets.id)) + LIMIT 18 OFFSET :offset" +); $query->bindParam(":offset", $offset, PDO::PARAM_INT); $query->execute(); @@ -26,7 +33,7 @@ while($asset = $query->fetch(PDO::FETCH_OBJ)) "texture_id" => $asset->imageID, "creator_id" => $asset->creator, "creator_name" => $asset->username, - "type" => catalog::getTypeByNum($asset->type), + "type" => Catalog::GetTypeByNum($asset->type), "created" => date("j/n/y G:i A", $asset->created), "price" => $asset->sale ? $asset->price ? ' '.$asset->price : "Free" : "Off-Sale" ]; diff --git a/api/admin/git-pull.php b/api/admin/git-pull.php index 437e7eb..42a49cb 100644 --- a/api/admin/git-pull.php +++ b/api/admin/git-pull.php @@ -26,13 +26,13 @@ $output_array = []; if($emergency) { - $webhook .= sprintf("[%s] Git Pull intiated by %s\n", date('d/m/Y h:i:s A'), "[[[EMERGENCY]]]"); + $webhook .= sprintf("[%s] Git Pull intiated by %s on %s\n", date('d/m/Y h:i:s A'), "[[[OVERRIDE]]]", $_SERVER["HTTP_HOST"]); } else { require $_SERVER["DOCUMENT_ROOT"]."/api/private/core.php"; - if(!SESSION || !SESSION["adminLevel"]) die(http_response_code(404)); - $webhook .= sprintf("[%s] Git Pull executed by %s\n", date('d/m/Y h:i:s A'), SESSION["userName"]); + if(!Users::IsAdmin(Users::STAFF_ADMINISTRATOR)) die(http_response_code(404)); + $webhook .= sprintf("[%s] Git Pull executed by %s on %s\n", date('d/m/Y h:i:s A'), SESSION["userName"], $_SERVER["HTTP_HOST"]); } exec("git pull 2>&1", $output_array, $exitcode); @@ -46,4 +46,5 @@ $webhook .= "```yaml\n"; $webhook .= $output; $webhook .= "```"; -// sendSystemWebhook($webhook); +require $_SERVER["DOCUMENT_ROOT"]."/api/private/components/Discord.php"; +Discord::SendToWebhook(["content" => $webhook], Discord::WEBHOOK_POLYGON, false); diff --git a/api/admin/giveCurrency.php b/api/admin/giveCurrency.php index 00f9e9b..c867253 100644 --- a/api/admin/giveCurrency.php +++ b/api/admin/giveCurrency.php @@ -1,6 +1,6 @@ "POST", "admin" => true, "admin_ratelimit" => true, "secure" => true]); +api::initialize(["method" => "POST", "admin" => Users::STAFF_ADMINISTRATOR, "admin_ratelimit" => true, "secure" => true]); if(SESSION["userId"] != 1){ api::respond(400, false, "Insufficient admin level"); } if(!isset($_POST["username"]) || !isset($_POST["amount"]) || !isset($_POST["reason"])){ api::respond(400, false, "Invalid Request"); } @@ -13,7 +13,7 @@ if($_POST["amount"] > 500 || $_POST["amount"] < -500){ api::respond(400, false, if(!trim($_POST["reason"])){ api::respond(400, false, "You must set a reason"); } $amount = $_POST["amount"]; -$userInfo = users::getUserInfoFromUserName($_POST["username"]); +$userInfo = Users::GetInfoFromName($_POST["username"]); if(!$userInfo){ api::respond(400, false, "That user doesn't exist"); } if(($userInfo->currency + $_POST["amount"]) < 0){ api::respond(400, false, "That'll make the user go bankrupt!"); } @@ -22,5 +22,5 @@ $query->bindParam(":amount", $amount, PDO::PARAM_INT); $query->bindParam(":uid", $userInfo->id, PDO::PARAM_INT); $query->execute(); -users::logStaffAction("[ Currency ] Gave ".$_POST["amount"]." ".SITE_CONFIG["site"]["currency"]." to ".$userInfo->username." ( user ID ".$userInfo->id." ) ( Reason: ".$_POST["reason"]." )"); +Users::LogStaffAction("[ Currency ] Gave ".$_POST["amount"]." ".SITE_CONFIG["site"]["currency"]." to ".$userInfo->username." ( user ID ".$userInfo->id." ) ( Reason: ".$_POST["reason"]." )"); api::respond(200, true, "Gave ".$_POST["amount"]." ".SITE_CONFIG["site"]["currency"]." to ".$userInfo->username); \ No newline at end of file diff --git a/api/admin/moderateAsset.php b/api/admin/moderateAsset.php index 537d9b6..db69548 100644 --- a/api/admin/moderateAsset.php +++ b/api/admin/moderateAsset.php @@ -1,12 +1,13 @@ - "POST", "admin" => true, "secure" => true]); + "POST", "admin" => Users::STAFF, "secure" => true]); $assetId = $_POST['assetID'] ?? false; $action = $_POST['action'] ?? false; $action_sql = $action == "approve" ?: 2; $reason = $_POST['reason'] ?? false; -$asset = catalog::getItemInfo($assetId); +$asset = Catalog::GetAssetInfo($assetId); if(!in_array($action, ["approve", "decline"])) api::respond(400, false, "Invalid request"); if(!$asset) api::respond(400, false, "Asset does not exist"); @@ -17,5 +18,5 @@ $query->bindParam(":id", $asset->id, PDO::PARAM_INT); $query->bindParam(":image", $asset->imageID, PDO::PARAM_INT); $query->execute(); -users::logStaffAction('[ Asset Moderation ] '.ucfirst($action).'d "'.$asset->name.'" [ID '.$asset->id.']'.($reason ? ' with reason: '.$reason : '')); +Users::LogStaffAction('[ Asset Moderation ] '.ucfirst($action).'d "'.$asset->name.'" [ID '.$asset->id.']'.($reason ? ' with reason: '.$reason : '')); api::respond(200, true, '"'.htmlspecialchars($asset->name).'" has been '.$action.'d'); \ No newline at end of file diff --git a/api/admin/moderateUser.php b/api/admin/moderateUser.php index b87abcf..6e86d07 100644 --- a/api/admin/moderateUser.php +++ b/api/admin/moderateUser.php @@ -1,6 +1,6 @@ "POST", "admin" => true, "admin_ratelimit" => true, "secure" => true]); +api::initialize(["method" => "POST", "admin" => [Users::STAFF_MODERATOR, Users::STAFF_ADMINISTRATOR], "admin_ratelimit" => true, "secure" => true]); if(!isset($_POST["username"]) || !isset($_POST["banType"]) || !isset($_POST["moderationNote"]) || !isset($_POST["until"])){ api::respond(400, false, "Bad Request"); } if($_POST["banType"] < 1 || $_POST["banType"] > 4){ api::respond(400, false, "Bad Request"); } @@ -11,7 +11,7 @@ if($_POST["banType"] == 2 && empty($_POST["until"])){ api::respond(200, false, " $banType = $_POST["banType"]; $staffNote = isset($_POST["staffNote"]) && $_POST["staffNote"] ? $_POST["staffNote"] : ""; $userId = SESSION["userId"]; -$bannerInfo = users::getUserInfoFromUserName($_POST["username"]); +$bannerInfo = Users::GetInfoFromName($_POST["username"]); $reason = $_POST["moderationNote"]; $bannedUntil = $_POST["banType"] == 2 ? strtotime($_POST["until"]." ".date('G:i:s')) : 0; @@ -19,14 +19,14 @@ if(!$bannerInfo){ api::respond(200, false, "User does not exist"); } if($banType == 4) { - if(!users::getUserModeration($bannerInfo->id)){ api::respond(200, false, "That user isn't banned!"); } - users::undoUserModeration($bannerInfo->id, true); + if(!Users::GetUserModeration($bannerInfo->id)){ api::respond(200, false, "That user isn't banned!"); } + Users::UndoUserModeration($bannerInfo->id, true); } else { // if($bannerInfo->id == $userId){ api::respond(200, false, "You cannot moderate yourself!"); } // if($bannerInfo->adminlevel){ api::respond(200, false, "You cannot moderate a staff member"); } - if(users::getUserModeration($bannerInfo->id)){ api::respond(200, false, "That user is already banned!"); } + if(Users::GetUserModeration($bannerInfo->id)){ api::respond(200, false, "That user is already banned!"); } if($banType == 2 && $bannedUntil < strtotime('tomorrow')){ api::respond(200, false, "Ban time must be at least 1 day long"); } $query = $pdo->prepare("INSERT INTO bans (userId, bannerId, timeStarted, timeEnds, reason, banType, note) VALUES (:bid, :uid, UNIX_TIMESTAMP(), :ends, :reason, :type, :note)"); @@ -55,5 +55,5 @@ $staff = 4 => "Unbanned ".$bannerInfo->username ]; -users::logStaffAction("[ User Moderation ] ".$staff[$banType]." ( user ID ".$bannerInfo->id." )"); +Users::LogStaffAction("[ User Moderation ] ".$staff[$banType]." ( user ID ".$bannerInfo->id." )"); api::respond(200, true, $bannerInfo->username." has been ".$text[$banType]); \ No newline at end of file diff --git a/api/admin/previewModeration.php b/api/admin/previewModeration.php index 677a734..9a93a72 100644 --- a/api/admin/previewModeration.php +++ b/api/admin/previewModeration.php @@ -1,6 +1,6 @@ "POST", "admin" => true, "secure" => true]); +api::initialize(["method" => "POST", "admin" => [Users::STAFF_MODERATOR, Users::STAFF_ADMINISTRATOR], "secure" => true]); if(!isset($_POST["banType"]) || !isset($_POST["moderationNote"]) || !isset($_POST["until"])){ api::respond(400, false, "Invalid Request"); } if($_POST["banType"] < 1 || $_POST["banType"] > 3){ api::respond(400, false, "Invalid Request"); } diff --git a/api/admin/request-render.php b/api/admin/request-render.php index d7b46d0..082a250 100644 --- a/api/admin/request-render.php +++ b/api/admin/request-render.php @@ -1,6 +1,7 @@ - "POST", "admin" => true, "secure" => true]); + "POST", "admin" => Users::STAFF, "secure" => true]); $renderType = $_POST['renderType'] ?? false; $assetID = $_POST['assetID'] ?? false; @@ -11,24 +12,24 @@ if(!$assetID || !is_numeric($assetID)) api::respond(400, false, "Bad Request"); if($renderType == "Asset") { - $asset = catalog::getItemInfo($assetID); + $asset = Catalog::GetAssetInfo($assetID); if(!$asset) api::respond(200, false, "The asset you requested does not exist"); switch($asset->type) { - case 4: polygon::requestRender("Mesh", $assetID); break; // mesh - case 8: case 19: polygon::requestRender("Model", $assetID); break; // hat/gear - case 11: case 12: polygon::requestRender("Clothing", $assetID); break; // shirt/pants - case 17: polygon::requestRender("Head", $assetID); break; // head - case 10: polygon::requestRender("UserModel", $assetID); break; // user generated model + case 4: Polygon::RequestRender("Mesh", $assetID); break; // mesh + case 8: case 19: Polygon::RequestRender("Model", $assetID); break; // hat/gear + case 11: case 12: Polygon::RequestRender("Clothing", $assetID); break; // shirt/pants + case 17: Polygon::RequestRender("Head", $assetID); break; // head + case 10: Polygon::RequestRender("UserModel", $assetID); break; // user generated model default: api::respond(200, false, "This asset cannot be re-rendered"); } } else if($renderType == "Avatar") { - $user = users::getUserInfoFromUid($assetID); + $user = Users::GetInfoFromID($assetID); if(!$user) api::respond(200, false, "The user you requested does not exist"); - polygon::requestRender("Avatar", $assetID); + Polygon::RequestRender("Avatar", $assetID); } -users::logStaffAction("[ Render ] Re-rendered $renderType ID $assetID"); +Users::LogStaffAction("[ Render ] Re-rendered $renderType ID $assetID"); api::respond(200, true, "Render request has been successfully submitted! See render status here"); \ No newline at end of file diff --git a/api/admin/upload.php b/api/admin/upload.php index 8410208..b2ed8e5 100644 --- a/api/admin/upload.php +++ b/api/admin/upload.php @@ -1,18 +1,21 @@ - "POST", "admin" => true, "secure" => true]); + "POST", "admin" => [Users::STAFF_CATALOG, Users::STAFF_ADMINISTRATOR], "secure" => true]); $file = $_FILES["file"] ?? false; $name = $_POST["name"] ?? false; $type = $_POST["type"] ?? false; $uploadas = $_POST["creator"] ?? "Polygon"; -$creator = users::getUidFromUserName($uploadas); +$creator = Users::GetIDFromName($uploadas); if(!$file) api::respond(200, false, "You must select a file"); if(!$name) api::respond(200, false, "You must specify a name"); if(strlen($name) > 50) api::respond(200, false, "Name cannot be longer than 50 characters"); if(!$creator) api::respond(400, false, "The user you're trying to create as does not exist"); -if(polygon::filterText($name, false, false, true) != $name) api::respond(400, false, "The name contains inappropriate text"); +if(Polygon::FilterText($name, false, false, true) != $name) api::respond(400, false, "The name contains inappropriate text"); //$lastCreation = $pdo->query("SELECT created FROM assets WHERE creator = 2 ORDER BY id DESC")->fetchColumn(); //if($lastCreation+60 > time()) api::respond(400, false, "Please wait ".(60-(time()-$lastCreation))." seconds before creating a new asset"); @@ -21,72 +24,72 @@ if($type == 1) //image - this is for textures and stuff { if(!in_array($file["type"], ["image/png", "image/jpg", "image/jpeg"])) api::respond(400, false, "Must be a .png or .jpg file"); - polygon::importLibrary("class.upload"); + Polygon::ImportLibrary("class.upload"); $image = new Upload($file); if(!$image->uploaded) api::respond(500, false, "Failed to process image - please contact an admin"); $image->allowed = ['image/png', 'image/jpg', 'image/jpeg']; $image->image_convert = 'png'; - $imageId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); - image::process($image, ["name" => "$imageId", "resize" => false, "dir" => "/asset/files/"]); + $imageId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); + Image::Process($image, ["name" => "$imageId", "resize" => false, "dir" => "/asset/files/"]); Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "C"]); Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "C"]); } elseif($type == 3) // audio { - if(!in_array($file["type"], ["audio/mpeg", "audio/ogg", "audio/mid", "audio/wav", "video/ogg"])) api::respond(400, false, "Must be an mpeg, wav, ogg or midi audio. - ".$file["type"]); - $assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "audioType" => $file["type"], "approved" => 1]); + if(!in_array($file["type"], ["audio/mpeg", "audio/ogg", "audio/mid", "audio/wav"])) api::respond(400, false, "Must be an mpeg, wav, ogg or midi audio. - ".$file["type"]); + $assetId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "audioType" => $file["type"], "approved" => 1]); copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId); - image::renderfromimg("audio", $assetId); + Image::RenderFromStaticImage("audio", $assetId); } elseif($type == 4) //mesh { if(!str_ends_with($file["name"], ".mesh")) api::respond(400, false, "Must be a .mesh file"); - $assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); + $assetId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId); - polygon::requestRender("Mesh", $assetId); + Polygon::RequestRender("Mesh", $assetId); } elseif($type == 5) //lua { if(!str_ends_with($file["name"], ".lua")) api::respond(400, false, "Must be a .lua file"); - $assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); + $assetId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId); - image::renderfromimg("Script", $assetId); + Image::RenderFromStaticImage("Script", $assetId); } elseif($type == 8) //hat { if(!str_ends_with($file["name"], ".xml") && !str_ends_with($file["name"], ".rbxm")) api::respond(400, false, "Must be a .rbxm or .xml file"); - $assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); + $assetId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId); - polygon::requestRender("Model", $assetId); + Polygon::RequestRender("Model", $assetId); } elseif($type == 17) //head { if(!str_ends_with($file["name"], ".xml") && !str_ends_with($file["name"], ".rbxm")) api::respond(400, false, "Must be a .rbxm or .xml file"); - $assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); + $assetId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId); - polygon::requestRender("Head", $assetId); + Polygon::RequestRender("Head", $assetId); } elseif($type == 18) //faces are literally just decals lmao (with a minor alteration to the xml) { if(!in_array($file["type"], ["image/png", "image/jpg", "image/jpeg"])) api::respond(400, false, "Must be a .png or .jpg file"); - polygon::importLibrary("class.upload"); + Polygon::ImportLibrary("class.upload"); $image = new Upload($file); if(!$image->uploaded) api::respond(500, false, "Failed to process image - please contact an admin"); $image->allowed = ['image/png', 'image/jpg', 'image/jpeg']; $image->image_convert = 'png'; - $imageId = catalog::createAsset(["type" => 1, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); - image::process($image, ["name" => "$imageId", "resize" => false, "dir" => "/asset/files/"]); + $imageId = Catalog::CreateAsset(["type" => 1, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1]); + Image::Process($image, ["name" => "$imageId", "resize" => false, "dir" => "/asset/files/"]); Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "C"]); Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "C"]); - $itemId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "imageID" => $imageId, "approved" => 1]); + $itemId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "imageID" => $imageId, "approved" => 1]); - file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, catalog::generateGraphicXML("Face", $imageId)); + file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, Catalog::GenerateGraphicXML("Face", $imageId)); Thumbnails::UploadAsset($image, $itemId, 420, 230); Thumbnails::UploadAsset($image, $itemId, 420, 420); @@ -101,10 +104,10 @@ elseif($type == 19) //gear { if(!str_ends_with($file["name"], ".xml") && !str_ends_with($file["name"], ".rbxm")) api::respond(400, false, "Must be a .rbxm or .xml file"); - $assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1, "gear_attributes" => '{"melee":false,"powerup":false,"ranged":false,"navigation":false,"explosive":false,"musical":false,"social":false,"transport":false,"building":false}']); + $assetId = Catalog::CreateAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "approved" => 1, "gear_attributes" => '{"melee":false,"powerup":false,"ranged":false,"navigation":false,"explosive":false,"musical":false,"social":false,"transport":false,"building":false}']); copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId); - polygon::requestRender("Model", $assetId); + Polygon::RequestRender("Model", $assetId); } -users::logStaffAction("[ Asset creation ] Created \"$name\" [ID ".($itemId ?? $assetId ?? $imageId)."]"); -api::respond_custom(["status" => 200, "success" => true, "message" => "".catalog::getTypeByNum($type)." successfully created!"]); \ No newline at end of file +Users::LogStaffAction("[ Asset creation ] Created \"$name\" [ID ".($itemId ?? $assetId ?? $imageId)."]"); +api::respond_custom(["status" => 200, "success" => true, "message" => "".Catalog::GetTypeByNum($type)." successfully created!"]); \ No newline at end of file diff --git a/api/catalog/get-comments.php b/api/catalog/get-comments.php index 34ea1bc..f3cf815 100644 --- a/api/catalog/get-comments.php +++ b/api/catalog/get-comments.php @@ -1,5 +1,6 @@ -fetch(PDO::FETCH_OBJ)) "commenter_name" => $row->username, "commenter_id" => $row->author, "commenter_avatar" => Thumbnails::GetAvatar($row->author, 110, 110), - "content" => nl2br(polygon::filterText($row->content)) + "content" => nl2br(Polygon::FilterText($row->content)) ]; } diff --git a/api/catalog/post-comment.php b/api/catalog/post-comment.php index 1909892..10fd74a 100644 --- a/api/catalog/post-comment.php +++ b/api/catalog/post-comment.php @@ -1,5 +1,6 @@ - "POST", "logged_in" => true, "secure" => true]); if(!isset($_POST['assetID']) || !isset($_POST['content'])); @@ -8,7 +9,7 @@ $uid = SESSION["userId"]; $id = $_POST['assetID']; $content = $_POST['content']; -$item = catalog::getItemInfo($id); +$item = Catalog::GetAssetInfo($id); if(!$item) api::respond(400, false, "Asset does not exist"); if(!$item->comments) api::respond(400, false, "Comments are unavailable for this asset"); if(!strlen($content)) api::respond(400, false, "Comment cannot be empty"); @@ -17,8 +18,7 @@ if(strlen($content) > 100) api::respond(400, false, "Comment cannot be longer th $query = $pdo->prepare("SELECT time FROM asset_comments WHERE time+60 > UNIX_TIMESTAMP() AND author = :uid"); $query->bindParam(":uid", $uid, PDO::PARAM_INT); $query->execute(); -$lastComment = $query->fetchColumn(); -if($lastComment) api::respond(400, false, "Please wait ".(60-(time()-$lastComment))." seconds before posting a new comment"); +if($query->rowCount()) api::respond(400, false, "Please wait ".GetReadableTime($query->fetchColumn(), ["RelativeTime" => "1 minute"])." before posting a new comment"); $query = $pdo->prepare("INSERT INTO asset_comments (author, content, assetID, time) VALUES (:uid, :content, :aid, UNIX_TIMESTAMP())"); $query->bindParam(":uid", $uid, PDO::PARAM_INT); diff --git a/api/catalog/purchase.php b/api/catalog/purchase.php index 9a05cbd..4993465 100644 --- a/api/catalog/purchase.php +++ b/api/catalog/purchase.php @@ -1,5 +1,7 @@ - "POST", "logged_in" => true, "secure" => true]); function getPrice($price) @@ -11,12 +13,14 @@ $uid = SESSION["userId"]; $id = $_POST['id'] ?? false; $price = $_POST['price'] ?? 0; -$item = catalog::getItemInfo($id); +$item = Catalog::GetAssetInfo($id); if(!$item) api::respond(400, false, "Asset does not exist"); -if(catalog::ownsAsset($uid, $id)) api::respond(400, false, "User already owns asset"); +if(Catalog::OwnsAsset($uid, $id)) api::respond(400, false, "User already owns asset"); if(!$item->sale) api::respond(400, false, "Asset is off-sale"); if(SESSION["currency"] - $item->price < 0) api::respond(400, false, "User cannot afford asset"); + if($item->price != $price) +{ die(json_encode( [ "status" => 200, @@ -28,12 +32,23 @@ if($item->price != $price) "footer" => 'Your balance after this transaction will be '.(SESSION["currency"] - $item->price), "newprice" => $item->price ])); +} -$query = $pdo->prepare("UPDATE users SET currency = currency - :price WHERE id = :uid; UPDATE users SET currency = currency + :price WHERE id = :seller"); -$query->bindParam(":price", $item->price, PDO::PARAM_INT); -$query->bindParam(":uid", $uid, PDO::PARAM_INT); -$query->bindParam(":seller", $item->creator, PDO::PARAM_INT); -$query->execute(); +$IsAlt = false; + +foreach(Users::GetAlternateAccounts($item->creator) as $alt) +{ + if($alt["userid"] == $uid) $IsAlt = true; +} + +if(!$IsAlt) +{ + $query = $pdo->prepare("UPDATE users SET currency = currency - :price WHERE id = :uid; UPDATE users SET currency = currency + :price WHERE id = :seller"); + $query->bindParam(":price", $item->price, PDO::PARAM_INT); + $query->bindParam(":uid", $uid, PDO::PARAM_INT); + $query->bindParam(":seller", $item->creator, PDO::PARAM_INT); + $query->execute(); +} $query = $pdo->prepare("INSERT INTO ownedAssets (assetId, userId, timestamp) VALUES (:aid, :uid, UNIX_TIMESTAMP())"); $query->bindParam(":aid", $id, PDO::PARAM_INT); @@ -47,6 +62,14 @@ $query->bindParam(":aid", $id, PDO::PARAM_INT); $query->bindParam(":price", $item->price, PDO::PARAM_INT); $query->execute(); +if(time() < strtotime("2021-09-07 00:00:00") && $id == 2692 && !Catalog::OwnsAsset(SESSION["userId"], 2800)) +{ + db::run( + "INSERT INTO ownedAssets (assetId, userId, timestamp) VALUES (2800, :uid, UNIX_TIMESTAMP())", + [":uid" => SESSION["userId"]] + ); +} + die(json_encode( [ "status" => 200, @@ -54,6 +77,6 @@ die(json_encode( "message" => "OK", "header" => "Purchase Complete!", "image" => Thumbnails::GetAsset($item, 110, 110), - "text" => "You have successfully purchased the ".htmlspecialchars($item->name)." ".catalog::getTypeByNum($item->type)." from ".$item->username." for ".getPrice($item->price), + "text" => "You have successfully purchased the ".htmlspecialchars($item->name)." ".Catalog::GetTypeByNum($item->type)." from ".$item->username." for ".getPrice($item->price), "buttons" => [['class' => 'btn btn-primary continue-shopping', 'dismiss' => true, 'text' => 'Continue Shopping']], ])); \ No newline at end of file diff --git a/api/develop/getCreations.php b/api/develop/getCreations.php index 42a70d4..02b99b7 100644 --- a/api/develop/getCreations.php +++ b/api/develop/getCreations.php @@ -1,5 +1,7 @@ - "POST", "logged_in" => true, "secure" => true]); $userid = SESSION["userId"]; @@ -7,7 +9,7 @@ $type = $_POST["type"] ?? false; $page = $_POST["page"] ?? 1; $assets = []; -if(!catalog::getTypeByNum($type)) api::respond(400, false, "Invalid asset type"); +if(!Catalog::GetTypeByNum($type)) api::respond(400, false, "Invalid asset type"); $query = $pdo->prepare("SELECT * FROM assets WHERE creator = :uid AND type = :type ORDER BY id DESC"); $query->bindParam(":uid", $userid, PDO::PARAM_INT); @@ -16,7 +18,7 @@ $query->execute(); while($asset = $query->fetch(PDO::FETCH_OBJ)) { - $info = catalog::getItemInfo($asset->id); + $info = Catalog::GetAssetInfo($asset->id); $assets[] = [ diff --git a/api/develop/upload.php b/api/develop/upload.php index 283d7a9..76c6cf2 100644 --- a/api/develop/upload.php +++ b/api/develop/upload.php @@ -1,5 +1,8 @@ - "POST", "logged_in" => true, "secure" => true]); $userid = SESSION["userId"]; @@ -7,17 +10,17 @@ $file = $_FILES["file"] ?? false; $name = $_POST["name"] ?? false; $type = $_POST["type"] ?? false; -if(!$file) api::respond(400, false, "You must select a file"); -if(!in_array($file["type"], ["image/png", "image/jpg", "image/jpeg"])) api::respond(400, false, "Must be a .png or .jpg file"); -if(!$name) api::respond(400, false, "You must specify a name"); -if(polygon::filterText($name, false, false, true) != $name) api::respond(400, false, "The name contains inappropriate text"); -if(!in_array($type, [2, 11, 12, 13])) api::respond(400, false, "You can't upload that type of content!"); +if(!$file) api::respond(200, false, "You must select a file"); +if(!in_array($file["type"], ["image/png", "image/jpg", "image/jpeg"])) api::respond(200, false, "Must be a .png or .jpg file"); +if(!$name) api::respond(200, false, "You must specify a name"); +if(Polygon::IsExplicitlyFiltered($name)) api::respond(200, false, "The name contains inappropriate text"); +if(!in_array($type, [2, 11, 12, 13])) api::respond(200, false, "You can't upload that type of content!"); $query = $pdo->prepare("SELECT created FROM assets WHERE creator = :uid ORDER BY id DESC"); $query->bindParam(":uid", $userid, PDO::PARAM_INT); $query->execute(); $lastCreation = $query->fetchColumn(); -if($lastCreation+30 > time()) api::respond(400, false, "Please wait ".(30-(time()-$lastCreation))." seconds before creating a new asset"); +if($lastCreation+30 > time()) api::respond(200, false, "Please wait ".(30-(time()-$lastCreation))." seconds before creating a new asset"); // tshirts are a bit messy but straightforward: // the image asset itself must be 128x128 with the texture resized to preserve aspect ratio @@ -57,35 +60,35 @@ if($lastCreation+30 > time()) api::respond(400, false, "Please wait ".(30-(time( // Decal/Face |yes (s)| |yes (s)| yes (s) | yes (s) | | yes (s) | yes (s) | yes (s) | yes (s) | // +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+ -polygon::importLibrary("class.upload"); +Polygon::ImportLibrary("class.upload"); $image = new Upload($file); if(!$image->uploaded) api::respond(200, false, "Failed to process image - please contact an admin"); $image->allowed = ['image/png', 'image/jpg', 'image/jpeg']; $image->image_convert = 'png'; -$imageId = catalog::createAsset(["type" => 1, "creator" => SESSION["userId"], "name" => $name, "description" => catalog::getTypeByNum($type)." Image"]); +$imageId = Catalog::CreateAsset(["type" => 1, "creator" => SESSION["userId"], "name" => $name, "description" => Catalog::GetTypeByNum($type)." Image"]); if($type == 2) //tshirt { - image::process($image, ["name" => "$imageId", "keepRatio" => true, "align" => "T", "x" => 128, "y" => 128, "dir" => "/asset/files/"]); + Image::Process($image, ["name" => "$imageId", "keepRatio" => true, "align" => "T", "x" => 128, "y" => 128, "dir" => "/asset/files/"]); Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "T"]); Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "T"]); - $itemId = catalog::createAsset(["type" => 2, "creator" => SESSION["userId"], "name" => $name, "description" => "T-Shirt", "imageID" => $imageId]); + $itemId = Catalog::CreateAsset(["type" => 2, "creator" => SESSION["userId"], "name" => $name, "description" => "T-Shirt", "imageID" => $imageId]); - file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, catalog::generateGraphicXML("T-Shirt", $imageId)); + file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, Catalog::GenerateGraphicXML("T-Shirt", $imageId)); //process initial tshirt thumbnail $template = imagecreatefrompng($_SERVER['DOCUMENT_ROOT']."/img/tshirt-template.png"); - $shirtdecal = image::resize(SITE_CONFIG['paths']['thumbs_assets']."/$imageId-420x420.png", 250, 250); + $shirtdecal = Image::Resize(SITE_CONFIG['paths']['thumbs_assets']."/$imageId-420x420.png", 250, 250); imagesavealpha($template, true); imagesavealpha($shirtdecal, true); - image::merge($template, $shirtdecal, 85, 85, 0, 0, 250, 250, 100); + Image::MergeLayers($template, $shirtdecal, 85, 85, 0, 0, 250, 250, 100); imagepng($template, SITE_CONFIG['paths']['thumbs_assets']."/$itemId-420x420.png"); - image::resize(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-420x420.png", 100, 100, SITE_CONFIG['paths']['thumbs_assets']."/$itemId-100x100.png"); - image::resize(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-420x420.png", 110, 110, SITE_CONFIG['paths']['thumbs_assets']."/$itemId-110x110.png"); + Image::Resize(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-420x420.png", 100, 100, SITE_CONFIG['paths']['thumbs_assets']."/$itemId-100x100.png"); + Image::Resize(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-420x420.png", 110, 110, SITE_CONFIG['paths']['thumbs_assets']."/$itemId-110x110.png"); Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-100x100.png"); Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-110x110.png"); @@ -93,31 +96,31 @@ if($type == 2) //tshirt } elseif($type == 11 || $type == 12) //shirt / pants { - image::process($image, ["name" => "$imageId", "x" => 585, "y" => 559, "dir" => "/asset/files/"]); + Image::Process($image, ["name" => "$imageId", "x" => 585, "y" => 559, "dir" => "/asset/files/"]); Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "C"]); Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "C"]); - $itemId = catalog::createAsset(["type" => $type, "creator" => SESSION["userId"], "name" => $name, "description" => catalog::getTypeByNum($type), "imageID" => $imageId]); - file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, catalog::generateGraphicXML(catalog::getTypeByNum($type), $imageId)); - polygon::requestRender("Clothing", $itemId); + $itemId = Catalog::CreateAsset(["type" => $type, "creator" => SESSION["userId"], "name" => $name, "description" => Catalog::GetTypeByNum($type), "imageID" => $imageId]); + file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, Catalog::GenerateGraphicXML(Catalog::GetTypeByNum($type), $imageId)); + Polygon::RequestRender("Clothing", $itemId); } elseif($type == 13) //decal { - image::process($image, ["name" => "$imageId", "x" => 256, "scaleY" => true, "dir" => "/asset/files/"]); + Image::Process($image, ["name" => "$imageId", "x" => 256, "scaleY" => true, "dir" => "/asset/files/"]); Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "C"]); Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "C"]); - $itemId = catalog::createAsset(["type" => 13, "creator" => SESSION["userId"], "name" => $name, "description" => "Decal", "imageID" => $imageId]); + $itemId = Catalog::CreateAsset(["type" => 13, "creator" => SESSION["userId"], "name" => $name, "description" => "Decal", "imageID" => $imageId]); - file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, catalog::generateGraphicXML("Decal", $imageId)); - image::process($image, ["name" => "$itemId-48x48.png", "x" => 48, "y" => 48, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-75x75.png", "x" => 75, "y" => 75, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-100x100.png", "x" => 100, "y" => 100, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-110x110.png", "x" => 110, "y" => 110, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-250x250.png", "x" => 250, "y" => 250, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-352x352.png", "x" => 352, "y" => 352, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-420x230.png", "x" => 420, "y" => 230, "dir" => "/thumbs/assets/"]); - image::process($image, ["name" => "$itemId-420x420.png", "x" => 420, "y" => 420, "dir" => "/thumbs/assets/"]); + file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, Catalog::GenerateGraphicXML("Decal", $imageId)); + Thumbnails::UploadAsset($image, $itemId, 48, 48); + Thumbnails::UploadAsset($image, $itemId, 75, 75); + Thumbnails::UploadAsset($image, $itemId, 100, 100); + Thumbnails::UploadAsset($image, $itemId, 110, 110); + Thumbnails::UploadAsset($image, $itemId, 250, 250); + Thumbnails::UploadAsset($image, $itemId, 352, 352); + Thumbnails::UploadAsset($image, $itemId, 420, 230); + Thumbnails::UploadAsset($image, $itemId, 420, 420); } -api::respond_custom(["status" => 200, "success" => true, "message" => catalog::getTypeByNum($type)." successfully created!"]); \ No newline at end of file +api::respond_custom(["status" => 200, "success" => true, "message" => Catalog::GetTypeByNum($type)." successfully created!"]); \ No newline at end of file diff --git a/api/discord/check-verification.php b/api/discord/check-verification.php new file mode 100644 index 0000000..dee9537 --- /dev/null +++ b/api/discord/check-verification.php @@ -0,0 +1,25 @@ + "GET", "api" => "DiscordBot"]); + +if (isset($_GET["Token"]) && isset($_GET["DiscordID"])) +{ + $userInfo = db::run("SELECT * FROM users WHERE discordKey = :key", [":key" => $_GET["Token"]])->fetch(PDO::FETCH_OBJ); + if (!$userInfo) api::respond(200, false, "InvalidKey"); // check if verification key is valid + if ($userInfo->discordID != NULL) api::respond(200, false, "AlreadyVerified"); // check if mercury account is already verified + + db::run( + "UPDATE users SET discordID = :id, discordVerifiedTime = UNIX_TIMESTAMP() WHERE discordKey = :key", + [":id" => $_GET["DiscordID"], ":key" => $_GET["Token"]] + ); + + api::respond(200, true, $userInfo->username); +} +else if (isset($_GET["DiscordID"])) +{ + $username = db::run("SELECT username FROM users WHERE discordID = :id", [":id" => $_GET["DiscordID"]]); + if (!$username->rowCount()) api::respond(200, false, "NotVerified"); // check if discord account is already verified + api::respond(200, true, $username->fetchColumn()); +} + +api::respond(400, false, "Bad Request"); \ No newline at end of file diff --git a/api/discord/whois.php b/api/discord/whois.php new file mode 100644 index 0000000..182f205 --- /dev/null +++ b/api/discord/whois.php @@ -0,0 +1,28 @@ + "GET", "api" => "DiscordBot"]); + +if (isset($_GET["UserName"])) +{ + $userInfo = db::run( + "SELECT id, username, blurb, adminlevel, jointime, lastonline, discordID FROM users WHERE username = :name", + [":name" => $_GET["UserName"]] + )->fetch(PDO::FETCH_OBJ); + if (!$userInfo) api::respond(200, false, "DoesntExist"); +} +else if (isset($_GET["DiscordID"])) +{ + $userInfo = db::run( + "SELECT id, username, blurb, adminlevel, jointime, lastonline, discordID FROM users WHERE discordID = :id", + [":id" => $_GET["DiscordID"]] + )->fetch(PDO::FETCH_OBJ); + if (!$userInfo) api::respond(200, false, "NotVerified"); +} +else +{ + api::respond(400, false, "Bad Request"); +} + +$userInfo->blurb = str_ireplace(["@everyone", "@here"], ["[everyone]", "[here]"], $userInfo->blurb); +$userInfo->blurb = preg_replace("/<(@[0-9]+)>/i", "[$1]", $userInfo->blurb); +api::respond(200, true, $userInfo); \ No newline at end of file diff --git a/api/friends/getFriendRequests.php b/api/friends/getFriendRequests.php index 1fbc6f0..4ed29b6 100644 --- a/api/friends/getFriendRequests.php +++ b/api/friends/getFriendRequests.php @@ -1,5 +1,6 @@ - "POST", "logged_in" => true, "secure" => true]); $userid = SESSION["userId"]; @@ -25,7 +26,7 @@ while($row = $query->fetch(PDO::FETCH_OBJ)) { $friends[] = [ - "username" => users::getUserNameFromUid($row->requesterId), + "username" => Users::GetNameFromID($row->requesterId), "userid" => $row->requesterId, "avatar" => Thumbnails::GetAvatar($row->requesterId, 250, 250), "friendid" => $row->id diff --git a/api/friends/getFriends.php b/api/friends/getFriends.php index 9311d6b..730f855 100644 --- a/api/friends/getFriends.php +++ b/api/friends/getFriends.php @@ -1,5 +1,6 @@ - "POST"]); $url = $_SERVER['HTTP_REFERER'] ?? false; @@ -9,7 +10,7 @@ $order = strpos($url, "/home") ? "lastonline DESC" : "id"; $limit = strpos($url, "/friends") ? 18 : 6; $self = str_ends_with($url, "/user") || str_ends_with($url, "/friends") || strpos($url, "/home"); -if(!users::getUserInfoFromUid($userId)) api::respond(400, false, "User does not exist"); +if(!Users::GetInfoFromID($userId)) api::respond(400, false, "User does not exist"); $query = $pdo->prepare("SELECT COUNT(*) FROM friends WHERE :uid IN (requesterId, receiverId) AND status = 1"); $query->bindParam(":uid", $userId, PDO::PARAM_INT); @@ -18,7 +19,7 @@ $query->execute(); $pages = ceil($query->fetchColumn()/$limit); $offset = ($page - 1)*$limit; -if(!$pages) api::respond(200, true, ($self ? "You do" : users::getUserNameFromUid($userId)." does")."n't have any friends"); +if(!$pages) api::respond(200, true, ($self ? "You do" : Users::GetNameFromID($userId)." does")."n't have any friends"); $query = $pdo->prepare(" SELECT friends.*, users.username, users.id AS userId, users.status, users.lastonline FROM friends @@ -40,7 +41,7 @@ while($row = $query->fetch(PDO::FETCH_OBJ)) "userid" => $row->userId, "avatar" => Thumbnails::GetAvatar($row->userId, 250, 250), "friendid" => $row->id, - "status" => polygon::filterText($row->status) + "status" => Polygon::FilterText($row->status) ]; } diff --git a/api/games/getServers.php b/api/games/getServers.php index e49f698..79d19fa 100644 --- a/api/games/getServers.php +++ b/api/games/getServers.php @@ -1,6 +1,8 @@ - "POST"]); + "POST", "logged_in" => true]); $client = $_POST["client"] ?? "false"; $creator = $_POST["creator"] ?? false; @@ -40,13 +42,14 @@ while($server = $servers->fetch(PDO::FETCH_OBJ)) { $gears = []; foreach(json_decode($server->allowed_gears, true) as $gear_attr => $gear_val) - if($gear_val) $gears[] = ["name" => catalog::$gear_attr_display[$gear_attr]["text_sel"], "icon" => catalog::$gear_attr_display[$gear_attr]["icon"]]; + if($gear_val) $gears[] = ["name" => Catalog::$GearAttributesDisplay[$gear_attr]["text_sel"], "icon" => Catalog::$GearAttributesDisplay[$gear_attr]["icon"]]; $items[] = [ - "server_name" => polygon::filterText($server->name), + "server_name" => Polygon::FilterText($server->name), + "server_description" => strlen($server->description) ? Polygon::FilterText($server->description) : "No description available.", "server_id" => $server->id, "server_thumbnail" => Thumbnails::GetAvatar($server->hoster, 420, 420), - "hoster_name" => users::getUserNameFromUid($server->hoster), + "hoster_name" => Users::GetNameFromID($server->hoster), "hoster_id" => $server->hoster, "date" => date('n/d/Y g:i:s A', $server->created), "version" => $server->version, diff --git a/api/games/serverlauncher.php b/api/games/serverlauncher.php index ce9b378..7fb85fd 100644 --- a/api/games/serverlauncher.php +++ b/api/games/serverlauncher.php @@ -49,14 +49,17 @@ $query->bindParam(":sid", $serverID, PDO::PARAM_INT); $query->bindParam(":teleport", $isTeleport, PDO::PARAM_INT); $query->execute(); +$Protocol = "https"; +if($serverInfo->version == 2009) $Protocol = "http"; + api::respond_custom([ "status" => 200, "success" => true, "message" => "OK", "version" => $serverInfo->version, - "joinScriptUrl" => "http://chef.pizzaboxer.xyz/game/join?ticket=".$ticket, + "joinScriptUrl" => "{$Protocol}://{$_SERVER['HTTP_HOST']}/game/join?ticket={$ticket}", // these last few params are for teleportservice and lack any function - just ignore - "authenticationUrl" => "http://chef.pizzaboxer.xyz/Login/Negotiate.ashx", - "authenticationTicket" => "unusedplzignore", + "authenticationUrl" => "{$Protocol}://{$_SERVER['HTTP_HOST']}/Login/Negotiate.ashx", + "authenticationTicket" => "0", "status" => 2 ]); \ No newline at end of file diff --git a/api/groups/admin/get-members.php b/api/groups/admin/get-members.php new file mode 100644 index 0000000..0a086b5 --- /dev/null +++ b/api/groups/admin/get-members.php @@ -0,0 +1,52 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(isset($_POST["Page"]) && !is_numeric($_POST["Page"])) api::respond(400, false, "Page is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$Page = $_POST["Page"] ?? 1; +$Members = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); +$Rank = Groups::GetUserRank(SESSION["userId"], $GroupID); + +if($Rank->Level == 0) api::respond(200, false, "You are not a member of this group"); +if(!$Rank->Permissions->CanManageGroupAdmin) api::respond(200, false, "You are not allowed to perform this action"); + +$MemberCount = db::run( + "SELECT COUNT(*) FROM groups_members + WHERE GroupID = :GroupID AND Rank < :RankLevel AND NOT Pending", + [":GroupID" => $GroupID, ":RankLevel" => $Rank->Level] +)->fetchColumn(); + +$Pages = ceil($MemberCount/12); +$Offset = ($Page - 1)*12; + +if(!$Pages) api::respond(200, true, "This group does not have any members."); + +$MembersQuery = db::run( + "SELECT users.username, users.id, Rank FROM groups_members + INNER JOIN users ON users.id = groups_members.UserID + WHERE GroupID = :GroupID AND Rank < :RankLevel AND NOT Pending + ORDER BY Joined DESC LIMIT 12 OFFSET $Offset", + [":GroupID" => $GroupID, ":RankLevel" => $Rank->Level] +); + +while($Member = $MembersQuery->fetch(PDO::FETCH_OBJ)) +{ + $Members[] = + [ + "UserName" => $Member->username, + "UserID" => $Member->id, + "RoleLevel" => $Member->Rank, + "Avatar" => Thumbnails::GetAvatar($Member->id, 250, 250) + ]; +} + +die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pages, "count" => $MemberCount, "items" => $Members])); \ No newline at end of file diff --git a/api/groups/admin/get-roles.php b/api/groups/admin/get-roles.php new file mode 100644 index 0000000..b611d77 --- /dev/null +++ b/api/groups/admin/get-roles.php @@ -0,0 +1,44 @@ + "POST"]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$Roles = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); +$Rank = Groups::GetUserRank(SESSION["userId"], $GroupID); + +if($Rank->Level == 0) api::respond(200, false, "You are not a member of this group"); +if(!$Rank->Permissions->CanManageGroupAdmin) api::respond(200, false, "You are not allowed to perform this action"); + +if($Rank->Level == 255) +{ + $RolesQuery = db::run( + "SELECT * FROM groups_ranks WHERE GroupID = :GroupID ORDER BY Rank ASC", + [":GroupID" => $GroupID] + ); +} +else +{ + $RolesQuery = db::run( + "SELECT * FROM groups_ranks WHERE GroupID = :GroupID AND Rank < :MyRank ORDER BY Rank ASC", + [":GroupID" => $GroupID, ":MyRank" => $Rank->Level] + ); +} + +while($Role = $RolesQuery->fetch(PDO::FETCH_OBJ)) +{ + $Roles[] = + [ + "Name" => htmlspecialchars($Role->Name), + "Description" => htmlspecialchars($Role->Description), + "Rank" => $Role->Rank, + "Permissions" => json_decode($Role->Permissions) + ]; +} + +die(json_encode(["status" => 200, "success" => true, "message" => "OK", "items" => $Roles])); \ No newline at end of file diff --git a/api/groups/admin/request-relationship.php b/api/groups/admin/request-relationship.php new file mode 100644 index 0000000..83b05e6 --- /dev/null +++ b/api/groups/admin/request-relationship.php @@ -0,0 +1,110 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["Recipient"])) api::respond(400, false, "Recipient is not set"); + +if(!isset($_POST["Type"])) api::respond(400, false, "Type is not set"); +if(!in_array($_POST["Type"], ["ally", "enemy"])) api::respond(400, false, "Type is not valid"); + +$GroupID = $_POST["GroupID"] ?? false; +$RecipientName = $_POST["Recipient"] ?? false; +$Type = $_POST["Type"] ?? false; +$Groups = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +$Recipient = db::run("SELECT * FROM groups WHERE name = :GroupName", [":GroupName" => $RecipientName]); +$RecipientInfo = $Recipient->fetch(PDO::FETCH_OBJ); + +if(!$Recipient->rowCount()) api::respond(200, false, "No group with that name exists"); + +$MyRank = Groups::GetUserRank(SESSION["userId"], $GroupID); +if(!$MyRank->Permissions->CanManageRelationships) api::respond(200, false, "You are not allowed to manage this group's relationships"); + +if($RecipientInfo->id == $GroupID) +{ + if($Type == "ally") api::respond(200, false, "You cannot send an ally request to your own group"); + else if($Type == "enemy") api::respond(200, false, "You cannot declare your own group as an enemy"); +} + +$Relationship = db::run( + "SELECT * FROM groups_relationships WHERE :GroupID IN (Declarer, Recipient) AND :Recipient IN (Declarer, Recipient) AND Status != 2", + [":GroupID" => $GroupID, ":Recipient" => $RecipientInfo->id] +); +$RelationshipInfo = $Relationship->fetch(PDO::FETCH_OBJ); + +if($Relationship->rowCount()) +{ + if($RelationshipInfo->Type == "Allies") + { + if($RelationshipInfo->Status == 0) + { + if($RelationshipInfo->Declarer == $GroupID) + { + api::respond(200, false, "You already have an outgoing ally request to this group"); + } + else + { + api::respond(200, false, "You already have an incoming ally request from this group"); + } + } + else if($RelationshipInfo->Status == 1) + { + api::respond(200, false, "You are already allies with this group!"); + } + } + else if($RelationshipInfo->Type == "Enemies") + { + api::respond(200, false, "You are already enemies with this group!"); + } +} + +if($Type == "ally") +{ + $LastRequest = db::run("SELECT Declared FROM groups_relationships WHERE Declarer = :GroupID AND Declared+3600 > UNIX_TIMESTAMP()", [":GroupID" => $GroupID]); + if($LastRequest->rowCount()) + api::respond(429, false, "Please wait ".GetReadableTime($LastRequest->fetchColumn(), ["RelativeTime" => "1 hour"])." before sending a new ally request"); + + db::run( + "INSERT INTO groups_relationships (Type, Declarer, Recipient, Status, Declared) + VALUES (\"Allies\", :GroupID, :Recipient, 0, UNIX_TIMESTAMP())", + [":GroupID" => $GroupID, ":Recipient" => $RecipientInfo->id] + ); + + Groups::LogAction( + $GroupID, "Send Ally Request", + sprintf( + "%s sent an ally request to %s", + SESSION["userId"], SESSION["userName"], $RecipientInfo->id, htmlspecialchars($RecipientInfo->name) + ) + ); + api::respond(200, true, "Ally request has been sent to ".Polygon::FilterText($RecipientInfo->name)); +} +else if($Type == "enemy") +{ + $LastRequest = db::run("SELECT Declared FROM groups_relationships WHERE Declarer = :GroupID AND Declared+3600 > UNIX_TIMESTAMP()", [":GroupID" => $GroupID]); + if($LastRequest->rowCount()) + api::respond(429, false, "Please wait ".GetReadableTime($LastRequest->fetchColumn(), ["RelativeTime" => "1 hour"])." before sending a new ally request"); + + db::run( + "INSERT INTO groups_relationships (Type, Declarer, Recipient, Status, Declared, Established) + VALUES (\"Enemies\", :GroupID, :Recipient, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", + [":GroupID" => $GroupID, ":Recipient" => $RecipientInfo->id] + ); + + Groups::LogAction( + $GroupID, "Create Enemy", + sprintf( + "%s declared %s as an enemy", + SESSION["userId"], SESSION["userName"], $RecipientInfo->id, htmlspecialchars($RecipientInfo->name) + ) + ); + api::respond(200, true, Polygon::FilterText($RecipientInfo->name)." is now your enemy!"); +} + +api::respond(200, false, "An unexpected error occurred"); \ No newline at end of file diff --git a/api/groups/admin/update-member.php b/api/groups/admin/update-member.php new file mode 100644 index 0000000..70c30d8 --- /dev/null +++ b/api/groups/admin/update-member.php @@ -0,0 +1,57 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["UserID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["UserID"])) api::respond(400, false, "GroupID is not a number"); + +if(isset($_POST["RoleLevel"]) && !is_numeric($_POST["RoleLevel"])) api::respond(400, false, "RoleLevel is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$UserID = $_POST["UserID"] ?? false; + +$RoleLevel = $_POST["RoleLevel"] ?? false; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +$MyRole = Groups::GetUserRank(SESSION["userId"], $GroupID); +$UserRole = Groups::GetUserRank($UserID, $GroupID); + +if($MyRole->Level == 0) api::respond(200, false, "You are not a member of this group"); +if(!$MyRole->Permissions->CanManageGroupAdmin) api::respond(200, false, "You are not allowed to perform this action"); +if($UserRole->Level == 0) api::respond(200, false, "That user is not a member of this group"); + +if($RoleLevel !== false) +{ + if(!Groups::GetRankInfo($GroupID, $RoleLevel)) api::respond(200, false, "That role does not exist"); + if($RoleLevel == 0 || $RoleLevel == 255) api::respond(200, false, "That role cannot be manually assigned to a member"); + + if($UserRole->Level == $RoleLevel) api::respond(200, false, "The role you tried to assign is the user's current role"); + if($MyRole->Level <= $RoleLevel) api::respond(200, false, "You can only assign roles lower than yours"); + if($MyRole->Level <= $UserRole->Level) api::respond(200, false, "You can only modify the role of a user who is a role lower than yours"); + + db::run( + "UPDATE groups_members SET Rank = :RoleLevel WHERE GroupID = :GroupID AND UserID = :UserID", + [":GroupID" => $GroupID, ":UserID" => $UserID, ":RoleLevel" => $RoleLevel] + ); + + $UserName = Users::GetNameFromID($UserID); + $RoleName = Groups::GetRankInfo($GroupID, $RoleLevel)->Name; + $Action = $RoleLevel > $UserRole->Level ? "promoted" : "demoted"; + + Groups::LogAction( + $GroupID, "Change Rank", + sprintf( + "%s %s %s from %s to %s", + SESSION["userId"], SESSION["userName"], $Action, $UserID, $UserName, htmlspecialchars($UserRole->Name), htmlspecialchars($RoleName) + ) + ); + + api::respond(200, true, "$UserName has been $Action to " . htmlspecialchars($RoleName)); +} + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/groups/admin/update-relationship.php b/api/groups/admin/update-relationship.php new file mode 100644 index 0000000..9f043c5 --- /dev/null +++ b/api/groups/admin/update-relationship.php @@ -0,0 +1,104 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["Recipient"])) api::respond(400, false, "Recipient is not set"); +if(!is_numeric($_POST["Recipient"])) api::respond(400, false, "Recipient is not a number"); + +if(!isset($_POST["Action"])) api::respond(400, false, "Action is not set"); +if(!in_array($_POST["Action"], ["accept", "decline"])) api::respond(400, false, "Action is not valid"); + +$GroupID = $_POST["GroupID"] ?? false; +$Recipient = $_POST["Recipient"] ?? false; +$Action = $_POST["Action"] ?? false; +$Groups = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); +if(!Groups::GetGroupInfo($Recipient)) api::respond(200, false, "Recipient group does not exist"); + +$MyRank = Groups::GetUserRank(SESSION["userId"], $GroupID); +if(!$MyRank->Permissions->CanManageRelationships) api::respond(200, false, "You are not allowed to manage this group's relationships"); + +$Relationship = db::run( + "SELECT groups_relationships.*, groups.name FROM groups_relationships + INNER JOIN groups ON groups.id = (CASE WHEN Declarer = :GroupID THEN Recipient ELSE Declarer END) + WHERE :GroupID IN (Declarer, Recipient) AND :Recipient IN (Declarer, Recipient) AND Status != 2", + [":GroupID" => $GroupID, ":Recipient" => $Recipient] +); +$RelationshipInfo = $Relationship->fetch(PDO::FETCH_OBJ); + +if(!$Relationship->rowCount()) api::respond(200, false, "You are not in a relationship with this group"); + +if($Action == "accept") +{ + if($RelationshipInfo->Type == "Enemies") api::respond(200, false, "You cannot accept an enemy relationship"); + if($RelationshipInfo->Status != 0) api::respond(200, false, "You are already in a relationship with this group"); + + db::run( + "UPDATE groups_relationships SET Status = 1, Established = UNIX_TIMESTAMP() WHERE ID = :RelationshipID", + [":RelationshipID" => $RelationshipInfo->ID] + ); + + Groups::LogAction( + $GroupID, "Accept Ally Request", + sprintf( + "%s accepted an ally request from %s", + SESSION["userId"], SESSION["userName"], $Recipient, htmlspecialchars($RelationshipInfo->name) + ) + ); + + api::respond(200, true, "You have accepted {$RelationshipInfo->name}'s ally request"); +} +else if($Action == "decline") +{ + db::run( + "UPDATE groups_relationships SET Status = 2, Broken = UNIX_TIMESTAMP() WHERE ID = :RelationshipID", + [":RelationshipID" => $RelationshipInfo->ID] + ); + + if($RelationshipInfo->Type == "Allies") + { + if($RelationshipInfo->Status == 0) + { + Groups::LogAction( + $GroupID, "Decline Ally Request", + sprintf( + "%s declined an ally request from %s", + SESSION["userId"], SESSION["userName"], $Recipient, htmlspecialchars($RelationshipInfo->name) + ) + ); + + api::respond(200, true, "You have declined ".Polygon::FilterText($RelationshipInfo->name)."'s ally request"); + } + else if($RelationshipInfo->Status == 1) + { + Groups::LogAction( + $GroupID, "Delete Ally", + sprintf( + "%s removed %s as an ally", + SESSION["userId"], SESSION["userName"], $Recipient, htmlspecialchars($RelationshipInfo->name) + ) + ); + + api::respond(200, true, "You are no longer allies with ".Polygon::FilterText($RelationshipInfo->name)); + } + } + else if($RelationshipInfo->Type == "Enemies") + { + Groups::LogAction( + $GroupID, "Delete Enemy", + sprintf( + "%s removed %s as an enemy", + SESSION["userId"], SESSION["userName"], $Recipient, htmlspecialchars($RelationshipInfo->name) + ) + ); + + api::respond(200, true, "You are no longer enemies with ".Polygon::FilterText($RelationshipInfo->name)); + } +} + +api::respond(200, false, "An unexpected error occurred"); \ No newline at end of file diff --git a/api/groups/admin/update-roles.php b/api/groups/admin/update-roles.php new file mode 100644 index 0000000..98a3998 --- /dev/null +++ b/api/groups/admin/update-roles.php @@ -0,0 +1,167 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["Roles"])) api::respond(400, false, "Roles is not set"); + +$GroupID = $_POST["GroupID"]; +$Roles = json_decode($_POST["Roles"]); + +if(!$Roles) api::respond(400, false, "Roles is not valid JSON"); +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +$MyRole = Groups::GetUserRank(SESSION["userId"], $GroupID); + +if($MyRole->Level == 0) api::respond(200, false, "You are not a member of this group"); +if($MyRole->Level != 255) api::respond(200, false, "You are not allowed to perform this action"); + +function FindRolesWithRank($Rank) +{ + global $Roles; + $Count = 0; + + foreach ($Roles as $Role) + { + if (!isset($Role->Rank)) continue; + if ($Role->Rank == $Rank) $Count += 1; + } + + return $Count; +} + +function FindRoleWithRank($Rank) +{ + global $Roles; + + foreach ($Roles as $Role) + { + if (!isset($Role->Rank)) continue; + if ($Role->Rank == $Rank) return $Role; + } + + return false; +} + +$Permissions = +[ + "CanViewGroupWall", + "CanViewGroupStatus", + "CanPostOnGroupWall", + "CanPostGroupStatus", + "CanDeleteGroupWallPosts", + "CanAcceptJoinRequests", + "CanKickLowerRankedMembers", + "CanRoleLowerRankedMembers", + "CanManageRelationships", + "CanCreateAssets", + "CanConfigureAssets", + "CanSpendFunds", + "CanManageGames", + "CanManageGroupAdmin", + "CanViewAuditLog" +]; + +if(FindRolesWithRank(0) == 0) api::respond(200, false, "You can not remove the Guest role"); +if(FindRolesWithRank(255) == 0) api::respond(200, false, "You can not remove the Owner role"); +if(count($Roles) < 3) api::respond(200, false, "There must be at least three roles"); +if(count($Roles) > 10) api::respond(200, false, "There must be no more than ten roles"); + +foreach($Roles as $Role) +{ + if(!isset($Role->Name) || !isset($Role->Description) || !isset($Role->Rank) || !isset($Role->Permissions)) + api::respond(200, false, "Roles are missing parameters"); + + if($Role->Rank < 0 || $Role->Rank > 255) api::respond(200, false, "Each role must have a rank number between 0 and 255"); + if(FindRolesWithRank($Role->Rank) > 1) api::respond(200, false, "Each role must have a unique rank number"); + + $CurrentRole = Groups::GetRankInfo($GroupID, $Role->Rank); + + if($CurrentRole === false) $Role->Action = "Create"; + else $Role->Action = "Update"; + + if($Role->Rank == 0) + { + if($Role->Name != $CurrentRole->Name || $Role->Description != $CurrentRole->Description) + api::respond(200, false, "You can not modify the Guest role"); + } + + if($Role->Rank == 255 && $Role->Permissions != $CurrentRole->Permissions) + api::respond(200, false, "You can not modify the permissions of the Owner role"); + + if(strlen($Role->Name) < 3) api::respond(200, false, "Role names must be at least 3 characters long"); + if(strlen($Role->Name) > 15) api::respond(200, false, "Role names must be no longer than 15 characters"); + + if(strlen($Role->Description) < 3) api::respond(200, false, "Role description must at least 3 characters long"); + if(strlen($Role->Description) > 64) api::respond(200, false, "Role description must be no longer than 64 characters"); + + foreach ($Permissions as $Permission) + { + if(!isset($Role->Permissions->$Permission)) api::respond(200, false, "Role is missing a permission"); + if(!is_bool($Role->Permissions->$Permission)) api::respond(200, false, "Role permission property must have a boolean value"); + } + + if(count((array)$Role->Permissions) != count($Permissions)) api::respond(200, false, "Role permissions contains an incorrect number of permissions"); +} + +foreach($Roles as $Role) +{ + if($Role->Action == "Create") + { + // if(SESSION["userId"] == 1) echo "Creating Role {$Role->Rank}\r\n"; + + db::run( + "INSERT INTO groups_ranks (GroupID, Name, Description, Rank, Permissions, Created) + VALUES (:GroupID, :Name, :Description, :Rank, :Permissions, UNIX_TIMESTAMP())", + [":GroupID" => $GroupID, ":Name" => $Role->Name, ":Description" => $Role->Description, ":Rank" => $Role->Rank, ":Permissions" => json_encode($Role->Permissions)] + ); + } +} + +$GroupRoles = Groups::GetGroupRanks($GroupID, true); +while($ExistingRole = $GroupRoles->fetch(PDO::FETCH_OBJ)) +{ + $Role = FindRoleWithRank($ExistingRole->Rank); + + if($Role == false) + { + // if(SESSION["userId"] == 1) echo "Deleting Role {$ExistingRole->Rank}\r\n"; + + // for this one we gotta move the members with a role thats being deleted to the lowest rank + // slight issue with this is for a brief period, members assigned the role thats being deleted + // will have no role - if the timing is just right this could mess up the view of the group page + + // delete the rank by the oldest id - so that we dont accidentally delete the new one + db::run( + "DELETE FROM groups_ranks WHERE GroupID = :GroupID AND Rank = :Rank ORDER BY id ASC LIMIT 1", + [":GroupID" => $GroupID, ":Rank" => $ExistingRole->Rank] + ); + + $NewRank = db::run( + "SELECT Rank FROM polygon.groups_ranks WHERE GroupID = :GroupID AND Rank != 0 ORDER BY Rank ASC LIMIT 1", + [":GroupID" => $GroupID] + )->fetchColumn(); + + // if(SESSION["userId"] == 1) echo "Updating existing members to {$NewRank}\r\n"; + + db::run( + "UPDATE groups_members SET Rank = :NewRank WHERE GroupID = :GroupID AND Rank = :Rank", + [":GroupID" => $GroupID, ":Rank" => $ExistingRole->Rank, ":NewRank" => $NewRank] + ); + } + else if(isset($Role->Action) && $Role->Action == "Update") + { + // if(SESSION["userId"] == 1) echo "Updating Role {$Role->Rank}\r\n"; + + db::run( + "UPDATE groups_ranks SET Name = :Name, Description = :Description, Permissions = :Permissions + WHERE GroupID = :GroupID AND Rank = :Rank", + [":GroupID" => $GroupID, ":Name" => $Role->Name, ":Description" => $Role->Description, ":Rank" => $Role->Rank, ":Permissions" => json_encode($Role->Permissions)] + ); + } +} + +die(json_encode(["status" => 200, "success" => true, "message" => "Group roles have successfully been updated"])); \ No newline at end of file diff --git a/api/groups/delete-wall-post.php b/api/groups/delete-wall-post.php new file mode 100644 index 0000000..506eb12 --- /dev/null +++ b/api/groups/delete-wall-post.php @@ -0,0 +1,40 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["PostID"])) api::respond(400, false, "PostID is not set"); +if(!is_numeric($_POST["PostID"])) api::respond(400, false, "PostID is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$PostID = $_POST["PostID"] ?? false; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +$Rank = Groups::GetUserRank(SESSION["userId"], $GroupID); +if(!$Rank->Permissions->CanDeleteGroupWallPosts) api::respond(200, false, "You are not allowed to delete wall posts on this group"); + +$PostInfo = db::run( + "SELECT * FROM groups_wall WHERE id = :PostID AND :GroupID = :GroupID", + [":PostID" => $PostID, ":GroupID" => $GroupID] +)->fetch(PDO::FETCH_OBJ); + +if(!$PostInfo) api::respond(200, false, "Wall post does not exist"); + +Groups::LogAction( + $GroupID, "Delete Post", + sprintf( + "%s deleted post \"%s\" by %s", + SESSION["userId"], SESSION["userName"], htmlspecialchars($PostInfo->Content), $PostInfo->PosterID, Users::GetNameFromID($PostInfo->PosterID) + ) +); + +db::run( + "DELETE FROM groups_wall WHERE id = :PostID AND :GroupID = :GroupID", + [":PostID" => $PostID, ":GroupID" => $GroupID] +); + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/groups/get-audit.php b/api/groups/get-audit.php new file mode 100644 index 0000000..060e586 --- /dev/null +++ b/api/groups/get-audit.php @@ -0,0 +1,75 @@ + "POST"]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(isset($_POST["Page"]) && !is_numeric($_POST["Page"])) api::respond(400, false, "Page is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$Filter = $_POST["Filter"] ?? "All Actions"; +$Page = $_POST["Page"] ?? 1; +$Logs = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); +$MyRank = Groups::GetUserRank(SESSION["userId"] ?? 0, $GroupID); +if(!$MyRank->Permissions->CanViewAuditLog) api::respond(200, false, "You cannot audit this group"); + +if($Filter == "All Actions") +{ + $LogCount = db::run( + "SELECT COUNT(*) FROM groups_audit WHERE GroupID = :GroupID", + [":GroupID" => $GroupID] + )->fetchColumn(); +} +else +{ + $LogCount = db::run( + "SELECT COUNT(*) FROM groups_audit WHERE GroupID = :GroupID AND Category = :Action", + [":GroupID" => $GroupID, ":Action" => $Filter] + )->fetchColumn(); +} + +$Pages = ceil($LogCount/20); +$Offset = ($Page - 1)*20; + +if(!$Pages) api::respond(200, true, "This group does not have any logs for this action."); + +if($Filter == "All Actions") +{ + $LogsQuery = db::run( + "SELECT groups_audit.*, users.username FROM groups_audit + INNER JOIN users ON users.id = UserId + WHERE GroupID = :GroupID + ORDER BY Time DESC LIMIT 20 OFFSET $Offset", + [":GroupID" => $GroupID] + ); +} +else +{ + $LogsQuery = db::run( + "SELECT groups_audit.*, users.username FROM groups_audit + INNER JOIN users ON users.id = UserId + WHERE GroupID = :GroupID AND Category = :Action + ORDER BY Time DESC LIMIT 20 OFFSET $Offset", + [":GroupID" => $GroupID, ":Action" => $Filter] + ); +} + +while($Log = $LogsQuery->fetch(PDO::FETCH_OBJ)) +{ + $Logs[] = + [ + "Date" => date('j/n/y G:i', $Log->Time), + "UserName" => $Log->username, + "UserID" => $Log->UserID, + "UserAvatar" => Thumbnails::GetAvatar($Log->UserID, 250, 250), + "Rank" => Polygon::FilterText($Log->Rank), + "Description" => Polygon::FilterText($Log->Description, false) + ]; +} + +die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pages, "items" => $Logs])); \ No newline at end of file diff --git a/api/groups/get-members.php b/api/groups/get-members.php new file mode 100644 index 0000000..b5107ea --- /dev/null +++ b/api/groups/get-members.php @@ -0,0 +1,52 @@ + "POST"]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["RankLevel"])) api::respond(400, false, "RankLevel is not set"); +if(!is_numeric($_POST["RankLevel"])) api::respond(400, false, "RankLevel is not a number"); + +if(isset($_POST["Page"]) && !is_numeric($_POST["Page"])) api::respond(400, false, "Page is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$RankID = $_POST["RankLevel"] ?? false; +$Page = $_POST["Page"] ?? 1; +$Members = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); +if(!Groups::GetRankInfo($GroupID, $RankID)) api::respond(200, false, "Group rank does not exist"); + +$MemberCount = db::run( + "SELECT COUNT(*) FROM groups_members WHERE GroupID = :GroupID AND Rank = :RankID AND NOT Pending", + [":GroupID" => $GroupID, ":RankID" => $RankID] +)->fetchColumn(); + +$Pages = ceil($MemberCount/12); +$Offset = ($Page - 1)*12; + +if(!$Pages) api::respond(200, true, "This group does not have any members of this rank."); + +$MembersQuery = db::run( + "SELECT users.username, users.id, Rank FROM groups_members + INNER JOIN users ON users.id = groups_members.UserID + WHERE GroupID = :GroupID AND Rank = :RankID AND NOT Pending + ORDER BY Joined DESC LIMIT 12 OFFSET $Offset", + [":GroupID" => $GroupID, ":RankID" => $RankID] +); + +while($Member = $MembersQuery->fetch(PDO::FETCH_OBJ)) +{ + $Members[] = + [ + "UserName" => $Member->username, + "UserID" => $Member->id, + "RoleLevel" => $Member->Rank, + "Avatar" => Thumbnails::GetAvatar($Member->id, 250, 250) + ]; +} + +die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pages, "count" => $MemberCount, "items" => $Members])); \ No newline at end of file diff --git a/api/groups/get-related.php b/api/groups/get-related.php new file mode 100644 index 0000000..6cffe24 --- /dev/null +++ b/api/groups/get-related.php @@ -0,0 +1,82 @@ + "POST"]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["Type"])) api::respond(400, false, "Type is not set"); +if(!in_array($_POST["Type"], ["Pending Allies", "Allies", "Enemies"])) api::respond(400, false, "Type is not valid"); + +if(isset($_POST["Page"]) && !is_numeric($_POST["Page"])) api::respond(400, false, "Page is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$Type = $_POST["Type"] ?? false; +$Page = $_POST["Page"] ?? 1; +$Groups = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +if($Type == "Pending Allies") +{ + if(!SESSION) api::respond(200, false, "You are not allowed to get this group's pending allies"); + $MyRank = Groups::GetUserRank(SESSION["userId"], $GroupID); + if(!$MyRank->Permissions->CanManageRelationships) api::respond(200, false, "You are not allowed to get this group's pending allies"); + + $GroupsCount = db::run( + "SELECT COUNT(*) FROM groups_relationships WHERE Recipient = :GroupID AND Type = \"Allies\" AND Status = 0", + [":GroupID" => $GroupID] + )->fetchColumn(); +} +else +{ + $GroupsCount = db::run( + "SELECT COUNT(*) FROM groups_relationships WHERE :GroupID IN (Declarer, Recipient) AND Type = :Type AND Status = 1", + [":GroupID" => $GroupID, ":Type" => $Type] + )->fetchColumn(); +} + +$Pages = ceil($GroupsCount/12); +$Offset = ($Page - 1)*12; + +if(!$Pages) api::respond(200, true, "This group does not have any $Type"); + +if($Type == "Pending Allies") +{ + $GroupsQuery = db::run( + "SELECT groups.name, groups.id, groups.emblem, + (SELECT COUNT(*) FROM groups_members WHERE GroupID = groups.id AND NOT Pending) AS MemberCount + FROM groups_relationships + INNER JOIN groups ON groups.id = (CASE WHEN Declarer = :GroupID THEN Recipient ELSE Declarer END) + WHERE Recipient = :GroupID AND Type = \"Allies\" AND Status = 0 + ORDER BY Declared DESC LIMIT 12 OFFSET $Offset", + [":GroupID" => $GroupID] + ); +} +else +{ + $GroupsQuery = db::run( + "SELECT groups.name, groups.id, groups.emblem, + (SELECT COUNT(*) FROM groups_members WHERE GroupID = groups.id AND NOT Pending) AS MemberCount + FROM groups_relationships + INNER JOIN groups ON groups.id = (CASE WHEN Declarer = :GroupID THEN Recipient ELSE Declarer END) + WHERE :GroupID IN (Declarer, Recipient) AND Type = :Type AND Status = 1 + ORDER BY Established DESC LIMIT 12 OFFSET $Offset", + [":GroupID" => $GroupID, ":Type" => $Type] + ); +} + +while($Group = $GroupsQuery->fetch(PDO::FETCH_OBJ)) +{ + $Groups[] = + [ + "Name" => Polygon::FilterText($Group->name), + "ID" => $Group->id, + "MemberCount" => $Group->MemberCount, + "Emblem" => Thumbnails::GetAssetFromID($Group->emblem, 420, 420) + ]; +} + +die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pages, "items" => $Groups])); \ No newline at end of file diff --git a/api/groups/get-wall.php b/api/groups/get-wall.php new file mode 100644 index 0000000..591d673 --- /dev/null +++ b/api/groups/get-wall.php @@ -0,0 +1,53 @@ + "POST"]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(isset($_POST["Page"]) && !is_numeric($_POST["Page"])) api::respond(400, false, "Page is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$Page = $_POST["Page"] ?? 1; +$Wall = []; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +if(SESSION) $Rank = Groups::GetUserRank(SESSION["userId"], $GroupID); +else $Rank = Groups::GetRankInfo($GroupID, 0); + +if(!$Rank->Permissions->CanViewGroupWall) api::respond(200, false, "You are not allowed to view this group wall"); + +$PostCount = db::run( + "SELECT COUNT(*) FROM groups_wall WHERE GroupID = :GroupID AND NOT Deleted", + [":GroupID" => $GroupID] +)->fetchColumn(); + +$Pages = ceil($PostCount/15); +$Offset = ($Page - 1)*15; + +if(!$Pages) api::respond(200, true, "This group does not have any wall posts."); + +$PostQuery = db::run( + "SELECT groups_wall.id, users.username AS PosterName, PosterID, Content, TimePosted FROM groups_wall + INNER JOIN users ON users.id = PosterID WHERE GroupID = :GroupID AND NOT Deleted + ORDER BY TimePosted DESC LIMIT 15 OFFSET $Offset", + [":GroupID" => $GroupID] +); + +while($Post = $PostQuery->fetch(PDO::FETCH_OBJ)) +{ + $Wall[] = + [ + "id" => $Post->id, + "username" => $Post->PosterName, + "userid" => $Post->PosterID, + "content" => nl2br(Polygon::FilterText($Post->Content)), + "time" => date('j/n/Y g:i:s A', $Post->TimePosted), + "avatar" => Thumbnails::GetAvatar($Post->PosterID, 420, 420) + ]; +} + +die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pages, "count" => $PostCount, "items" => $Wall])); \ No newline at end of file diff --git a/api/groups/join-group.php b/api/groups/join-group.php new file mode 100644 index 0000000..d2d5708 --- /dev/null +++ b/api/groups/join-group.php @@ -0,0 +1,30 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); +if(Groups::CheckIfUserInGroup(SESSION["userId"], $GroupID)) api::respond(200, false, "You are already in this group"); + +if(Groups::GetUserGroups(SESSION["userId"])->rowCount() >= 20) api::respond(200, false, "You have reached the maximum number of groups"); + +$RateLimit = db::run("SELECT Joined FROM groups_members WHERE UserID = :UserID AND Joined+300 > UNIX_TIMESTAMP()", [":UserID" => SESSION["userId"]]); +if($RateLimit->rowCount()) + api::respond(200, false, "Please wait ".GetReadableTime($RateLimit->fetchColumn(), ["RelativeTime" => "5 minutes"])." before joining a new group"); + +$RankLevel = db::run( + "SELECT Rank FROM groups_ranks WHERE GroupID = :GroupID AND Rank != 0 ORDER BY Rank ASC LIMIT 1", + [":GroupID" => $GroupID] +)->fetchColumn(); + +db::run( + "INSERT INTO groups_members (GroupID, UserID, Rank, Joined) VALUES (:GroupID, :UserID, :RankLevel, UNIX_TIMESTAMP())", + [":GroupID" => $GroupID, ":UserID" => SESSION["userId"], ":RankLevel" => $RankLevel] +); + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/groups/leave-group.php b/api/groups/leave-group.php new file mode 100644 index 0000000..b758a78 --- /dev/null +++ b/api/groups/leave-group.php @@ -0,0 +1,21 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +$GroupID = $_POST["GroupID"] ?? false; +$GroupInfo = Groups::GetGroupInfo($GroupID); + +if(!$GroupInfo) api::respond(200, false, "Group does not exist"); +if($GroupInfo->creator == SESSION["userId"]) api::respond(200, false, "You are the creator of this group"); +if(!Groups::CheckIfUserInGroup(SESSION["userId"], $GroupID)) api::respond(200, false, "You are not in this group"); + +db::run( + "DELETE FROM groups_members WHERE GroupID = :GroupID AND UserID = :UserID", + [":GroupID" => $GroupID, ":UserID" => SESSION["userId"]] +); + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/groups/post-shout.php b/api/groups/post-shout.php new file mode 100644 index 0000000..ef13bbe --- /dev/null +++ b/api/groups/post-shout.php @@ -0,0 +1,56 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["Content"])) api::respond(400, false, "Content is not set"); + +$GroupID = $_POST["GroupID"] ?? false; +$Content = $_POST["Content"] ?? false; +$GroupInfo = Groups::GetGroupInfo($GroupID); + +if(!$GroupInfo) api::respond(200, false, "Group does not exist"); + +$Rank = Groups::GetUserRank(SESSION["userId"], $GroupID); + +if(!$Rank->Permissions->CanPostGroupStatus) api::respond(200, false, "You are not allowed to post on this group wall"); + +if(strlen($Content) < 3) api::respond(200, false, "Group shout must be at least 3 characters long"); +if(strlen($Content) > 255) api::respond(200, false, "Group shout can not be longer than 64 characters"); + +$LastPost = db::run( + "SELECT timestamp FROM feed WHERE groupId = :GroupID AND userId = :UserID AND timestamp+300 > UNIX_TIMESTAMP()", + [":GroupID" => $GroupID, ":UserID" => SESSION["userId"]] +); + +if($LastPost->rowCount()) + api::respond(200, false, "Please wait ".GetReadableTime($LastPost->fetchColumn(), ["RelativeTime" => "5 minutes"])." before posting a group shout"); + +Groups::LogAction( + $GroupID, "Post Shout", + sprintf( + "%s changed the group status to: %s", + SESSION["userId"], SESSION["userName"], htmlspecialchars($Content) + ) +); + +db::run( + "INSERT INTO feed (groupId, userId, text, timestamp) VALUES (:GroupID, :UserID, :Content, UNIX_TIMESTAMP())", + [":GroupID" => $GroupID, ":UserID" => SESSION["userId"], ":Content" => $Content] +); + +Discord::SendToWebhook( + [ + "username" => $GroupInfo->name, + "content" => $Content."\n(Posted by ".SESSION["userName"].")", + "avatar_url" => Thumbnails::GetAssetFromID($GroupInfo->emblem, 420, 420) + ], + Discord::WEBHOOK_KUSH +); + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/groups/post-wall.php b/api/groups/post-wall.php new file mode 100644 index 0000000..a75822b --- /dev/null +++ b/api/groups/post-wall.php @@ -0,0 +1,38 @@ + "POST", "logged_in" => true, "secure" => true]); + +if(!isset($_POST["GroupID"])) api::respond(400, false, "GroupID is not set"); +if(!is_numeric($_POST["GroupID"])) api::respond(400, false, "GroupID is not a number"); + +if(!isset($_POST["Content"])) api::respond(400, false, "Content is not set"); + +$GroupID = $_POST["GroupID"] ?? false; +$Content = $_POST["Content"] ?? false; + +if(!Groups::GetGroupInfo($GroupID)) api::respond(200, false, "Group does not exist"); + +$Rank = Groups::GetUserRank(SESSION["userId"], $GroupID); + +if(!$Rank->Permissions->CanPostOnGroupWall) api::respond(200, false, "You are not allowed to post on this group wall"); + +if(strlen($Content) < 3) api::respond(200, false, "Wall post must be at least 3 characters long"); +if(strlen($Content) > 255) api::respond(200, false, "Wall post can not be longer than 255 characters"); + +$LastPost = db::run( + "SELECT TimePosted FROM groups_wall + WHERE GroupID = :GroupID AND PosterID = :UserID AND TimePosted+60 > UNIX_TIMESTAMP() + ORDER BY TimePosted DESC LIMIT 1", + [":GroupID" => $GroupID, ":UserID" => SESSION["userId"]] +); + +if(SESSION["userId"] != 1 && $LastPost->rowCount()) + api::respond(200, false, "Please wait ".(60-(time()-$LastPost->fetchColumn()))." seconds before posting a new wall post"); + +db::run( + "INSERT INTO groups_wall (GroupID, PosterID, Content, TimePosted) VALUES (:GroupID, :UserID, :Content, UNIX_TIMESTAMP())", + [":GroupID" => $GroupID, ":UserID" => SESSION["userId"], ":Content" => $Content] +); + +api::respond(200, true, "OK"); \ No newline at end of file diff --git a/api/ide/toolbox.php b/api/ide/toolbox.php index 56b6f49..a26dcb5 100644 --- a/api/ide/toolbox.php +++ b/api/ide/toolbox.php @@ -1,5 +1,6 @@ -"Bricks", @@ -92,7 +93,7 @@ $query->execute();
- fetch(PDO::FETCH_OBJ)) { $name = polygon::filterText($row->name); ?> + fetch(PDO::FETCH_OBJ)) { $name = Polygon::FilterText($row->name); ?> <?=$name?> diff --git a/api/private/components/Auth.php b/api/private/components/Auth.php new file mode 100644 index 0000000..a5e725f --- /dev/null +++ b/api/private/components/Auth.php @@ -0,0 +1,40 @@ +plaintext, $this->key); + } + + function VerifyPassword($storedtext) + { + if(strpos($storedtext, "$2y$10") !== false) //standard bcrypt - used since 04/09/2020 + return password_verify($this->plaintext, $storedtext); + elseif(strpos($storedtext, "def50200") !== false) //argon2id w/ encryption - used since 26/02/2021 + return \ParagonIE\PasswordLock\PasswordLock::decryptAndVerify($this->plaintext, $storedtext, $this->key); + } + + function UpdatePassword($userId) + { + $pwhash = $this->createPassword(); + db::run("UPDATE users SET password = :hash, lastpwdchange = UNIX_TIMESTAMP() WHERE id = :id", [":hash" => $pwhash, ":id" => $userId]); + } + + function __construct($plaintext) + { + if(!class_exists('Defuse\Crypto\Key')) Polygon::ImportLibrary("PasswordLock"); + $this->plaintext = $plaintext; + $this->key = \Defuse\Crypto\Key::loadFromAsciiSafeString(SITE_CONFIG["keys"]["passwordEncryption"]); + } +} \ No newline at end of file diff --git a/api/private/components/Catalog.php b/api/private/components/Catalog.php new file mode 100644 index 0000000..30c034e --- /dev/null +++ b/api/private/components/Catalog.php @@ -0,0 +1,137 @@ + "Image", // (internal use only - this is used for asset images) + 2 => "T-Shirt", + 3 => "Audio", + 4 => "Mesh", // (internal use only) + 5 => "Lua", // (internal use only - use this for corescripts and linkedtool scripts) + 6 => "HTML", // (deprecated - dont use) + 7 => "Text", // (deprecated - dont use) + 8 => "Hat", + 9 => "Place", // (unused as of now) + 10 => "Model", + 11 => "Shirt", + 12 => "Pants", + 13 => "Decal", + 16 => "Avatar", // (deprecated - dont use) + 17 => "Head", + 18 => "Face", + 19 => "Gear", + 21 => "Badge" // (unused as of now) + ]; + + static function GetTypeByNum($type) + { + return self::$types[$type] ?? false; + } + + public static array $GearAttributesDisplay = + [ + "melee" => ["text_sel" => "Melee", "text_item" => "Melee Weapon", "icon" => "far fa-sword"], + "powerup" => ["text_sel" => "Power ups", "text_item" => "Power Up", "icon" => "far fa-arrow-alt-up"], + "ranged" => ["text_sel" => "Ranged", "text_item" => "Ranged Weapon", "icon" => "far fa-bow-arrow"], + "navigation" => ["text_sel" => "Navigation", "text_item" => "Melee", "icon" => "far fa-compass"], + "explosive" => ["text_sel" => "Explosives", "text_item" => "Explosive", "icon" => "far fa-bomb"], + "musical" => ["text_sel" => "Musical", "text_item" => "Musical", "icon" => "far fa-music"], + "social" => ["text_sel" => "Social", "text_item" => "Social Item", "icon" => "far fa-laugh"], + "transport" => ["text_sel" => "Transport", "text_item" => "Personal Transport", "icon" => "far fa-motorcycle"], + "building" => ["text_sel" => "Building", "text_item" => "Melee", "icon" => "far fa-hammer"] + ]; + + public static array $GearAttributes = + [ + "melee" => false, + "powerup" => false, + "ranged" => false, + "navigation" => false, + "explosive" => false, + "musical" => false, + "social" => false, + "transport" => false, + "building" => false + ]; + + static function ParseGearAttributes() + { + $gears = self::$GearAttributes; + foreach($gears as $gear => $enabled) $gears[$gear] = isset($_POST["gear_$gear"]) && $_POST["gear_$gear"] == "on"; + self::$GearAttributes = $gears; + } + + static function GetAssetInfo($id) + { + return db::run( + "SELECT assets.*, users.username, + (SELECT COUNT(*) FROM ownedAssets WHERE assetId = assets.id AND userId != assets.creator) AS sales_total, + (SELECT COUNT(*) FROM ownedAssets WHERE assetId = assets.id AND userId != assets.creator AND timestamp > :sda) AS sales_week + FROM assets INNER JOIN users ON creator = users.id WHERE assets.id = :id", + [":sda" => strtotime('7 days ago', time()), ":id" => $id])->fetch(PDO::FETCH_OBJ); + } + + static function CreateAsset($options) + { + global $pdo; + $columns = array_keys($options); + + $querystring = "INSERT INTO assets (".implode(", ", $columns).", created, updated) "; + array_walk($columns, function(&$value, $_){ $value = ":$value"; }); + $querystring .= "VALUES (".implode(", ", $columns).", UNIX_TIMESTAMP(), UNIX_TIMESTAMP())"; + + $query = $pdo->prepare($querystring); + foreach($options as $option => $val) $query->bindParam(":$option", $options[$option], is_numeric($val) ? PDO::PARAM_INT : PDO::PARAM_STR); + $query->execute(); + + $aid = $pdo->lastInsertId(); + $uid = $options["creator"] ?? SESSION["userId"]; + + db::run("INSERT INTO ownedAssets (assetId, userId, timestamp) VALUES (:aid, :uid, UNIX_TIMESTAMP())", [":aid" => $aid, ":uid" => $uid]); + + return $aid; + } + + static function OwnsAsset($uid, $aid) + { + return db::run("SELECT COUNT(*) FROM ownedAssets WHERE assetId = :aid AND userId = :uid", [":aid" => $aid, ":uid" => $uid])->fetchColumn(); + } + + static function GenerateGraphicXML($type, $assetID) + { + $strings = + [ + "T-Shirt" => ["class" => "ShirtGraphic", "contentName" => "Graphic", "stringName" => "Shirt Graphic"], + "Decal" => ["class" => "Decal", "contentName" => "Texture", "stringName" => "Decal"], + "Face" => ["class" => "Decal", "contentName" => "Texture", "stringName" => "face"], + "Shirt" => ["class" => "Shirt", "contentName" => "ShirtTemplate", "stringName" => "Shirt"], + "Pants" => ["class" => "Pants", "contentName" => "PantsTemplate", "stringName" => "Pants"] + ]; + ob_start(); ?> + + null + nil + " referent="RBX0"> + + + 5 + + 20 + 0 + + %ASSETURL% + + + "> + %ASSETURL% + + + + true + + + + "https://discord.com/api/v8/users/$UserID", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => ["Authorization: Bot"] + ]); + + $response = curl_exec($ch); + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($httpcode != 200) return false; + + $response = json_decode($response); + if ($response == NULL) return false; + + return (object) + [ + "username" => $response->username, + "tag" => $response->discriminator, + "id" => $response->id, + "avatar" => "https://cdn.discordapp.com/avatars/{$response->id}/{$response->avatar}.png", + "color" => $response->accent_color, + "banner" => $response->banner, + "banner_color" => $response->banner_color + ]; + } + + static function SendToWebhook($Payload, $Webhook, $EscapeContent = true) + { + // example payload: + // $payload = ["username" => "test", "content" => "test", "avatar_url" => "https://polygon.pizzaboxer.xyz/thumbs/avatar?id=1&x=100&y=100"]; + + if($EscapeContent) + { + $Payload["content"] = str_ireplace(["\\", "`"], ["\\\\", "\\`"], $Payload["content"]); + $Payload["content"] = str_ireplace(["@everyone", "@here"], ["`@everyone`", "`@here`"], $Payload["content"]); + $Payload["content"] = preg_replace("/(<@[0-9]+>)/i", "`$1`", $Payload["content"]); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $Webhook); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(['payload_json' => json_encode($Payload)])); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $response = curl_exec($ch); + curl_close($ch); + + return $response; + } +} \ No newline at end of file diff --git a/api/private/components/ErrorHandler.php b/api/private/components/ErrorHandler.php new file mode 100644 index 0000000..d67f02a --- /dev/null +++ b/api/private/components/ErrorHandler.php @@ -0,0 +1,115 @@ +Type == "Exception") + { + $VerboseMessage .= sprintf("Fatal Error: Uncaught Exception: %s in %s:%d\n", $this->Exception->getMessage(), $this->Exception->getFile(), $this->Exception->getLine()); + $VerboseMessage .= "Stack trace:\n"; + $VerboseMessage .= sprintf("%s\n", $this->Exception->getTraceAsString()); + $VerboseMessage .= sprintf(" thrown in %s on line %d", $this->Exception->getFile(), $this->Exception->getLine()); + } + else + { + $VerboseMessage .= sprintf("%s: %s in %s on line %s", $this->Type, $this->String, $this->File, $this->Line); + } + + return $VerboseMessage; + } + + private function WriteLog() + { + $LogFile = $_SERVER['DOCUMENT_ROOT']."/api/private/ErrorLog.json"; + $LogID = generateUUID(); + + if(!file_exists($LogFile)) file_put_contents($LogFile, "[]"); + + $Log = json_decode(file_get_contents($LogFile), true); + + $Log[$LogID] = + [ + "Timestamp" => time(), + "GETParameters" => $_GET, + "Message" => $this->GetVerboseMessage() + ]; + + file_put_contents($LogFile, json_encode($Log)); + + return $LogID; + } + + public function HandleError($Type, $String, $File, $Line) + { + $this->Type = $this->GetType($Type); + $this->String = $String; + $this->File = $File; + $this->Line = $Line; + + $LogID = $this->WriteLog(); + + if(headers_sent()) + { + die("An unexpected error occurred! More info: $LogID"); + } + else + { + redirect("/error?id=$LogID"); + } + } + + public function HandleException($Exception) + { + $this->Type = "Exception"; + $this->Exception = $Exception; + + $LogID = $this->WriteLog(); + + if(headers_sent()) + { + die("An unexpected error occurred! More info: $LogID"); + } + else + { + redirect("/error?id=$LogID"); + } + } + + public function __construct() + { + set_error_handler([$this, "HandleError"]); + set_exception_handler([$this, "HandleException"]); + } + + public static function GetLog($LogID = false) + { + $LogFile = $_SERVER['DOCUMENT_ROOT']."/api/private/ErrorLog.json"; + if(!file_exists($LogFile)) file_put_contents($LogFile, "[]"); + + $Log = json_decode(file_get_contents($LogFile), true); + + if($LogID !== false) return $Log[$LogID] ?? false; + return $Log; + } +} \ No newline at end of file diff --git a/api/private/components/Forum.php b/api/private/components/Forum.php new file mode 100644 index 0000000..7cb4bf7 --- /dev/null +++ b/api/private/components/Forum.php @@ -0,0 +1,89 @@ + $id])->fetch(PDO::FETCH_OBJ); + } + + static function GetReplyInfo($id) + { + return db::run("SELECT * FROM forum_replies WHERE id = :id", [":id" => $id])->fetch(PDO::FETCH_OBJ); + } + + static function GetThreadReplies($id) + { + return db::run("SELECT COUNT(*) FROM forum_replies WHERE threadId = :id AND NOT deleted", [":id" => $id])->fetchColumn() ?: "-"; + } + + static function GetSubforumInfo($id) + { + return db::run("SELECT * FROM forum_subforums WHERE id = :id", [":id" => $id])->fetch(PDO::FETCH_OBJ); + } + + static function GetSubforumThreadCount($id, $includeReplies = false) + { + $threads = db::run("SELECT COUNT(*) FROM forum_threads WHERE subforumid = :id", [":id" => $id])->fetchColumn(); + if(!$includeReplies) return $threads ?: '-'; + + $replies = db::run("SELECT COUNT(*) from forum_replies WHERE threadId IN (SELECT id FROM forum_threads WHERE subforumid = :id)", [":id" => $id])->fetchColumn(); + $total = $threads + $replies; + + return $total ?: '-'; + } +} + +class pagination +{ + // this is ugly and sucks + // really this is only for the forums + // everything else uses standard next and back pagination + + public static int $page = 1; + public static int $pages = 1; + public static string $url = '/'; + public static array $pager = [1 => 1, 2 => 1, 3 => 1]; + + public static function initialize() + { + self::$pager[1] = self::$page-1; self::$pager[2] = self::$page; self::$pager[3] = self::$page+1; + + if(self::$page <= 2){ self::$pager[1] = self::$page; self::$pager[2] = self::$page+1; self::$pager[3] = self::$page+2; } + if(self::$page == 1){ self::$pager[1] = self::$page+1; } + + if(self::$page >= self::$pages-1){ self::$pager[1] = self::$pages-3; self::$pager[2] = self::$pages-2; self::$pager[3] = self::$pages-1; } + if(self::$page == self::$pages){ self::$pager[1] = self::$pages-1; self::$pager[2] = self::$pages-2; } + if(self::$page == self::$pages-1){ self::$pager[1] = self::$pages-2; self::$pager[2] = self::$pages-1; } + } + + public static function insert() + { + if(self::$pages <= 1) return; + ?> + + UNIX_TIMESTAMP() AND serverID = selfhosted_servers.id AND valid) AS players, + (ping+35 > UNIX_TIMESTAMP()) AS online + FROM selfhosted_servers INNER JOIN users ON users.id = hoster WHERE selfhosted_servers.id = :id", [":id" => $id])->fetch(PDO::FETCH_OBJ); + } + + static function GetPlayersInServer($serverID) + { + return db::run(" + SELECT users.* FROM selfhosted_servers + INNER JOIN client_sessions ON client_sessions.ping+35 > UNIX_TIMESTAMP() AND serverID = selfhosted_servers.id AND valid + INNER JOIN users ON users.id = uid + WHERE selfhosted_servers.id = :id GROUP BY client_sessions.uid", [":id" => $serverID]); + } +} \ No newline at end of file diff --git a/api/private/components/Groups.php b/api/private/components/Groups.php new file mode 100644 index 0000000..a4c4274 --- /dev/null +++ b/api/private/components/Groups.php @@ -0,0 +1,162 @@ + $GroupID])->fetch(PDO::FETCH_OBJ); + } + else + { + $GroupInfo = db::run( + "SELECT groups.*, + (SELECT COUNT(*) FROM groups_members WHERE GroupID = :id AND NOT Pending) AS MemberCount, + users.username AS ownername FROM groups + INNER JOIN users ON users.id = groups.owner + WHERE groups.id = :id", + [":id" => $GroupID] + )->fetch(PDO::FETCH_OBJ); + } + + if(!$Force && $GroupInfo && $GroupInfo->deleted) return false; + return $GroupInfo; + } + + static function GetGroupStatus($GroupID) + { + return db::run( + "SELECT feed.*, users.username FROM feed + INNER JOIN users ON users.id = feed.userId + WHERE groupId = :GroupID ORDER BY id DESC LIMIT 1", + [":GroupID" => $GroupID] + )->fetch(PDO::FETCH_OBJ); + } + + static function GetLastGroupUserJoined($UserID) + { + $GroupID = db::run( + "SELECT GroupID FROM groups_members WHERE UserID = :UserID ORDER BY Joined DESC LIMIT 1", + [":UserID" => $UserID] + )->fetchColumn(); + + return self::GetGroupInfo($GroupID); + } + + static function GetRankInfo($GroupID, $RankLevel) + { + $RankInfo = db::run( + "SELECT * FROM groups_ranks WHERE GroupID = :GroupID AND Rank = :RankLevel", + [":GroupID" => $GroupID, ":RankLevel" => $RankLevel] + )->fetch(PDO::FETCH_OBJ); + + if(!$RankInfo) return false; + + return (object) [ + "Name" => $RankInfo->Name, + "Description" => $RankInfo->Description, + "Level" => $RankInfo->Rank, + "Permissions" => json_decode($RankInfo->Permissions) + ]; + } + + static function GetGroupRanks($GroupID, $includeGuest = false) + { + if($includeGuest) + return db::run("SELECT * FROM groups_ranks WHERE GroupID = :id ORDER BY Rank ASC", [":id" => $GroupID]); + else + return db::run("SELECT * FROM groups_ranks WHERE GroupID = :id AND Rank != 0 ORDER BY Rank ASC", [":id" => $GroupID]); + } + + static function CheckIfUserInGroup($UserID, $GroupID) + { + return db::run( + "SELECT * FROM groups_members WHERE UserID = :UserID AND GroupID = :GroupID", + [":UserID" => $UserID, ":GroupID" => $GroupID] + )->rowCount(); + } + + static function GetUserRank($UserID, $GroupID) + { + $RankLevel = db::run( + "SELECT Rank FROM groups_members WHERE UserID = :UserID And GroupID = :GroupID", + [":UserID" => $UserID, ":GroupID" => $GroupID] + )->fetchColumn(); + + if(!$RankLevel) return self::GetRankInfo($GroupID, 0); + + return self::GetRankInfo($GroupID, $RankLevel); + } + + static function GetUserGroups($UserID) + { + return db::run( + "SELECT groups.* FROM groups_members + INNER JOIN groups ON groups.id = groups_members.GroupID + WHERE groups_members.UserID = :UserID + ORDER BY groups_members.Joined DESC", + [":UserID" => $UserID] + ); + } + + static function LogAction($GroupID, $Category, $Description) + { + // small note: when using this, you gotta be very careful about what you pass into the description + // the description must be sanitized when inserted into the db, not when fetched from an api + // this is because the description may contain hyperlinks or other html elements + // also here's a list of categories: + + // Delete Post + // Remove Member + // Accept Join Request + // Decline Join Request + // Post Shout + // Change Rank + // Buy Ad + // Send Ally Request + // Create Enemy + // Accept Ally Request + // Decline Ally Request + // Delete Ally + // Delete Enemy + // Add Group Place + // Delete Group Place + // Create Items + // Configure Items + // Spend Group Funds + // Change Owner + // Delete + // Adjust Currency Amounts + // Abandon + // Claim + // Rename + // Change Description + // Create Group Asset + // Update Group Asset + // Configure Group Asset + // Revert Group Asset + // Create Group Developer Product + // Configure Group Game + // Lock + // Unlock + // Create Pass + // Create Badge + // Configure Badge + // Save Place + // Publish Place + // Invite to Clan + // Kick from Clan + // Cancel Clan Invite + // Buy Clan + + if(!SESSION) return false; + $MyRank = self::GetUserRank(SESSION["userId"], $GroupID); + + db::run( + "INSERT INTO groups_audit (GroupID, Category, Time, UserID, Rank, Description) + VALUES (:GroupID, :Category, UNIX_TIMESTAMP(), :UserID, :Rank, :Description)", + [":GroupID" => $GroupID, ":Category" => $Category, ":UserID" => SESSION["userId"], ":Rank" => $MyRank->Name, ":Description" => $Description] + ); + } +} \ No newline at end of file diff --git a/api/private/components/Gzip.php b/api/private/components/Gzip.php new file mode 100644 index 0000000..efd427d --- /dev/null +++ b/api/private/components/Gzip.php @@ -0,0 +1,51 @@ +file_new_name_ext = ""; + $handle->file_new_name_body = $options["name"]; + + if($image) + { + $handle->image_convert = "png"; + $handle->image_resize = $resize; + if($resize) + { + if($keepRatio) $handle->image_ratio_fill = $options["align"]; + if($scaleX) $handle->image_ratio_x = true; else $handle->image_x = $options["x"]; + if($scaleY) $handle->image_ratio_y = true; else $handle->image_y = $options["y"]; + } + } + + if(strlen($options["name"]) && file_exists(ROOT.$options["dir"].$options["name"])) + unlink(ROOT.$options["dir"].$options["name"]); + + $handle->process(ROOT.$options["dir"]); + if(!$handle->processed) return $handle->error; + + return true; + } + + static function Resize($file, $w, $h, $path = false) + { + list($width, $height) = getimagesize($file); + $src = imagecreatefrompng($file); + $dst = imagecreatetruecolor($w, $h); + imagealphablending($dst, false); + imagesavealpha($dst, true); + imagecopyresampled($dst, $src, 0, 0, 0, 0, $w, $h, $width, $height); + + // this resize function is used in conjunction with an imagepng function + // to resize an existing image and upload - having to do this eve + if($path) imagepng($dst, $path); + + return $dst; + } + + static function MergeLayers($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) + { + $cut = imagecreatetruecolor($src_w, $src_h); + imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h); + imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h); + imagecopymerge($dst_im, $cut, $dst_x, $dst_y, 0, 0, $src_w, $src_h, $pct); + } + + // pre rendered thumbnails (scripts and audios) are all rendered with the same size + // so this just sorta cleans up the whole thing + static function RenderFromStaticImage($img, $assetID) + { + Image::Resize(ROOT."/thumbs/$img.png", 75, 75, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-75x75.png"); + Image::Resize(ROOT."/thumbs/$img.png", 100, 100, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-100x100.png"); + Image::Resize(ROOT."/thumbs/$img.png", 110, 110, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-110x110.png"); + Image::Resize(ROOT."/thumbs/$img.png", 250, 250, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-250x250.png"); + Image::Resize(ROOT."/thumbs/$img.png", 352, 352, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-352x352.png"); + Image::Resize(ROOT."/thumbs/$img.png", 420, 230, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-420x230.png"); + Image::Resize(ROOT."/thumbs/$img.png", 420, 420, SITE_CONFIG['paths']['thumbs_assets']."/$assetID-420x420.png"); + } +} \ No newline at end of file diff --git a/api/private/components/RBXClient.php b/api/private/components/RBXClient.php new file mode 100644 index 0000000..5c1e5f5 --- /dev/null +++ b/api/private/components/RBXClient.php @@ -0,0 +1,18 @@ + $total*1024, "free" => $free*1024]; + } +} \ No newline at end of file diff --git a/api/private/components/Thumbnails.php b/api/private/components/Thumbnails.php new file mode 100644 index 0000000..e1d3144 --- /dev/null +++ b/api/private/components/Thumbnails.php @@ -0,0 +1,112 @@ + "0180a01964362301c67cc47344ff34c2041573c0", + "pending-110x110.png" => "e3dd8134956391d4b29070f3d4fc8db1a604f160", + "pending-250x250.png" => "d2c46fc832fb48e1d24935893124d21f16cb5824", + "pending-352x352.png" => "a4ce4cc7e648fba21da9093bcacf1c33c3903ab9", + "pending-420x420.png" => "2f4e0764e8ba3946f52e2b727ce5986776a8a0de", + "pending-48x48.png" => "4e3da1b2be713426b48ddddbd4ead386aadec461", + "pending-75x75.png" => "6ab927863f95d37af1546d31e3bf8b096cc9ed4a", + + "rendering-100x100.png" => "b67cc4a3d126f29a0c11e7cba3843e6aceadb769", + "rendering-110x110.png" => "d059575ffed532648d3dcf6b1429defcc98fc8b1", + "rendering-250x250.png" => "9794c31aa3c4779f9cb2c541cedf2c25fa3397fe", + "rendering-352x352.png" => "f523775cc3da917e15c3b15e4165fee2562c0ff1", + "rendering-420x420.png" => "a9e786b5c339f29f9016d21858bf22c54146855c", + "rendering-48x48.png" => "d7a9b5d7044636d3011541634aee43ca4a86ade6", + "rendering-75x75.png" => "fa2ec2e53a4d50d9103a6e4370a3299ba5391544", + + "unapproved-100x100.png" => "d4b4b1f0518597bafcd9cf342b6466275db34bbc", + "unapproved-110x110.png" => "7ad17e54cf834efd298d76c8799f58daf9c6829f", + "unapproved-250x250.png" => "cddec9d17ee3afc5da51d2fbf8011e562362e39a", + "unapproved-352x352.png" => "509b6c7bdb121e4185662987096860dd7f54ae11", + "unapproved-420x420.png" => "f31bc4f3d5008732f91ac90608d4e77fcd8d8d2b", + "unapproved-48x48.png" => "82da22ba47414d25ee544a253f6129d106cf17ef", + "unapproved-75x75.png" => "13ad6ad9ab4f84f03c58165bc8468a181d07339c" + ]; + + private static function GetCDNLocation($hash) + { + return self::$BaseURL.$hash.".png"; + } + + static function GetStatus($status, $x, $y) + { + return self::GetCDNLocation(self::$StatusThumbnails["{$status}-{$x}x{$y}.png"]); + } + + static function UploadToCDN($filepath) + { + $hash = sha1_file($filepath); + file_put_contents($_SERVER["DOCUMENT_ROOT"]."/../polygoncdn/".$hash.".png", file_get_contents($filepath)); + } + + static function GetAsset($sqlResult, $x, $y, $force = false) + { + // for this we need to pass in an sql pdo result + // this is so we can check if the asset is under review or disapproved + // passing in the sql result here saves us from having to do another query + // if we implement hash caching then we'd also use this for that + + $assetID = $sqlResult->id; + $filepath = SITE_CONFIG['paths']['thumbs_assets']."/{$assetID}-{$x}x{$y}.png"; + if(!file_exists($filepath)) return self::GetStatus("rendering", $x, $y); + + if($force || $sqlResult->approved == 1) return self::GetCDNLocation(sha1_file($filepath)); + if($sqlResult->approved == 0) return self::GetStatus("pending", $x, $y); + if($sqlResult->approved == 2) return self::GetStatus("unapproved", $x, $y); + } + + static function GetAssetFromID($AssetID, $x, $y, $force = false) + { + // primarily used for fetching group emblems + // we dont need to block this as group emblems are fine to show publicly + + $AssetInfo = db::run("SELECT * FROM assets WHERE id = :id", [":id" => $AssetID]); + if(!$AssetInfo->rowCount()) return false; + return self::GetAsset($AssetInfo->fetch(PDO::FETCH_OBJ), $x, $y, $force); + } + + static function GetAvatar($avatarID, $x, $y) + { + if(!SESSION) return self::GetStatus("rendering", $x, $y); + + $filepath = SITE_CONFIG['paths']['thumbs_avatars']."/{$avatarID}-{$x}x{$y}.png"; + if(!file_exists($filepath)) return self::GetStatus("rendering", $x, $y); + return self::GetCDNLocation(sha1_file($filepath)); + } + + static function UploadAsset($handle, $assetID, $x, $y, $additionalOptions = []) + { + Polygon::ImportClass("Image"); + + $options = ["name" => "{$assetID}-{$x}x{$y}.png", "x" => $x, "y" => $y, "dir" => "/thumbs/assets/"]; + $options = array_merge($options, $additionalOptions); + + Image::Process($handle, $options); + self::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."/{$assetID}-{$x}x{$y}.png"); + } + + static function UploadAvatar($handle, $avatarID, $x, $y) + { + Polygon::ImportClass("Image"); + + Image::Process($handle, ["name" => "{$avatarID}-{$x}x{$y}.png", "x" => $x, "y" => $y, "dir" => "/thumbs/avatars/"]); + self::UploadToCDN(SITE_CONFIG['paths']['thumbs_avatars']."/{$avatarID}-{$x}x{$y}.png"); + } +} \ No newline at end of file diff --git a/api/private/components/TwoFactorAuth.php b/api/private/components/TwoFactorAuth.php new file mode 100644 index 0000000..f477468 --- /dev/null +++ b/api/private/components/TwoFactorAuth.php @@ -0,0 +1,47 @@ + (int)!SESSION["2fa"], ":uid" => SESSION["userId"]] + ); + } + + static function GenerateRecoveryCodes() + { + if(!SESSION) return false; + + $codes = str_split(bin2hex(random_bytes(60)), 12); + db::run( + "UPDATE users SET twofaRecoveryCodes = :json WHERE id = :uid", + [":json" => json_encode(array_fill_keys($codes, true)), ":uid" => SESSION["userId"]] + ); + return $codes; + } + + static function GenerateNewSecret($GoogleAuthenticator) + { + if(!SESSION) return false; + + $secret = $GoogleAuthenticator->generateSecret(); + db::run( + "UPDATE users SET twofaSecret = :secret WHERE id = :uid", + [":secret" => $secret, ":uid" => SESSION["userId"]] + ); + return $secret; + } +} \ No newline at end of file diff --git a/api/private/db.php b/api/private/components/db.php similarity index 70% rename from api/private/db.php rename to api/private/components/db.php index 6fbde57..8cd442c 100644 --- a/api/private/db.php +++ b/api/private/components/db.php @@ -1,6 +1,4 @@ getMessage(); die(); +} + +class db +{ + static function run($sql, $params = false) + { + global $pdo; + if(!$params) return $pdo->query($sql); + + $query = $pdo->prepare($sql); + $query->execute($params); + return $query; + } } \ No newline at end of file diff --git a/api/private/pagebuilder.php b/api/private/components/pagebuilder.php similarity index 55% rename from api/private/pagebuilder.php rename to api/private/components/pagebuilder.php index 1905c88..5571320 100644 --- a/api/private/pagebuilder.php +++ b/api/private/components/pagebuilder.php @@ -11,15 +11,13 @@ class pageBuilder ]; // this is separate from js dependencies as these MUST be loaded at the bottom - // when core.js is moved to its own file instead of being plopped directly into - // the html, this wont be necessary - public static array $polygonScripts = []; + public static array $polygonScripts = ["/js/polygon/core.js?t=35"]; public static array $CSSdependencies = [ "/css/fontawesome-pro-v5.15.2/css/all.css", "/css/toastr.css", - "/css/polygon.css?t=4" + "/css/polygon.css?t=15" ]; public static array $pageConfig = @@ -28,7 +26,7 @@ class pageBuilder "og:site_name" => SITE_CONFIG["site"]["name"], "og:url" => "https://polygon.pizzaboxer.xyz", "og:description" => "yeah its a website about shapes and squares and triangles and stuff and ummmmm", - "og:image" => "https://chef.pizzaboxer.xyz/img/ProjectPolygon.png", + "og:image" => "https://polygon.pizzaboxer.xyz/img/ProjectPolygon.png", "includeNav" => true, "includeFooter" => true, "app-attributes" => "" @@ -51,10 +49,32 @@ class pageBuilder static function buildHeader() { + $theme = "light"; + // ideally i should probably have this loaded in from // core.php instead of doing the php query on the fly here global $pdo, $announcements, $markdown; - if(SESSION && SESSION["adminLevel"]) $pendingAssets = db::run("SELECT COUNT(*) FROM assets WHERE NOT approved AND type != 1")->fetchColumn(); + if(SESSION) + { + if(SESSION["adminLevel"]) + { + $pendingAssets = db::run("SELECT COUNT(*) FROM assets WHERE NOT approved AND (type != 1 || (SELECT COUNT(*) FROM polygon.groups WHERE emblem = assets.id))")->fetchColumn(); + } + + $theme = SESSION["userInfo"]["theme"]; + + if($theme == "dark") self::$CSSdependencies[] = "/css/polygon-dark.css?t=4"; + else if($theme == "2013") self::$CSSdependencies[] = "/css/polygon-2013.css"; + else if($theme == "hitius") self::$CSSdependencies[] = "/css/polygon-hitius.css"; + else if($theme == "2014") + { + self::$CSSdependencies[] = "/css/polygon-2014.css?t=".time(); + self::$JSdependencies[] = "/js/polygon/Navigation2014.js?t=".time(); + self::$pageConfig["app-attributes"] .= " id=\"navContent\""; + } + } + + ob_start(); ?> @@ -72,44 +92,122 @@ class pageBuilder "> - + - + - + - - - - - - - -
" role="alert" style="background-color: "> - text($announcement["text"])?> +
- -
> - + +
" role="alert" style="background-color: "> + text($announcement["text"])?>
- + + +
> + + +
+
+
+
+ Level != 0) { ?> +
+
+
Controls
+ Permissions->CanManageGroupAdmin) { ?>Group Admin + owner != SESSION["userId"]) { ?> + Permissions->CanViewAuditLog) { ?>Audit Log +
+
+ +
+
+ +

You are not currently in any groups. Search for some above, or create one!

+ +
- Edit + Edit

My Friends

@@ -24,7 +26,7 @@ pageBuilder::buildHeader();
- $username + $username

$username

@@ -38,9 +40,24 @@ pageBuilder::buildHeader();
-
-
+
+ +
+
- $userName + $userName
$header @@ -85,7 +104,7 @@ pageBuilder::buildHeader();
- $game_name + $game_name

$game_name

@@ -96,76 +115,4 @@ pageBuilder::buildHeader();
- - - diff --git a/img/2013/BuildPage/btn-gear_sprite_27px.png b/img/2013/BuildPage/btn-gear_sprite_27px.png new file mode 100644 index 0000000..3082a39 Binary files /dev/null and b/img/2013/BuildPage/btn-gear_sprite_27px.png differ diff --git a/img/2013/Buttons/Arrows/btn-silver-left-27.png b/img/2013/Buttons/Arrows/btn-silver-left-27.png new file mode 100644 index 0000000..ab3d656 Binary files /dev/null and b/img/2013/Buttons/Arrows/btn-silver-left-27.png differ diff --git a/img/2013/Buttons/Arrows/btn-silver-right-27.png b/img/2013/Buttons/Arrows/btn-silver-right-27.png new file mode 100644 index 0000000..ec3aab1 Binary files /dev/null and b/img/2013/Buttons/Arrows/btn-silver-right-27.png differ diff --git a/img/2013/Buttons/StyleGuide/bg-btn-blue-arrow-md.png b/img/2013/Buttons/StyleGuide/bg-btn-blue-arrow-md.png new file mode 100644 index 0000000..48a984c Binary files /dev/null and b/img/2013/Buttons/StyleGuide/bg-btn-blue-arrow-md.png differ diff --git a/img/2013/Buttons/StyleGuide/bg-btn-blue.png b/img/2013/Buttons/StyleGuide/bg-btn-blue.png new file mode 100644 index 0000000..e82b97e Binary files /dev/null and b/img/2013/Buttons/StyleGuide/bg-btn-blue.png differ diff --git a/img/2013/Buttons/StyleGuide/bg-btn-gray-arrow-md.png b/img/2013/Buttons/StyleGuide/bg-btn-gray-arrow-md.png new file mode 100644 index 0000000..e55322e Binary files /dev/null and b/img/2013/Buttons/StyleGuide/bg-btn-gray-arrow-md.png differ diff --git a/img/2013/Buttons/StyleGuide/bg-btn-gray.png b/img/2013/Buttons/StyleGuide/bg-btn-gray.png new file mode 100644 index 0000000..ec014f9 Binary files /dev/null and b/img/2013/Buttons/StyleGuide/bg-btn-gray.png differ diff --git a/img/2013/Buttons/StyleGuide/bg-btn-green.png b/img/2013/Buttons/StyleGuide/bg-btn-green.png new file mode 100644 index 0000000..b19e6fc Binary files /dev/null and b/img/2013/Buttons/StyleGuide/bg-btn-green.png differ diff --git a/img/2013/Buttons/StyleGuide/bg-lg-green-play.png b/img/2013/Buttons/StyleGuide/bg-lg-green-play.png new file mode 100644 index 0000000..10e91c9 Binary files /dev/null and b/img/2013/Buttons/StyleGuide/bg-lg-green-play.png differ diff --git a/img/2013/Buttons/bg-drop_down_btn.png b/img/2013/Buttons/bg-drop_down_btn.png new file mode 100644 index 0000000..a7ae6db Binary files /dev/null and b/img/2013/Buttons/bg-drop_down_btn.png differ diff --git a/img/2013/Buttons/bg-form_btn_lg-tile.png b/img/2013/Buttons/bg-form_btn_lg-tile.png new file mode 100644 index 0000000..71195d8 Binary files /dev/null and b/img/2013/Buttons/bg-form_btn_lg-tile.png differ diff --git a/img/2013/Buttons/questionmark-12x12.png b/img/2013/Buttons/questionmark-12x12.png new file mode 100644 index 0000000..be9db8f Binary files /dev/null and b/img/2013/Buttons/questionmark-12x12.png differ diff --git a/img/2013/GamesPage/arrow_left.png b/img/2013/GamesPage/arrow_left.png new file mode 100644 index 0000000..bc7ef2b Binary files /dev/null and b/img/2013/GamesPage/arrow_left.png differ diff --git a/img/2013/GamesPage/arrow_right.png b/img/2013/GamesPage/arrow_right.png new file mode 100644 index 0000000..99349c9 Binary files /dev/null and b/img/2013/GamesPage/arrow_right.png differ diff --git a/img/2013/Icons/Navigation2014/Nav2014-icon-sprite-sheet.png b/img/2013/Icons/Navigation2014/Nav2014-icon-sprite-sheet.png new file mode 100644 index 0000000..ec6d1a9 Binary files /dev/null and b/img/2013/Icons/Navigation2014/Nav2014-icon-sprite-sheet.png differ diff --git a/img/2013/StyleGuide/btn-control-large-tile.png b/img/2013/StyleGuide/btn-control-large-tile.png new file mode 100644 index 0000000..c9bedf6 Binary files /dev/null and b/img/2013/StyleGuide/btn-control-large-tile.png differ diff --git a/img/2013/StyleGuide/btn-control-medium-tile.png b/img/2013/StyleGuide/btn-control-medium-tile.png new file mode 100644 index 0000000..487aaeb Binary files /dev/null and b/img/2013/StyleGuide/btn-control-medium-tile.png differ diff --git a/img/2013/StyleGuide/btn-control-small-tile.png b/img/2013/StyleGuide/btn-control-small-tile.png new file mode 100644 index 0000000..2eb9aa9 Binary files /dev/null and b/img/2013/StyleGuide/btn-control-small-tile.png differ diff --git a/img/2013/roblox_logo.png b/img/2013/roblox_logo.png new file mode 100644 index 0000000..ee0cedc Binary files /dev/null and b/img/2013/roblox_logo.png differ diff --git a/img/badges/CatalogManager.png b/img/badges/CatalogManager.png new file mode 100644 index 0000000..d7cae12 Binary files /dev/null and b/img/badges/CatalogManager.png differ diff --git a/img/badges/Friends.png b/img/badges/Friends.png new file mode 100644 index 0000000..e44a891 Binary files /dev/null and b/img/badges/Friends.png differ diff --git a/img/badges/Moderator.png b/img/badges/Moderator.png new file mode 100644 index 0000000..1e6ad60 Binary files /dev/null and b/img/badges/Moderator.png differ diff --git a/img/badges/Veteran.png b/img/badges/Veteran.png new file mode 100644 index 0000000..93da954 Binary files /dev/null and b/img/badges/Veteran.png differ diff --git a/img/error.png b/img/error.png index 000552e..d6392a6 100644 Binary files a/img/error.png and b/img/error.png differ diff --git a/img/feed/cart.png b/img/feed/cart.png new file mode 100644 index 0000000..41d0540 Binary files /dev/null and b/img/feed/cart.png differ diff --git a/img/feed/friends.png b/img/feed/friends.png new file mode 100644 index 0000000..de53aee Binary files /dev/null and b/img/feed/friends.png differ diff --git a/index.php b/index.php index 3ad7f6f..8a064ef 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,122 @@ - 1, + "e6a76346d3ece5e9891c4876b85174bf" => 5, + "78af60b3e80630cc8b2f4372ab1e8c8d" => 5, + "c8f51135774e7f4e4027921fe947f67f" => 4, + "e6a76346b3ece5e9891c4876b85174bc" => 4, + "doodoku deez nuts" => 1, + "OneYearAnniversary!" => 9999 +]; + +$Errors = (object) +[ + "Username" => false, + "Password" => false, + "ConfirmPassword" => false, + "RegistrationKey" => false, + "ReCAPTCHA" => false +]; + +$Fields = (object) +[ + "Username" => "", + "Password" => "", + "ConfirmPassword" => "", + "RegistrationKey" => "" +]; + +$MaximumAccounts = count(Users::GetAlternateAccounts(GetIPAddress())) >= 2; + +$RequestSent = false; + +if($_SERVER['REQUEST_METHOD'] == 'POST' && !$MaximumAccounts) +{ + $RequestSent = true; + + $Fields->Username = $_POST['Username'] ?? ""; + $Fields->Password = $_POST['Password'] ?? ""; + $Fields->ConfirmPassword = $_POST['ConfirmPassword'] ?? ""; + $Fields->RegistrationKey = "OneYearAnniversary!"; //$_POST['RegistrationKey'] ?? "!"; + + if(empty($Fields->Username)) $Errors->Username = "Please enter a username"; + else if(strlen($Fields->Username) < 3 || strlen($Fields->Username) > 20) $Errors->Username = "Your username can only be between three and twenty characters long"; + else if(!ctype_alnum($Fields->Username)) $Errors->Username = "Your username can only contain letters and numbers"; + else + { + $Blacklisted = db::run( + "SELECT COUNT(*) FROM namefilter WHERE (exact AND username = :name) OR (NOT exact AND :name LIKE CONCAT('%', username, '%'))", + [":name" => strtolower($Fields->Username)] + )->fetchColumn() > 0; + + if($Blacklisted) $Errors->Username = "That username is unavailable. Sorry!"; + + $AlreadyUsed = db::run( + "SELECT COUNT(*) FROM users WHERE username = :name", + [":name" => $Fields->Username] + )->fetchColumn() > 0; + + if($AlreadyUsed) $Errors->Username = "Someone already has that username! Try choosing a different one."; + } + + if(empty($Fields->Password)) $Errors->Password = "Please enter a password"; + else if(strlen(preg_replace('/[0-9]/', "", $Fields->Password)) < 6) $Errors->Password = "Your password is too weak. Make sure it contains at least six non-numeric characters"; + else if(strlen(preg_replace('/[^0-9]/', "", $Fields->Password)) < 2) $Errors->Password = "Your password is too weak. Make sure it contains at least two numbers"; + + if(empty($Fields->ConfirmPassword)) $Errors->ConfirmPassword = "Please confirm your password"; + else if($Fields->Password != $Fields->ConfirmPassword) $Errors->ConfirmPassword = "Confirmation password does not match with your password"; + + if(!isset($keys[$Fields->RegistrationKey])) $Errors->RegistrationKey = "Invalid registration key"; + else + { + $KeyUses = db::run("SELECT COUNT(*) FROM users WHERE keyUsed = :key", [":key" => $Fields->RegistrationKey])->fetchColumn(); + if($KeyUses >= $keys[$Fields->RegistrationKey]) $Errors->RegistrationKey = "Invalid registration key"; + } + + if(!VerifyReCAPTCHA()) $Errors->ReCAPTCHA = "ReCAPTCHA verification failed, please try again"; + + if(!$Errors->Username && !$Errors->Password && !$Errors->ConfirmPassword && !$Errors->RegistrationKey) + { + Polygon::ImportClass("Auth"); + + $auth = new Auth($Fields->Password); + $pwhash = $auth->CreatePassword(); + + db::run( + "INSERT INTO users (username, password, keyUsed, email, jointime, lastonline, regip, nextCurrencyStipend, status) + VALUES (:name, :hash, :key, 'placeholder', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :ip, UNIX_TIMESTAMP()+86400, 'I\'m new to Polygon!')", + [":name" => $Fields->Username, ":hash" => $pwhash, ":key" => $Fields->RegistrationKey, ":ip" => GetIPAddress()] + ); + + $UserID = $pdo->lastInsertId(); + + db::run( + "INSERT INTO ownedAssets (assetId, userId, wearing, timestamp) VALUES (162, :uid, 1, UNIX_TIMESTAMP()); + INSERT INTO ownedAssets (assetId, userId, wearing, timestamp) VALUES (310, :uid, 1, UNIX_TIMESTAMP())", + [":uid" => (int)$UserID] + ); + + session::createSession($UserID); + + // Polygon::RequestRender("Avatar", $UserID); + // this is just malwarebytes's avatar - he still has the default avatar and is banned so eh + copy(ROOT."/thumbs/avatars/32-420x420.png", ROOT."/thumbs/avatars/$UserID-420x420.png"); + copy(ROOT."/thumbs/avatars/32-352x352.png", ROOT."/thumbs/avatars/$UserID-352x352.png"); + copy(ROOT."/thumbs/avatars/32-250x250.png", ROOT."/thumbs/avatars/$UserID-250x250.png"); + copy(ROOT."/thumbs/avatars/32-110x110.png", ROOT."/thumbs/avatars/$UserID-110x110.png"); + copy(ROOT."/thumbs/avatars/32-100x100.png", ROOT."/thumbs/avatars/$UserID-100x100.png"); + copy(ROOT."/thumbs/avatars/32-75x75.png", ROOT."/thumbs/avatars/$UserID-75x75.png"); + copy(ROOT."/thumbs/avatars/32-48x48.png", ROOT."/thumbs/avatars/$UserID-48x48.png"); + + die(header("Location: /")); + } +} + +pageBuilder::$JSdependencies[] = "https://www.google.com/recaptcha/api.js"; pageBuilder::$pageConfig["title"] = "Welcome"; -pageBuilder::$pageConfig["includeNav"] = false; pageBuilder::buildHeader(); ?>
@@ -44,58 +162,80 @@ pageBuilder::buildHeader();

welcome to

-
- -
-
- + +
+
(placeholder video)
-
+
-
-
-
+
+
" id="signup" role="tabpanel" aria-labelledby="signup-tab"> + +
+ +

Account limit reached

+ You can only create up to two accounts +
+ +
- + " name="Username" id="username" autocomplete="username" value="Username)?>"> + Username != false) { ?>Username?> 3 - 20 alphanumeric characters, no spaces or underscores
- + " name="Password" id="password" autocomplete="new-password" value="Password)?>"> + Password != false) { ?>Password?> minimum 8 characters, must have at least 6 characters and 2 numbers
- + " name="ConfirmPassword" id="confirmpassword" value="ConfirmPassword)?>"> + ConfirmPassword != false) { ?>ConfirmPassword?>
-
- - + +
+ ReCAPTCHA != false) { ?>ReCAPTCHA?>
- + +
-
+
" id="login" role="tabpanel" aria-labelledby="login-tab"> + +
+ +
@@ -112,4 +252,10 @@ pageBuilder::buildHeader();
+ diff --git a/info/privacy.php b/info/privacy.php index 627ca49..d57a0d5 100644 --- a/info/privacy.php +++ b/info/privacy.php @@ -16,6 +16,5 @@ pageBuilder::buildHeader();

We do not willingly give any of your information to any third-parties.

Additional Stuff

We do use cookies, and the only cookie we store is your session cookie used to identify you.

-

When you play or host a game, your peer may be able to get your IP address. Click here for more information.

diff --git a/info/selfhosting.php b/info/selfhosting.php deleted file mode 100644 index 3b8ce7b..0000000 --- a/info/selfhosting.php +++ /dev/null @@ -1,21 +0,0 @@ - -
-

About self-hosted servers

-

In , games are hosted by players on the website that other players connect to directly, instead of conventionally connecting to a server hosted by us. This is called self-hosting, but it's not without its caveats. There's some small security stuff that you should keep in mind while playing or hosting a game.

-

For players

-

When you connect to a server, if the server hoster knows a thing or two, they can get your IP address using a network analyzer or something.
This shouldn't really be something you have to be concerned about as they need to have actual malicious intent to use special tools to actually get it. It's not just right there in front of them to easily take (like it admittedly was with GoodBlox). However if you want to be cautious then it's recommended you use a VPN when playing, or you only join servers hosted by people you trust.

-

For server hosters

-

If a player also knows a thing or two, they can get your IP address using a web debugging proxy. It should be noted that clients connected to a server can only get the IP of the server hoster, and not the IP addresses of anyone else connected to the server.

-

With all of these in mind, have fun!

-

Some additional information

-

Resetting

-

Kind of like the ;ec command in Finobe, you can say ;hxiuh (or ;hx for short) to reset. That's about it.

-

Assets not loading

-

In order to help improve security, roblox.com had to be removed from the client's trust check code. Because of this, assets using www.roblox.com/asset/?id= will not load.
To get assets in your map to load, open up the map you want to host in a text editor (like notepad++) and do a find/replace operation for www.roblox.com/asset with chef.pizzaboxer.xyz/asset.
We may eventually automate this inside the client, but for now this is what you have to do.

-
- diff --git a/info/terms-of-service.php b/info/terms-of-service.php index 76b5cd8..39bcfea 100644 --- a/info/terms-of-service.php +++ b/info/terms-of-service.php @@ -14,7 +14,7 @@ pageBuilder::buildHeader();
  • Don't say slurs in a demeaning or discriminatory way.
  • Don't post any malicious stuff onsite (dox, IP loggers, etc).
  • Don't post racist or homo/transphobic stuff. Hey, even if you don't agree with them personally, you don't have to talk about it here. ?> -
  • Exploiting is not allowed unless the server hoster allows it.
  • +
  • Exploiting is not allowed unless the game owner allows it.
  • In general, all we ask for is to keep it nice and civil here and don't be overly toxic.

    diff --git a/item.php b/item.php index 3bff683..f9e7a07 100644 --- a/item.php +++ b/item.php @@ -1,23 +1,26 @@ -id); -$isCreator = SESSION && (SESSION["adminLevel"] || $item->creator == SESSION["userId"]); +$ownsAsset = SESSION && Catalog::OwnsAsset(SESSION["userId"], $item->id); +$isCreator = SESSION && $item->creator == SESSION["userId"]; +$isAdmin = Users::IsAdmin(); if($_SERVER['REQUEST_URI'] != "/".encode_asset_name($item->name)."-item?id=".$item->id) redirect("/".encode_asset_name($item->name)."-item?id=".$item->id); -if(SESSION && SESSION["adminLevel"]) pageBuilder::$polygonScripts[] = "/js/polygon/admin/asset-moderation.js?t=".time(); -pageBuilder::$pageConfig['title'] = polygon::filterText($item->name).", ".vowel(catalog::getTypeByNum($item->type))." by ".$item->username; -pageBuilder::$pageConfig["og:description"] = polygon::filterText($item->description); +if(Users::IsAdmin()) pageBuilder::$polygonScripts[] = "/js/polygon/admin/asset-moderation.js?t=".time(); +pageBuilder::$pageConfig['title'] = Polygon::FilterText($item->name).", ".vowel(Catalog::GetTypeByNum($item->type))." by ".$item->username; +pageBuilder::$pageConfig["og:description"] = Polygon::FilterText($item->description); pageBuilder::$pageConfig["og:image"] = Thumbnails::GetAsset($item, 420, 420); pageBuilder::$pageConfig["app-attributes"] = ' data-asset-id="'.$item->id.'"'; pageBuilder::buildHeader(); ?>
    - + -

    name)?>

    -
    type)?>
    +

    name)?>

    +
    type)?>
    @@ -55,12 +59,12 @@ pageBuilder::buildHeader();
    description)) { ?> -

    description))?>

    +

    description))?>


    type == 19) { ?> Gear Attributes:
    gear_attributes) as $attr => $enabled) { if($enabled) { ?> -
    ">
    +
    ">
    type == 3) { ?>
    -
    +
    sale){ ?>

    Price: price?' '.$item->price:'FREE'?>

    @@ -77,7 +81,7 @@ pageBuilder::buildHeader(); sale) { ?> - + @@ -140,7 +144,7 @@ pageBuilder::buildHeader();
    - +
    diff --git a/js/Navigation2014.js b/js/Navigation2014.js new file mode 100644 index 0000000..ff0c3ba --- /dev/null +++ b/js/Navigation2014.js @@ -0,0 +1,265 @@ +'use strict'; +$(function() +{ + function callback(obj) + { + var mainHeader = $(".nav-open"); + if (obj !== undefined) + { + mainHeader = mainHeader.not(obj); + } + + mainHeader = mainHeader.not(".nav-container"); + if (item.hasClass("nav-open")) + { + item.toggleClass("universal-search-open", false); + item.addClass("closing").delay(300).queue(function(metadataCallback) + { + $(this).removeClass("closing"); + metadataCallback(); + }); + } + + mainHeader.toggleClass("nav-open"); + if (obj !== undefined) + { + obj.toggleClass("nav-open"); + } + } + + function scoringValidation(event) + { + var codes = $(".header-2014 .search .universal-search-option"); + var i = -1; + + $.each(codes, function(maxAtomIndex, nextElement) + { + if ($(nextElement).hasClass("selected")) + { + $(nextElement).removeClass("selected"); + i = maxAtomIndex; + } + }); + + i = i + (event.which === 38 ? codes.length - 1 : 1); + i = i % codes.length; + + $(codes[i]).addClass("selected"); + } + + var debuggerContainer = $(".navigation-container"); + if (debuggerContainer.length > 0) + { + debuggerContainer.mCustomScrollbar({ + theme : "dark-3", + scrollInertia : 0, + autoHideScrollbar : false, + autoExpandScrollbar : false, + scrollButtons : + { + enable : false + } + }); + } + + $("#navigation .notification-icon.tooltip-right").tipsy(); + $(".nav-icon .notification-icon.tooltip-bottom").tipsy(); + + var expectedFloor = 1359; + var viewportCenter = 1480; + var actualCeil = Roblox.FixedUI.getWindowWidth(); + + var satisfiesLowerLimit = actualCeil >= expectedFloor; + var item = $(".header-2014 .search"); + var fakeInputElement = $(".header-2014 .search input"); + var mainHeader = $(".nav-container"); + + if (Roblox.FixedUI.isMobile) + { + $("#navigation").addClass("mobile"); + } + + if (!mainHeader.hasClass("no-gutter-ads") && actualCeil < viewportCenter) + { + satisfiesLowerLimit = false; + } + + if (satisfiesLowerLimit) + { + mainHeader.addClass("nav-open-static"); + } + + if ($("#navigation").length == 0) + { + $("#navContent").css( + { + "margin-left" : "0px", + width : "100%" + }); + + $(".nav-container .nav-icon").css("display", "none"); + $(".header-2014 .logo").css("margin", "3px 0 0 45px"); + $("#navContent").addClass("nav-no-left"); + } + + $(".nav-icon").on("click", function() + { + if (mainHeader.hasClass("nav-open-static")) + { + mainHeader.removeClass("nav-open-static"); + } + else + { + mainHeader.toggleClass("nav-open"); + } + }); + + $(".tickets-icon, .robux-icon, .tickets-amount, .robux-amount, .settings-icon").on("click mouseover mouseout", function(event) + { + event.stopPropagation(); + event.preventDefault(); + callback($(this).parent()); + }); + + $("#lsLoginStatus").on("click", function(event) + { + event.stopPropagation(); + event.preventDefault(); + var clicked_element = $("#lsLoginStatus"); + var form = clicked_element.closest("form"); + if (form.length === 0) + { + form = $("").appendTo("body"); + } + form.attr("action", clicked_element.attr("href")); + form.attr("method", "post"); + form.submit(); + }); + + $(".header-2014 .search-icon").on("click", function(event) + { + var c; + var divel; + var s; + event.stopPropagation(); + c = fakeInputElement.val(); + + if (c.length > 2 && item.hasClass("universal-search-open")) + { + divel = $(".header-2014 .search .universal-search-option.selected"); + s = divel.data("searchurl"); + window.location = s + encodeURIComponent(c); + } + else + { + callback(item); + fakeInputElement.focus(); + } + }); + + $(window).resize(function() + { + var reconnectTryTimes = Roblox.FixedUI.getWindowWidth(); + var interestingPoint = expectedFloor; + + if (!mainHeader.hasClass("no-gutter-ads")) + { + interestingPoint = viewportCenter; + } + + if (reconnectTryTimes >= interestingPoint && !(mainHeader.hasClass("nav-open") || mainHeader.hasClass("nav-open-static"))) + { + mainHeader.addClass("nav-open"); + } + + item.toggleClass("universal-search-open", false); + callback(); + }); + + $(".search input").on("keydown", function(event) + { + var expRecords = $(this).val(); + if ((event.which === 9 || event.which === 38 || event.which === 40) && expRecords.length > 0) + { + event.stopPropagation(); + event.preventDefault(); + scoringValidation(event); + } + }); + + $(".search input").on("keyup", function(event) + { + var param = $(this).val(); + var divel; + var url; + if (event.which === 13) + { + event.stopPropagation(); + event.preventDefault(); + divel = $(".header-2014 .search .universal-search-option.selected"); + url = divel.data("searchurl"); + if (param.length > 2) + { + window.location = url + encodeURIComponent(param); + } + } + else + { + if (param.length > 0) + { + item.toggleClass("universal-search-open", true); + $(".header-2014 .search .universal-search-dropdown .universal-search-string").text('"' + param + '"'); + } + else + { + item.toggleClass("universal-search-open", false); + } + } + }); + + $(".header-2014 .search .universal-search-option").on("click touchstart", function(event) + { + var hash; + var u; + event.stopPropagation(); + hash = fakeInputElement.val(); + if (hash.length > 2) + { + u = $(this).data("searchurl"); + window.location = u + encodeURIComponent(hash); + } + }); + + $(".header-2014 .search .universal-search-option").on("mouseover", function() + { + $(".header-2014 .search .universal-search-option").removeClass("selected"); + $(this).addClass("selected"); + }); + + $(".search input").on("focus", function() + { + var expRecords = fakeInputElement.val(); + if (expRecords.length > 0) + { + item.addClass("universal-search-open"); + } + }); + + $(".search input").on("click", function(event) + { + event.stopPropagation(); + }); + + $(".under-13").tipsy({ gravity : "n" }); + + $(".nav-content, .navigation, .header-2014").on("click", function() + { + callback(undefined); + item.toggleClass("universal-search-open", false); + }); + + $(".navigation, .notifications-container, .tickets-container, .robux-container").on("click", "a, a > span", function(event) + { + event.stopPropagation(); + }); +}); diff --git a/js/polygon/Navigation2014.js b/js/polygon/Navigation2014.js new file mode 100644 index 0000000..517fc4f --- /dev/null +++ b/js/polygon/Navigation2014.js @@ -0,0 +1,210 @@ +'use strict'; +$(function() +{ + function callback(obj) + { + var mainHeader = $(".nav-open"); + if (obj !== undefined) + { + mainHeader = mainHeader.not(obj); + } + + mainHeader = mainHeader.not(".nav-container"); + if (item.hasClass("nav-open")) + { + item.toggleClass("universal-search-open", false); + item.addClass("closing").delay(300).queue(function(metadataCallback) + { + $(this).removeClass("closing"); + metadataCallback(); + }); + } + + mainHeader.toggleClass("nav-open"); + if (obj !== undefined) + { + obj.toggleClass("nav-open"); + } + } + + function scoringValidation(event) + { + var codes = $(".header-2014 .search .universal-search-option"); + var i = -1; + + $.each(codes, function(maxAtomIndex, nextElement) + { + if ($(nextElement).hasClass("selected")) + { + $(nextElement).removeClass("selected"); + i = maxAtomIndex; + } + }); + + i = i + (event.which === 38 ? codes.length - 1 : 1); + i = i % codes.length; + + $(codes[i]).addClass("selected"); + } + + var expectedFloor = 1359; + var viewportCenter = 1480; + var actualCeil = $(window).width(); + + var satisfiesLowerLimit = actualCeil >= expectedFloor; + var item = $(".header-2014 .search"); + var fakeInputElement = $(".header-2014 .search input"); + var mainHeader = $(".nav-container"); + + if (!mainHeader.hasClass("no-gutter-ads") && actualCeil < viewportCenter) + { + satisfiesLowerLimit = false; + } + + if (satisfiesLowerLimit) + { + mainHeader.addClass("nav-open-static"); + } + + if ($("#navigation").length == 0) + { + $("#navContent").css( + { + "margin-left" : "0px", + width : "100%" + }); + + $(".nav-container .nav-icon").css("display", "none"); + $(".header-2014 .logo").css("margin", "3px 0 0 45px"); + $("#navContent").addClass("nav-no-left"); + } + + $(".nav-icon").on("click", function() + { + if (mainHeader.hasClass("nav-open-static")) + { + mainHeader.removeClass("nav-open-static"); + } + else + { + mainHeader.toggleClass("nav-open"); + } + }); + + $(".header-2014 .search-icon").on("click", function(event) + { + var c; + var divel; + var s; + event.stopPropagation(); + c = fakeInputElement.val(); + + if (c.length > 2 && item.hasClass("universal-search-open")) + { + divel = $(".header-2014 .search .universal-search-option.selected"); + s = divel.data("searchurl"); + window.location = s + encodeURIComponent(c); + } + else + { + callback(item); + fakeInputElement.focus(); + } + }); + + $(window).resize(function() + { + var reconnectTryTimes = $(window).width(); + var interestingPoint = expectedFloor; + + if (reconnectTryTimes >= interestingPoint && !(mainHeader.hasClass("nav-open") || mainHeader.hasClass("nav-open-static"))) + { + mainHeader.addClass("nav-open"); + } + + item.toggleClass("universal-search-open", false); + callback(); + }); + + $(".search input").on("keydown", function(event) + { + var expRecords = $(this).val(); + if ((event.which === 9 || event.which === 38 || event.which === 40) && expRecords.length > 0) + { + event.stopPropagation(); + event.preventDefault(); + scoringValidation(event); + } + }); + + $(".search input").on("keyup", function(event) + { + var param = $(this).val(); + var divel; + var url; + if (event.which === 13) + { + event.stopPropagation(); + event.preventDefault(); + divel = $(".header-2014 .search .universal-search-option.selected"); + url = divel.data("searchurl"); + if (param.length > 2) + { + window.location = url + encodeURIComponent(param); + } + } + else + { + if (param.length > 0) + { + item.toggleClass("universal-search-open", true); + $(".header-2014 .search .universal-search-dropdown .universal-search-string").text('"' + param + '"'); + } + else + { + item.toggleClass("universal-search-open", false); + } + } + }); + + $(".header-2014 .search .universal-search-option").on("click touchstart", function(event) + { + var hash; + var u; + event.stopPropagation(); + hash = fakeInputElement.val(); + if (hash.length > 2) + { + u = $(this).data("searchurl"); + window.location = u + encodeURIComponent(hash); + } + }); + + $(".header-2014 .search .universal-search-option").on("mouseover", function() + { + $(".header-2014 .search .universal-search-option").removeClass("selected"); + $(this).addClass("selected"); + }); + + $(".search input").on("focus", function() + { + var expRecords = fakeInputElement.val(); + if (expRecords.length > 0) + { + item.addClass("universal-search-open"); + } + }); + + $(".search input").on("click", function(event) + { + event.stopPropagation(); + }); + + $(".nav-content, .navigation, .header-2014").on("click", function() + { + callback(undefined); + item.toggleClass("universal-search-open", false); + }); + + $(".dropdown-hover a")[0].click(); +}); diff --git a/js/polygon/catalog.js b/js/polygon/catalog.js index f997764..d5b0081 100644 --- a/js/polygon/catalog.js +++ b/js/polygon/catalog.js @@ -48,6 +48,8 @@ $(".pagination .page").on("focusout keypress", this, function(event) polygon.catalog.PageNumber = $(this).val(); polygon.catalog.show(); }); +$(function(){ polygon.registerHandlers("catalog"); }); + /*$(".items .item").hover( function(){ $(this).find(".details").removeClass("d-none"); }, function(){ $(this).find(".details").addClass("d-none"); });*/ \ No newline at end of file diff --git a/js/polygon/character.js b/js/polygon/character.js new file mode 100644 index 0000000..81e680d --- /dev/null +++ b/js/polygon/character.js @@ -0,0 +1,108 @@ +polygon.character = +{ + type: 8, + wardrobe_page: 1, + wearing_page: 1, + + get_wardrobe: function(page, type) + { + if(page == undefined) page = polygon.character.wardrobe_page; + else polygon.character.wardrobe_page = page; + + if(type == null) type = polygon.character.type; + else polygon.character.type = type; + + // $(".wardrobe-container .items").empty(); + // $(".wardrobe-container .loading").removeClass("d-none"); + $(".wardrobe-container .no-items").addClass("d-none"); + //z$(".wardrobe-container .pagination").addClass("d-none"); + + $.post('/api/account/character/get-assets', {type: type, page: page, wearing: false}, function(data) + { + $(".wardrobe-container .items").empty(); + $(".wardrobe-container .loading").addClass("d-none"); + polygon.pagination.handle("wardrobe", page, data.pages); + if(data.items == undefined) return $(".wardrobe-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("wardrobe", data.items); + }); + }, + + get_wearing: function(page) + { + if(page == undefined) page = this.wearing_page; + else this.wearing_page = page; + + $.post('/api/account/character/get-assets', {page: page, wearing: true}, function(data) + { + $(".wearing-container .loading").addClass("d-none"); + $(".wearing-container .items").empty(); + $(".wearing-container .no-items").addClass("d-none"); + + polygon.pagination.handle("wearing", page, data.pages); + if(data.items == undefined) return $(".wearing-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("wearing", data.items); + }); + }, + + wait_for_render: function() + { + $.get("/thumbs/rawavatar", { UserID: polygon.user.id, x: 352, y: 352 }, function(data) + { + if (data == "PENDING") window.setTimeout(function() { polygon.character.wait_for_render(); }, 1500); + else window.setTimeout(function() { $('.avatar').attr('src', data); }, 1000); //this delay is put here because the avatar was often being displayed before the new one was written + }); + }, + + render_avatar: function() + { + $('.avatar').attr('src', 'https://i.stack.imgur.com/kOnzy.gif'); + $.post('/api/account/character/request-render', function(){ polygon.character.wait_for_render(); }); + }, + + toggle_wear: function() + { + var assetID = $(this).attr("data-asset-id"); + $.post('/api/account/character/toggle-wear', {assetID: assetID}, function(data) + { + if(data.success) { polygon.character.get_wardrobe(); polygon.character.get_wearing(); polygon.character.render_avatar(); } + else { polygon.buildModal({ header: "Error", body: data.message, buttons: [{'class':'btn btn-primary px-4', 'dismiss':true, 'text':'OK'}]}); } + }); + }, + + show_color_panel: function() + { + var body_part = $(this).attr("data-body-part"); + polygon.buildModal({ + header: "Choose a "+body_part+" Color", + body: $(".ColorPickerModalTemplate").clone().html(function(_, html){ return html.replace("$body_part", body_part); }).html(), + buttons: [] + }); + }, + + pick_color: function() + { + var body_part = $(this).closest(".ColorPickerContainer").attr("data-body-part"); + $('.modal').modal('hide'); + $(".ColorChooserRegion[data-body-part='"+body_part+"']").css("background-color", $(this).css("background-color")); + $.post("/api/account/character/paint-body", { BodyPart: body_part, Color: $(this).css("background-color")}, function(data) + { + if(data.success) polygon.character.render_avatar(); + else polygon.buildModal({ header: "Error", body: data.message, buttons: [{'class':'btn btn-primary px-4', 'dismiss':true, 'text':'OK'}]}); + }); + } +} + +$(".wardrobe-container .AttireCategorySelector").click(function(){ polygon.character.get_wardrobe(1, $(this).attr("data-asset-type")); }); +$("body").on('click',".toggle-wear", polygon.character.toggle_wear); + +$(".ColorChooserRegion").click(polygon.character.show_color_panel); +$("body").on('click', ".ColorPickerItem", polygon.character.pick_color); + +$(function() +{ + polygon.pagination.register("wardrobe", polygon.character.get_wardrobe); + polygon.pagination.register("wearing", polygon.character.get_wearing); + + polygon.character.get_wardrobe(); + polygon.character.get_wearing(); +}); \ No newline at end of file diff --git a/js/polygon/core.js b/js/polygon/core.js new file mode 100644 index 0000000..7891a57 --- /dev/null +++ b/js/polygon/core.js @@ -0,0 +1,272 @@ +$.ajaxSetup({ headers: { 'x-polygon-csrf': $('meta[name="polygon-csrf"]').attr('content') } }); + +/* todo - dont be lazy and work on this +polygon.ajax = function(url, method, data, trusted, successCallback, errorCallback) +{ + var ajaxOptions = {type: method, data: data}; + + if(trusted) + { + ajaxOptions.url = window.location.origin + url; + ajaxOptions.headers: {'x-polygon-csrf': $('meta[name="polygon-csrf"]').attr('content')}; + } + else { ajaxOptions.url = url; } + + +}*/ + +polygon.button = +{ + busy: function(button) + { + $(button).attr("disabled", "disabled").find(".spinner-border").removeClass("d-none"); + }, + + active: function(button) + { + $(button).removeAttr("disabled").find(".spinner-border").addClass("d-none"); + } +}; + +polygon.insertAlert = function(options) +{ + var alertCode = ''; + if (options.alertClasses == undefined) options.alertClasses = ''; + + if (options.parentClasses) alertCode += '
    '; + alertCode += ''; + if (options.parentClasses) alertCode += '
    '; + + $(options.parent).append(alertCode); +} + +polygon.buildModal = function(options) +{ + if (options.options == undefined) options.options = "show"; + if (options.fade == undefined) $(".global.modal").addClass("fade"); + else if (!options.fade) $(".global.modal").removeClass("fade"); + var footer = $(".global.modal .modal-footer .mx-auto"); + $(".global.modal .modal-title").html(options.header); + $(".global.modal .modal-body").html(options.body); + + footer.empty(); + $.each(options.buttons, function(_, button) + { + var buttonCode = ''; + footer.append(buttonCode); + }); + + if (options.footer) footer.append('

    ' + options.footer + '

    '); + + $(".global.modal").modal(options.options); +}; + +polygon.populate = function(data, template, container, keepWrapper) +{ + if(keepWrapper == null) keepWrapper = true; + + $.each(data, function(_, item) + { + var templateCode = $(template).clone(); + templateCode.html(function(_, html) + { + for (let key in item) html = html.replace(new RegExp("\\$" + key, "g"), item[key]); + return html; + }); + + if (templateCode.find("img").attr("preload-src")) + templateCode.find("img").attr("src", templateCode.find("img").attr("preload-src")); + + if(!keepWrapper) templateCode = templateCode.contents().unwrap(); + + templateCode.appendTo(container); + }); +} + +polygon.populateRow = function(control, data) +{ + polygon.populate(data, "." + control + "-container .template .item", "." + control + "-container .items"); + polygon.registerHandlers(control); +} + +polygon.populateAccordion = function(control, data) +{ + polygon.populate(data, "." + control + "-container .accordion-template", "." + control + "-container .accordion", false); + polygon.registerHandlers(control); +} + +polygon.registerHandlers = function(control) +{ + if(control == undefined) + { + if($("[data-toggle='tooltip']").length) + $("[data-toggle='tooltip']").tooltip(); + + if($("input[data-toggle='toggle']").length) + $("input[data-toggle='toggle']").bootstrapToggle(); + } + else + { + if($("." + control + "-container [data-toggle='tooltip']").length) + $("." + control + "-container [data-toggle='tooltip']").tooltip(); + + if($("." + control + "-container input[data-toggle='toggle']").length) + $("." + control + "-container input[data-toggle='toggle']").bootstrapToggle(); + + if($("." + control + "-container .item .details").length) + $("." + control + "-container .item").hover(function(){ $(this).find(".details").removeClass("d-none"); }, function(){ $(this).find(".details").addClass("d-none"); }); + + $("." + control + "-container img").each(function() + { + if($(this).attr("preload-src") === undefined) return; + $(this).attr("src", $(this).attr("preload-src")); + $(this).removeAttr("preload-src"); + }); + + if($("." + control + "-container .accordion").length) + { + if($.ui == undefined) throw "Accordion widget detected for " + control + " applet but jQuery UI is not loaded"; + + if($("." + control + "-container .accordion").hasClass("ui-accordion")) + $("." + control + "-container .accordion").accordion("destroy"); + + $("." + control + "-container .accordion").accordion({ autoHeight: false, collapsible: true }); + } + } +} + +polygon.pagination = +{ + register: function(control, callback) + { + var pagination = "." + control + "-container .pagination"; + var page; + + if (!$(pagination).length) return; + + $(pagination + " .back").click(function() + { + callback(+$(pagination + " .page").val() - 1); + }); + $(pagination + " .next").click(function() + { + callback(+$(pagination + " .page").val() + 1); + }); + + $(pagination + " .page").on("focusout keypress", this, function(event) + { + page = $(this).val(); + + if (isNaN(page) || page < 1) page = 1; + + if (event.type == "keypress") + if (event.which == 13) $(this).blur(); else return; + + if (page == $(this).attr("data-last-page")) return; + $(this).attr("data-last-page", page); + callback(page); + }); + }, + + handle: function(control, page, pages) + { + var pagination = "." + control + "-container .pagination"; + + if (page > pages) page = pages; + if (isNaN(page) || page < 1) page = 1; + + if (!$(pagination).length) return; + if (pages <= 1 || pages == undefined) return $(pagination).addClass("d-none"); + + $(pagination).removeClass("d-none"); + $(pagination + " .pages").text(pages); + + if ($(pagination + " .page").prop("tagName") == "INPUT") $(pagination + " .page").val(page); + else $(pagination + " .page").text(page); + + if (page <= 1) $(pagination + " .back").attr("disabled", "disabled"); + else $(pagination + " .back").removeAttr("disabled"); + + if (page >= pages) $(pagination + " .next").attr("disabled", "disabled"); + else $(pagination + " .next").removeAttr("disabled"); + } +} + +polygon.appendination = +{ + register: function(object, threshold) + { + if(!$("."+object.control+"-container").length) return false; + + $("body").on("click", "."+object.control+"-container .show-more", function(){ object.load(true); }); + + $(window).scroll(function() + { + if(object.loading || object.reached_end) return; + if($(window).scrollTop() + $(window).height() < $(document).height() - threshold) return; + object.load(true); + }); + + $(function(){ object.load(false) }); + + return true; + }, + + handle: function(object, data) + { + if(object.page < data.pages) + { + $("."+object.control+"-container .show-more").removeClass("d-none"); + object.reached_end = false; + } + else + { + object.reached_end = true; + } + } +} + +toastr.options = +{ + "closeButton": false, + "debug": false, + "newestOnTop": false, + "progressBar": true, + "positionClass": "toast-top-right", + "preventDuplicates": false, + "onclick": null, + "showDuration": "300", + "hideDuration": "1000", + "timeOut": "10000", + "extendedTimeOut": "1000", + "showEasing": "swing", + "hideEasing": "linear", + "showMethod": "fadeIn", + "hideMethod": "fadeOut" +} + +$(function() +{ + polygon.registerHandlers(); + if (polygon.user.logged_in) + { + setInterval(function() + { + if (document.hidden) return; + $.post("/api/account/update-ping", function(data) + { + if (data.friendRequests) $(".friend-requests-indicator").text(data.friendRequests).removeClass("d-none"); + else $(".friend-requests-indicator").addClass("d-none"); + if (data.status == 401) window.location.reload(); + }); + }, 30000); + } +}); \ No newline at end of file diff --git a/js/polygon/games.js b/js/polygon/games.js index 3f1312a..f58f3bf 100644 --- a/js/polygon/games.js +++ b/js/polygon/games.js @@ -35,25 +35,50 @@ polygon.games = $(".placelauncher .modal-dialog a").text("Close"); }, - page: 1, - client: false, - load_servers: function(append, client) + join_server: function(serverID) { - if(append) polygon.games.page += 1; - else polygon.games.page = 1; + polygon.games.launch("Checking server status..."); + $.get('/api/games/serverlauncher', {serverID: serverID}, function(data) + { + if(data.success) + polygon.games.launch("Starting Project Polygon...", data.version, "launchmode:play+joinscripturl:"+data.joinScriptUrl); + else + polygon.games.error(data.message); + }); + }, - if(client) polygon.games.client = client; - else client = polygon.games.client; + delete_server: function(serverID) + { + $.post('/games/configure?ID='+serverID, {delete:true}, function(){ window.location = "/games"; }); + } +}; + +polygon.games.servers = +{ + page: 1, + reached_end: false, + loading: true, + control: "games", + client: false, + load: function(append, client) + { + if(append) polygon.games.servers.page += 1; + else polygon.games.page = 1; + + if(client) polygon.games.servers.client = client; + else client = polygon.games.servers.client; if(client == "All Versions") client = false; if(!client) { + $(".download-client").text("Select a version to download"); $(".download-client").addClass("disabled"); $(".download-client").removeAttr("href"); } else { + $(".download-client").text("Download "+client); $(".download-client").removeClass("disabled"); $(".download-client").attr("href", "https://setup"+client+".pizzaboxer.xyz/Polygon"+client+".exe"); } @@ -62,10 +87,13 @@ polygon.games = $(".games-container .no-items").addClass("d-none"); $(".games-container .show-more").addClass("d-none"); if(!append) $(".games-container .items").empty(); + + polygon.games.servers.loading = true; - $.post('/api/games/getServers', {client: client, page: polygon.games.page}, function(data) + $.post('/api/games/getServers', {client: client, page: polygon.games.servers.page}, function(data) { $(".games-container .loading").addClass("d-none"); + polygon.games.servers.loading = false; if(data.items == undefined) return $(".games-container .no-items").text(data.message).removeClass("d-none"); @@ -83,33 +111,17 @@ polygon.games = templateCode.appendTo(".games-container .items"); }); - if(data.pages > polygon.games.page) $(".games-container .show-more").removeClass("d-none"); + polygon.appendination.handle(polygon.games.servers, data); }); - }, - - join_server: function(serverID) - { - polygon.games.launch("Checking server status..."); - $.get('/api/games/serverlauncher', {serverID: serverID}, function(data) - { - if(data.success) - polygon.games.launch("Starting Project Polygon...", data.version, "launchmode:play+joinscripturl:"+data.joinScriptUrl); - else - polygon.games.error(data.message); - }); - }, - - delete_server: function(serverID) - { - $.post('/games/configure?ID='+serverID, {delete:true}, function(){ window.location = "/games"; }); } } if(window.location.pathname == "/games") { - $("select.version-selector").change(function(){ polygon.games.load_servers(false, $(this).val()); }); - $("body").on("click", ".games-container .show-more", function(){ polygon.games.load_servers(true); }); - $(function(){ polygon.games.load_servers(false); }); + $("select.version-selector").change(function(){ polygon.games.servers.load(false, $(this).val()); }); + $(function(){ polygon.appendination.register(polygon.games.servers, 1200); }); } + $("body").on("click", ".join-server", function(){ polygon.games.join_server($(this).attr("data-server-id")); }); -$(".delete-server").click(function(){ polygon.games.delete_server($(this).attr("data-server-id")); }); \ No newline at end of file +$(".delete-server").click(function(){ polygon.games.delete_server($(this).attr("data-server-id")); }); + diff --git a/js/polygon/groups.js b/js/polygon/groups.js new file mode 100644 index 0000000..04fd689 --- /dev/null +++ b/js/polygon/groups.js @@ -0,0 +1,715 @@ +polygon.groups = +{ + allies_page: 1, + display_allies: function(page) + { + if(!$(".allies-container").length) return false; + + if(page == undefined) page = this.allies_page; + else this.allies_page = page; + + $(".allies-container .items").empty(); + $(".allies-container .no-items").addClass("d-none"); + $(".allies-container .pagination").addClass("d-none"); + $(".allies-container .loading").removeClass("d-none"); + + $.post('/api/groups/get-related', { GroupID: $(".app").attr("data-group-id"), Type: "Allies", Page: page }, function(data) + { + $(".allies-container .loading").addClass("d-none"); + + polygon.pagination.handle("allies", page, data.pages); + if(!data.success) return polygon.insertAlert({text: "An error occurred while fetching group allies", parent: ".allies-container"}); + if(data.items == undefined) return $(".allies-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("allies", data.items); + + $(".allies-tab-item").removeClass("d-none"); + }); + + return true; + }, + + enemies_page: 1, + display_enemies: function(page) + { + if(!$(".enemies-container").length) return false; + + if(page == undefined) page = this.allies_page; + else this.allies_page = page; + + $(".enemies-container .items").empty(); + $(".enemies-container .no-items").addClass("d-none"); + $(".enemies-container .pagination").addClass("d-none"); + $(".enemies-container .loading").removeClass("d-none"); + + $.post('/api/groups/get-related', { GroupID: $(".app").attr("data-group-id"), Type: "Enemies", Page: page }, function(data) + { + $(".enemies-container .loading").addClass("d-none"); + + polygon.pagination.handle("enemies", page, data.pages); + if(!data.success) return polygon.insertAlert({text: "An error occurred while fetching group enemies", parent: ".enemies-container"}); + if(data.items == undefined) return $(".enemies-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("enemies", data.items); + + $(".enemies-tab-item").removeClass("d-none"); + }); + + return true; + }, + + members_rank: 1, + members_page: 1, + display_members: function(page, rank) + { + if(!$(".members-container").length) return false; + + if(page == undefined) page = this.members_page; + else this.members_page = page; + + if(rank == undefined) rank = $("select#ranks").val(); + this.members_rank = rank; + + $(".members-container .items").empty(); + $(".members-container .no-items").addClass("d-none"); + $(".members-container .pagination").addClass("d-none"); + $(".members-container .loading").removeClass("d-none"); + + $.post('/api/groups/get-members', {GroupID: $(".app").attr("data-group-id"), RankLevel: rank, Page: page}, function(data) + { + $(".members-container .loading").addClass("d-none"); + + polygon.pagination.handle("members", page, data.pages); + if(!data.success) return polygon.insertAlert({text: "An error occurred while fetching group members", parent: ".members-container"}); + + var select = $("select#ranks option[value=\"" + rank + "\"]"); + if(select.attr("data-loaded") == "false") + { + if(data.count == undefined) select.text(select.text() + " (0)"); + else select.text(select.text() + " (" + data.count + ")"); + + select.attr("data-loaded", "true") + } + + if(data.items == undefined) return $(".members-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("members", data.items); + }); + + return true; + }, + + wall_page: 1, + display_wall: function(page) + { + if(!$(".wall-container").length) return false; + + if(page == undefined) page = this.wall_page; + else this.wall_page = page; + + $(".wall-container .items").empty(); + $(".wall-container .no-items").addClass("d-none"); + $(".wall-container .pagination").addClass("d-none"); + $(".wall-container .loading").removeClass("d-none"); + + $.post('/api/groups/get-wall', {GroupID: $(".app").attr("data-group-id"), Page: page}, function(data) + { + $(".wall-container .loading").addClass("d-none"); + + polygon.pagination.handle("wall", page, data.pages); + if(!data.success) return polygon.insertAlert({text: "An error occurred while fetching group wall", parent: ".wall-container"}); + if(data.items == undefined) return $(".wall-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("wall", data.items); + }); + + return true; + }, + + post_wall: function() + { + $(".post-wall-error").text(""); + polygon.button.busy(".post-wall"); + + $.post('/api/groups/post-wall', {GroupID: $(".app").attr("data-group-id"), Content: $(".post-wall-input").val()}, function(data) + { + polygon.button.active(".post-wall"); + if(data.success) polygon.groups.display_wall(1); + else $(".post-wall-error").text(data.message); + }); + }, + + delete_wall_post: function(event) + { + event.preventDefault(); + + $.post('/api/groups/delete-wall-post', {GroupID: $(".app").attr("data-group-id"), PostID: $(event.target).attr("data-post-id")}, function(data) + { + if(data.success) polygon.groups.display_wall(); + else polygon.buildModal({ header: "Error", body: data.message, buttons: [{'class':'btn btn-primary px-4', 'dismiss':true, 'text':'OK'}]}); + }); + }, + + post_shout: function() + { + $(".post-shout-error").text(""); + polygon.button.busy(".post-shout"); + + $.post('/api/groups/post-shout', {GroupID: $(".app").attr("data-group-id"), Content: $(".post-shout-input").val()}, function(data) + { + polygon.button.active(".post-shout"); + if(data.success) window.location.reload(); + else $(".post-shout-error").text(data.message); + }); + }, + + join_group: function() + { + if(!polygon.user.logged_in) + return window.location = "/login?ReturnUrl="+encodeURI(window.location.pathname+window.location.search); + + $.post('/api/groups/join-group', {GroupID: $(".app").attr("data-group-id")}, function(data) + { + if(data.success) window.location.reload(); + else polygon.buildModal({ header: "Error", body: data.message, buttons: [{'class':'btn btn-primary px-4', 'dismiss':true, 'text':'OK'}]}); + }); + }, + + leave_group_prompt: function() + { + polygon.buildModal({ + header: "Leave Group", + body: 'Are you sure you want to leave this group?', + buttons: [{class:'btn btn-primary px-4 leave-group', text:'Yes'}, {class:'btn btn-secondary px-4', dismiss:true, text:'No'}] + }); + }, + + leave_group: function() + { + $.post('/api/groups/leave-group', {GroupID: $(".app").attr("data-group-id")}, function(data) + { + if(data.success) window.location.reload(); + else polygon.buildModal({ header: "Error", body: data.message, buttons: [{'class':'btn btn-primary px-4', 'dismiss':true, 'text':'OK'}]}); + }); + } +} + +polygon.groups.admin = +{ + roles: [], + role_permissions: + [ + { + "Title": "Posts", + "Permissions": + [ + {"Title": "View group wall", "Name": "CanViewGroupWall", "Value": false}, + {"Title": "View group status", "Name": "CanViewGroupStatus", "Value": false}, + {"Title": "Post on group wall", "Name": "CanPostOnGroupWall", "Value": false}, + {"Title": "Post group status", "Name": "CanPostGroupStatus", "Value": false}, + {"Title": "Delete group wall posts", "Name": "CanDeleteGroupWallPosts", "Value": false} + ] + }, + { + "Title": "Members", + "Permissions": + [ + {"Title": "Accept join requests", "Name": "CanAcceptJoinRequests", "Value": false}, + {"Title": "Kick lower-ranked members", "Name": "CanKickLowerRankedMembers", "Value": false}, + {"Title": "Manage lower-ranked member roles", "Name": "CanRoleLowerRankedMembers", "Value": false}, + {"Title": "Manage allies and enemies", "Name": "CanManageRelationships", "Value": false} + ] + }, + { + "Title": "Assets", + "Permissions": + [ + {"Title": "Create group items", "Name": "CanCreateAssets", "Value": false}, + {"Title": "Configure group items", "Name": "CanConfigureAssets", "Value": false}, + {"Title": "Spend group funds", "Name": "CanSpendFunds", "Value": false}, + {"Title": "Create and edit group games", "Name": "CanManageGames", "Value": false} + ] + }, + { + "Title": "Miscellaneous", + "Permissions": + [ + {"Title": "Manage group admin", "Name": "CanManageGroupAdmin", "Value": false}, + {"Title": "View audit log", "Name": "CanViewAuditLog", "Value": false} + ] + } + ], + roles_cap: 10, + get_roles: function() + { + $.post('/api/groups/admin/get-roles', {GroupID: $(".app").attr("data-group-id")}, function(data) + { + if(!data.success) return options.on_error(); + + $(".template select#MemberRanks").empty(); + $.each(data.items, function(index, item) + { + data.items[index].ID = Math.round(Math.random()*10000); // not the actual role id, just a unique identifier for when we create/remove roles + if(item.Rank == 0 || item.Rank == 255) return; + $(".template select#MemberRanks").append(""); + }); + + polygon.groups.admin.roles = data.items; + polygon.groups.admin.display_roles(); + polygon.groups.admin.display_members(); + }); + }, + + display_roles: function() + { + if(!$(".roles-container").length) return false; + + if(polygon.groups.admin.roles.length >= polygon.groups.admin.roles_cap) $(".roles-create").attr("disabled", "disabled"); + else $(".roles-create").removeAttr("disabled"); + + $(".roles-container tbody").empty("") + $(".roles-container .loading").addClass("d-none"); + + // owner rank must be first + if(polygon.groups.admin.roles[0].Rank != 255) + polygon.groups.admin.roles.reverse() + + + $.each(polygon.groups.admin.roles, function(index, item) + { + var row = ""; + + row += ""; + + if(item.Rank == 0) + { + row += ""; + row += ""; + } + else + { + row += ""; + row += ""; + } + + if(item.Rank == 0 || item.Rank == 255) + { + row += ""; + } + else + { + row += ""; + } + + row += ""; + if(item.Rank != 255) + { + row += ""; + } + if(item.Rank != 0 && item.Rank != 255 && polygon.groups.admin.roles.length > 3) + { + row += ""; + } + row += ""; + + row += ""; + + $(row).appendTo(".roles-container tbody"); + }); + + return true; + }, + + update_roles: function() + { + polygon.button.busy(".roles-save"); + + $(".roles-container tbody tr").each(function(_, item) + { + var ID = +$(item).attr("data-role-identifier"); + var Role = polygon.groups.admin.roles.find(Role => Role.ID == ID); + + Role.Name = $(item).find(".role-name").val(); + Role.Description = $(item).find(".role-description").val(); + Role.Rank = +$(item).find(".role-rank").val(); + }); + + $.post('/api/groups/admin/update-roles', {GroupID: $(".app").attr("data-group-id"), Roles: JSON.stringify(polygon.groups.admin.roles)}, function(data) + { + polygon.button.active(".roles-save"); + + if(!data.success) return toastr["error"](data.message); + + toastr["success"](data.message); + polygon.groups.admin.get_roles({reload: true}); + }); + }, + + add_role: function() + { + if(polygon.groups.admin.roles.length >= polygon.groups.admin.roles_cap) + return toastr["error"]("You can only have a maximum of "+polygon.groups.admin.roles_cap+" roles"); + + var GuestPermissions = polygon.groups.admin.roles.find(Role => Role.Rank == 0).Permissions; + + // basically just get the minimum available rank + var DefaultRank = 1; + var DefaultIndex = polygon.groups.admin.roles.length-1; + var DefaultRankCalculated = false; + + while (!DefaultRankCalculated) + { + if(polygon.groups.admin.roles.find(Role => Role.Rank == DefaultRank)) + { + DefaultRank++; + DefaultIndex--; + } + else + { + DefaultRankCalculated = true; + } + } + + polygon.groups.admin.roles.splice(DefaultIndex, 0, + { + ID: Math.round(Math.random()*10000), + Name: "New Role", + Description: "Describe your role!", + Rank: DefaultRank, + Permissions: GuestPermissions + }); + + polygon.groups.admin.display_roles(); + }, + + delete_role: function(RoleID) + { + var RoleIndex = polygon.groups.admin.roles.findIndex(Role => Role.ID == +RoleID); + if(RoleIndex == -1) return; + + polygon.groups.admin.roles.splice(RoleIndex, 1); + polygon.groups.admin.display_roles(); + }, + + delete_role_prompt: function(RoleID) + { + polygon.buildModal({ + header: "Delete Role", + body: 'Are you sure you want to delete this role?
    Deleting this role will move all members assigned with this role to the lowest ranked role.', + buttons: + [ + { + class: 'btn btn-primary px-4 role-delete', + text: 'Yes', + dismiss: true, + attributes: [{attr: 'data-role-identifier', val: RoleID}] + }, + { + class: 'btn btn-secondary px-4', + text: 'No', + dismiss: true + } + ] + }); + }, + + configure_role_permissions: function(RoleID) + { + var Role = polygon.groups.admin.roles.find(Role => Role.ID == +RoleID); + if(!Role) return; + + var Body = ""; + Body += "
    "; + Body += "
    "; + + $.each(polygon.groups.admin.role_permissions, function(_, Category) + { + Body += ""; + Body += "
    "; + + $.each(Category.Permissions, function(_, Permission) + { + Permission.Value = Role.Permissions[Permission.Name]; + + Body += "
    "; + + Body += ""+ Permission.Title +""; + Body += "
    "; + }); + + Body += "
    "; + }); + + Body += "
    "; + Body += "
    "; + + polygon.buildModal({ + header: "Change Permissions For " + Role.Name, + body: Body, + buttons: + [ + { + class: 'btn btn-success role-save-permissions px-4', + text: 'Save', + dismiss: true, + attributes: [{attr: 'data-role-identifier', val: RoleID}] + }, + { + class: 'btn btn-secondary px-4', + text: 'Cancel', + dismiss: true + } + ] + }); + + // if these are called too early, they wont work correctly + setTimeout(function() + { + $(".modal-body .loading").addClass("d-none"); + $(".modal-body .role-permissions-container").removeClass("d-none"); + polygon.registerHandlers("role-permissions"); + }, 500); + }, + + change_role_permission: function(PermissionToChange, Value) + { + // having to do two array finds is kinda wasteful. maybe find a better way to do this? + polygon.groups.admin.role_permissions.find(Category => Category.Permissions.find(Permission => Permission.Name == PermissionToChange)) + .Permissions.find(Permission => Permission.Name == PermissionToChange) + .Value = Value; + }, + + save_role_permissions: function(RoleID) + { + var StagingPermissions = polygon.groups.admin.roles.find(Role => Role.ID == +RoleID).Permissions; + + $.each(polygon.groups.admin.role_permissions, function(_, Category) + { + $.each(Category.Permissions, function(_, Permission) + { + StagingPermissions[Permission.Name] = Permission.Value; + }); + }); + + polygon.groups.admin.roles.find(Role => Role.ID == +RoleID).Permissions = StagingPermissions; + }, + + members_page: 1, + display_members: function(page) + { + if(!$(".members-container").length) return false; + + if(page == undefined) page = this.members_page; + else this.members_page = page; + + $(".members-container .items").empty(); + $(".members-container .no-items").addClass("d-none"); + $(".members-container .pagination").addClass("d-none"); + $(".members-container .loading").removeClass("d-none"); + + $.post('/api/groups/admin/get-members', {GroupID: $(".app").attr("data-group-id"), Page: page}, function(data) + { + $(".members-container .loading").addClass("d-none"); + + polygon.pagination.handle("members", page, data.pages); + if(!data.success) return polygon.insertAlert({text:"An error occurred while fetching group members", parent:".members-container"}); + + if(data.items == undefined) return $(".members-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("members", data.items); + + $.each(data.items, function(_, item) + { + $("select#MemberRanks[data-user-id=\"" + item.UserID + "\"] option[value=\"" + item.RoleLevel + "\"]").prop("selected", "selected"); + }); + }); + + return true; + }, + + update_member: function(options = {}) + { + options.GroupID = $(".app").attr("data-group-id"); + + $.post('/api/groups/admin/update-member', options, function(data) + { + toastr[data.success ? "success" : "error"](data.message); + }); + }, + + update_member_rank: function(event) + { + var option = $(event.target); + polygon.groups.admin.update_member({UserID: option.attr("data-user-id"), RoleLevel: option.val()}); + }, + + pending_allies_page: 1, + display_pending_allies: function(page) + { + if(!$(".pending-allies-container").length) return false; + + if(page == undefined) page = this.pending_allies_page; + else this.pending_allies_page = page; + + $(".pending-allies-container .items").empty(); + $(".pending-allies-container .no-items").addClass("d-none"); + $(".pending-allies-container .pagination").addClass("d-none"); + $(".pending-allies-container .loading").removeClass("d-none"); + + $.post('/api/groups/get-related', { GroupID: $(".app").attr("data-group-id"), Type: "Pending Allies", Page: page }, function(data) + { + $(".pending-allies-container .loading").addClass("d-none"); + + polygon.pagination.handle("pending-allies", page, data.pages); + if(!data.success) return polygon.insertAlert({text: "An error occurred while fetching group allies", parent: ".pending.allies-container"}); + if(data.items == undefined) $(".pending-allies-container").addClass("d-none"); else $(".pending-allies-container").removeClass("d-none"); + polygon.populateRow("pending-allies", data.items); + }); + + return true; + }, + + request_relationship: function() + { + var Button = this; + var Type = $(Button).attr("data-type"); + + if(!$("input.request-" + Type).length) return; + + var GroupName = $("input.request-" + Type).val(); + + polygon.button.busy(Button); + + $.post('/api/groups/admin/request-relationship', { GroupID: $(".app").attr("data-group-id"), Recipient: GroupName, Type: Type }, function(data) + { + polygon.button.active(Button); + + toastr[data.success ? "success" : "error"](data.message); + if(data.success) + { + polygon.groups.display_allies(); + polygon.groups.display_enemies(); + polygon.groups.admin.display_pending_allies(); + } + }); + }, + + update_relationship: function() + { + var Button = this; + var GroupID = parseInt($(Button).parents(".card").attr("data-group-id")); + var Action = $(Button).attr("data-action"); + + polygon.button.busy(Button); + + $.post('/api/groups/admin/update-relationship', { GroupID: $(".app").attr("data-group-id"), Recipient: GroupID, Action: Action }, function(data) + { + polygon.button.active(Button); + + toastr[data.success ? "success" : "error"](data.message); + if(data.success) + { + polygon.groups.display_allies(); + polygon.groups.display_enemies(); + polygon.groups.admin.display_pending_allies(); + } + }); + } +}; + +polygon.groups.audit = +{ + page: 1, + reached_end: false, + loading: true, + control: "audit", + filter: "All Actions", + load: function(append, filter) + { + if(!$(".audit-container").length) return false; + + if(filter == undefined) filter = polygon.groups.audit.filter; + else polygon.groups.audit.filter = filter; + + if(append) polygon.groups.audit.page += 1; + else polygon.groups.audit.page = 1; + + $(".audit-container .loading").removeClass("d-none"); + $(".audit-container .no-items").addClass("d-none"); + $(".audit-container .show-more").addClass("d-none"); + if(!append) $("tbody").empty(); + + polygon.groups.audit.loading = true; + + $.post('/api/groups/get-audit', {GroupID: $(".app").attr("data-group-id"), Filter: polygon.groups.audit.filter, Page: polygon.groups.audit.page}, function(data) + { + $(".audit-container .loading").addClass("d-none"); + polygon.groups.audit.loading = false; + + if(data.items == undefined) return $(".audit-container .no-items").text(data.message).removeClass("d-none"); + + $.each(data.items, function(_, Item) + { + $("\ + \ + "+Item.Date+"\ + "+Item.UserName+"\ + "+Item.Rank+"\ + "+Item.Description+"\ + \ + ").appendTo(".audit-container tbody"); + }); + + polygon.appendination.handle(polygon.groups.audit, data); + }); + + return true; + } +}; + +$(function() +{ + if(window.location.pathname == "/my/groupadmin") + { + polygon.groups.admin.get_roles(); + } + else + { + if(polygon.groups.display_members()) polygon.pagination.register("members", polygon.groups.display_members); + } + + if(polygon.groups.display_wall()) polygon.pagination.register("wall", polygon.groups.display_wall); + if(polygon.groups.display_allies()) polygon.pagination.register("allies", polygon.groups.display_allies); + if(polygon.groups.display_enemies()) polygon.pagination.register("enemies", polygon.groups.display_enemies); + + if(polygon.groups.admin.display_pending_allies()) polygon.pagination.register("pending-allies", polygon.groups.admin.display_pending_allies); + + polygon.appendination.register(polygon.groups.audit, 300); +}); + +$(".post-shout").click(polygon.groups.post_shout); +$(".post-wall").click(polygon.groups.post_wall); +$("body").on("click", ".delete-wall-post", polygon.groups.delete_wall_post); + +$(".join-group").click(polygon.groups.join_group); +$(".leave-group-prompt").click(polygon.groups.leave_group_prompt); +$("body").on("click", ".leave-group", polygon.groups.leave_group); + +$("select#ranks").change(function(){ polygon.groups.display_members(1, $(this).val()); }); + + +$("body").on("change", "select#MemberRanks", polygon.groups.admin.update_member_rank); + +$(".roles-create").click(polygon.groups.admin.add_role); +$(".roles-save").click(polygon.groups.admin.update_roles); +$("body").on("click", ".role-delete-prompt", function(){ polygon.groups.admin.delete_role_prompt($(this).parents("tr").attr("data-role-identifier")); }); +$("body").on("click", ".role-delete", function(){ polygon.groups.admin.delete_role($(this).attr("data-role-identifier")); }); + +$("body").on("click", ".role-edit-permissions", function(){ polygon.groups.admin.configure_role_permissions($(this).parents("tr").attr("data-role-identifier")); }); +$("body").on("change", ".role-change-permission", function(){ polygon.groups.admin.change_role_permission($(this).attr("data-permission"), $(this).is(':checked')); }); +$("body").on("click", ".role-save-permissions", function(){ polygon.groups.admin.save_role_permissions($(this).attr("data-role-identifier")); }); + +$("body").on("click", ".request-relationship", polygon.groups.admin.request_relationship); +$("body").on("click", ".update-relationship", polygon.groups.admin.update_relationship); + + +$(".audit-container .audit-filter-action").change(function(){ polygon.groups.audit.load(null, $(this).val()); }); \ No newline at end of file diff --git a/js/polygon/home.js b/js/polygon/home.js new file mode 100644 index 0000000..e9c4c3b --- /dev/null +++ b/js/polygon/home.js @@ -0,0 +1,54 @@ +polygon.home = +{ + getFeed: function() + { + $.ajax({url: "/api/account/get-feed", type: "POST", success: function(data) + { + $(".feed-container .items").empty(); + $(".newsfeed-container .items").empty(); + + $(".feed-container .loading").addClass("d-none"); + if(!data.success) return polygon.insertAlert({text:"An error occurred while fetching your feed", parent:".my-feed", parentClasses:"divider-top py-2"}); + polygon.populateRow("feed", data.feed); + + $(".newsfeed-container .loading").addClass("d-none"); + if(!data.news.length) return $(".polygon-news").hide(250); + polygon.populateRow("newsfeed", data.news); + $(".polygon-news").show(250); + }}); + }, + + getRecentlyPlayed: function() + { + $.post('/api/account/get-recentlyplayed', function(data) + { + $(".recently-played-container .items").empty(); + $(".recently-played-container .loading").addClass("d-none"); + $(".recently-played-container .no-items").addClass("d-none"); + + if(!data.success) return polygon.insertAlert({text:"An error occurred while fetching your recently played games", parent:".recently-played-container", parentClasses:"p-2"}); + if(data.items == undefined || !data.items.length) return $(".recently-played-container .no-items").removeClass("d-none"); + polygon.populateRow("recently-played", data.items); + }); + }, + + loadHomepage: function() + { + polygon.home.getFeed(); + polygon.home.getRecentlyPlayed(); + } +}; + +$('.btn-update-status').click(function() +{ + $(this).attr("disabled", "disabled").find("span").show(); + $.post('/api/account/update-status', {"status":$("#status").val()}, function(data) + { + $('.btn-update-status').removeAttr("disabled").find("span").hide(); + if(data.success) polygon.home.getFeed(); + else toastr["error"](data.message); + }); +}); + +$(polygon.home.loadHomepage); +setInterval(function(){ if(document.hidden) return; polygon.home.loadHomepage(); polygon.friends.displayFriends(); }, 60000); \ No newline at end of file diff --git a/js/polygon/inventory.js b/js/polygon/inventory.js index 1b545ba..3a6d61c 100644 --- a/js/polygon/inventory.js +++ b/js/polygon/inventory.js @@ -1,7 +1,7 @@ polygon.inventory = { type: 8, - display: function(type, page) + display: function(page, type) { if(type == null) type = polygon.inventory.type; else polygon.inventory.type = type; @@ -13,22 +13,20 @@ polygon.inventory = $(".inventory-container .pagination").addClass("d-none"); $(".inventory-container .loading").removeClass("d-none"); - $.post('/api/users/getInventory', {userId: $(".app").attr("data-user-id"), type: type, page: page}, function(data) + $.post('/api/users/get-inventory', { userId: $(".app").attr("data-user-id"), type: type, page: page }, function(data) { $(".inventory-container .loading").addClass("d-none"); - //$(".inventory-container .items").empty(); - //$(".inventory-container .no-items").addClass("d-none"); polygon.pagination.handle("inventory", page, data.pages); - if(data.assets == undefined) return $(".inventory-container .no-items").text(data.message).removeClass("d-none"); - polygon.populate(data.assets, ".inventory-template .item", ".inventory-container .items"); + if(data.items == undefined) return $(".inventory-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("inventory", data.items); }); } } -$(".inventory-container .selector").click(function(){ polygon.inventory.display($(this).attr("data-asset-type")); }); +$(".inventory-container .selector").click(function(){ polygon.inventory.display(null, $(this).attr("data-asset-type")); }); $(function() { - polygon.pagination.register("inventory", function(page){ polygon.inventory.display(null, page); }); + polygon.pagination.register("inventory", polygon.inventory.display); polygon.inventory.display(); }); \ No newline at end of file diff --git a/js/polygon/money.js b/js/polygon/money.js new file mode 100644 index 0000000..0fc5152 --- /dev/null +++ b/js/polygon/money.js @@ -0,0 +1,49 @@ +polygon.money = {}; + +polygon.money.transactions = +{ + page: 1, + reached_end: false, + loading: true, + control: "transactions", + type: "Purchases", + load: function(append, type) + { + if(type == undefined) type = polygon.money.transactions.type; + else polygon.money.transactions.type = type; + + if(append) polygon.money.transactions.page += 1; + else polygon.money.transactions.page = 1; + + $(".transactions-container .loading").removeClass("d-none"); + $(".transactions-container .show-more").addClass("d-none"); + if(!append) $("tbody").empty(); + + polygon.money.transactions.loading = true; + + $.post('/api/account/get-transactions', {type: type, page: polygon.money.transactions.page}, function(data) + { + $(".transactions-container .loading").addClass("d-none"); + polygon.money.transactions.loading = false; + + if(data.transactions == undefined) return $(".transactions-container .no-items").text(data.message).removeClass("d-none"); + + $.each(data.transactions, function(_, transaction) + { + $('\ + '+transaction.date+'\ + '+transaction.member_name+'\ + '+transaction.type+' '+transaction.asset_name+'\ + '+transaction.amount+'\ + ').appendTo(".transactions-container tbody"); + }); + + polygon.appendination.handle(polygon.money.transactions, data); + }); + } +}; + + +$(".transactions-container #transactionType").change(function(){ polygon.money.transactions.load(false, $(this).val()); }); + +$(function(){ polygon.appendination.register(polygon.money.transactions, 300); }); \ No newline at end of file diff --git a/js/polygon/profile.js b/js/polygon/profile.js new file mode 100644 index 0000000..d7c161d --- /dev/null +++ b/js/polygon/profile.js @@ -0,0 +1,88 @@ +polygon.profile = {}; + +polygon.profile.games = +{ + page: 1, + display: function(page) + { + if(page == undefined) page = this.page; + else this.page = page; + + $(".games-container .accordion").empty(); + $(".games-container .no-items").addClass("d-none"); + $(".games-container .pagination").addClass("d-none"); + $(".games-container .loading").removeClass("d-none"); + + $.post("/api/games/getServers", {creator: $(".app").attr("data-user-id"), page: page}, function(data) + { + $(".games-container .loading").addClass("d-none"); + + polygon.pagination.handle("games", page, data.pages); + if(data.items == undefined) return $(".games-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateAccordion("games", data.items); + }); + } +}; + +polygon.profile.badges = +{ + type: "polygon", + display: function(page, type) + { + if(type == null) type = polygon.profile.badges.type; + else polygon.profile.badges.type = type; + + if(page == undefined) page = 1; + + $(".badges-container .items").empty(); + $(".badges-container .no-items").addClass("d-none"); + $(".badges-container .pagination").addClass("d-none"); + $(".badges-container .loading").removeClass("d-none"); + + $.post('/api/users/get-badges', {userID: $(".app").attr("data-user-id"), type: type, page: page}, function(data) + { + $(".badges-container .loading").addClass("d-none"); + + polygon.pagination.handle("badges", page, data.pages); + if(data.items == undefined) return $(".badges-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("badges", data.items); + }); + } +}; + +polygon.profile.groups = +{ + page: 1, + display: function(page) + { + if(page == undefined) page = this.page; + else this.page = page; + + $(".groups-container .items").empty(); + $(".groups-container .no-items").addClass("d-none"); + $(".groups-container .pagination").addClass("d-none"); + $(".groups-container .loading").removeClass("d-none"); + + $.post('/api/users/get-groups', {userID: $(".app").attr("data-user-id"), page: page}, function(data) + { + $(".groups-container .loading").addClass("d-none"); + + polygon.pagination.handle("groups", page, data.pages); + if(data.items == undefined) return $(".groups-container .no-items").text(data.message).removeClass("d-none"); + polygon.populateRow("groups", data.items); + }); + } +}; + +$(".badges-container .selector").click(function(){ polygon.profile.badges.display(null, $(this).attr("data-badge-type")); }); + +$(function() +{ + polygon.profile.games.display(); + polygon.profile.badges.display(); + polygon.profile.groups.display(); + + polygon.pagination.register("games", polygon.profile.games.display); + polygon.pagination.register("badges", polygon.profile.badges.display); + polygon.pagination.register("groups", polygon.profile.groups.display); +}); \ No newline at end of file diff --git a/keys/template.png b/keys/template.png new file mode 100644 index 0000000..5a8742e Binary files /dev/null and b/keys/template.png differ diff --git a/login.php b/login.php index 789c420..d65dfc7 100644 --- a/login.php +++ b/login.php @@ -1,6 +1,6 @@ false, "password" => false]; $username = $password = false; @@ -11,21 +11,23 @@ $returnurl = str_starts_with($returnurl_raw, "https://".$_SERVER['HTTP_HOST']) ? if($_SERVER['REQUEST_METHOD'] == 'POST') { + Polygon::ImportClass("Auth"); + $username = $_POST['username'] ?? false; $password = $_POST['password'] ?? false; $pwresult = false; - $userInfo = users::getUserInfoFromUserName($username); - $auth = new auth($password); + $userInfo = Users::GetInfoFromName($username); + $auth = new Auth($password); if(!$password) $errors["password"] = "Please enter your password"; if(!$username) $errors["username"] = "Please enter your username"; elseif(!$userInfo) $errors["username"] = "That user doesn't exist"; - elseif(!$auth->verifyPassword($userInfo->password)) $errors["password"] = "Incorrect password"; + elseif(!$auth->VerifyPassword($userInfo->password)) $errors["password"] = "Incorrect password"; if(!$errors["username"] && !$errors["password"]) { // upgrade password to argon2id w/ encryption if still using bcrypt - if(strpos($userInfo->password, "$2y$10") !== false) $auth->updatePassword($userInfo->id); + if(strpos($userInfo->password, "$2y$10") !== false) $auth->UpdatePassword($userInfo->id); session::createSession($userInfo->id); if($userInfo->twofa) { @@ -51,7 +53,7 @@ pageBuilder::buildHeader();
    - " name="username" id="username" value="" autocomplete="username"> + " name="username" id="username" value="" autocomplete="username">

    >

    @@ -92,7 +94,7 @@ pageBuilder::buildHeader();

    - more text goes here

    diff --git a/messages/inbox.php b/messages/inbox.php index e4ff65d..791b60c 100644 --- a/messages/inbox.php +++ b/messages/inbox.php @@ -1,7 +1,7 @@ diff --git a/moderation.php b/moderation.php index b8eac9f..145933d 100644 --- a/moderation.php +++ b/moderation.php @@ -3,12 +3,12 @@ $bypassModeration = true; require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php'; if(!SESSION){ pageBuilder::errorCode(404); } -if($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["reactivate"]) && users::undoUserModeration(SESSION["userId"])) +if($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["reactivate"]) && Users::UndoUserModeration(SESSION["userId"])) { redirect("/"); } -$moderationInfo = users::getUserModeration(SESSION["userId"]); +$moderationInfo = Users::GetUserModeration(SESSION["userId"]); if(!$moderationInfo) pageBuilder::errorCode(404); $text = @@ -38,7 +38,7 @@ $text = pageBuilder::$pageConfig["title"] = SITE_CONFIG["site"]["name"]." Moderation"; pageBuilder::buildHeader(); ?> -
    +
    Moderation
    @@ -47,10 +47,8 @@ pageBuilder::buildHeader();

    banType]?>

    Done at: timeStarted)?>

    Moderator note:

    -
    -
    - ', '

    ', $markdown->text($moderationInfo->reason, true))?> -

    +
    + ', '

    ', $markdown->text($moderationInfo->reason, true))?>


    banType]?>

    diff --git a/my/account.php b/my/account.php index 82619fd..325b60c 100644 --- a/my/account.php +++ b/my/account.php @@ -1,47 +1,83 @@ NULL, + "key" => $userinfo->discordKey, + "timeVerified" => $userinfo->discordVerifiedTime +]; -$sessions = $pdo->prepare("SELECT * FROM sessions WHERE userId = :uid AND valid AND created+157700000 > UNIX_TIMESTAMP() AND lastonline+432000 > UNIX_TIMESTAMP() ORDER BY created DESC"); -$sessions->bindParam(":uid", $userinfo->id, PDO::PARAM_INT); -$sessions->execute(); +if ($discordinfo->key == NULL) +{ + $discordinfo->key = generateUUID(); + db::run( + "UPDATE users SET discordKey = :key WHERE id = :id", + [":key" => $discordinfo->key, ":id" => $userinfo->id] + ); +} +else if ($userInfo->discordID != NULL) +{ + $discordinfo->info = Discord::GetUserInfo($userinfo->discordID); +} +$gauth = TwoFactorAuth::Initialize(); $twofa = SESSION["2fa"]; $twofaSecret = $userinfo->twofaSecret; +$sessions = db::run( + "SELECT * FROM sessions WHERE userId = :uid AND valid AND created+157700000 > UNIX_TIMESTAMP() AND lastonline+432000 > UNIX_TIMESTAMP() ORDER BY created DESC", + [":uid" => $userinfo->id] +); + +$Fields = (object) +[ + "Code" => "", + "Password" => "" +]; + +$Errors = (object) +[ + "Code" => false, + "Password" => false +]; + +$RequestSent = false; + //2fa stuff is not done via ajax cuz am lazy -if(isset($_POST["2fa"])) +if($_SERVER["REQUEST_METHOD"] == "POST") { + Polygon::ImportClass("Auth"); + + $RequestSent = true; + $panel = "2FA"; + $csrf = $_POST['polygon_csrf'] ?? false; - $code = $_POST['code'] ?? false; - $password = $_POST['password'] ?? false; - $auth = new auth($password); + $Fields->Code = $_POST['code'] ?? "false"; + $Fields->Password = $_POST['password'] ?? "false"; - if($csrf != SESSION["csrfToken"]) - { - pageBuilder::showStaticNotification("error", "Invalid CSRF token"); goto pb; - } + $auth = new Auth($Fields->Password); - if(!$gauth->checkCode($twofaSecret, $code, 1)) + if($csrf != SESSION["csrfToken"]) $Errors->Password = "An unexpected error occurred"; + if(!$gauth->checkCode($twofaSecret, $Fields->Code, 1)) $Errors->Code = "Incorrect code"; + if(!$auth->VerifyPassword($userInfo->password)) $Errors->Password = "Incorrect password"; + + if(!$Errors->Code && !$Errors->Password) { - pageBuilder::showStaticNotification("error", "Incorrect code"); goto pb; - } - if(!$auth->verifyPassword($userInfo->password)) - { - pageBuilder::showStaticNotification("error", "Incorrect password"); goto pb; - } + TwoFactorAuth::Toggle(); + $twofa = !SESSION["2fa"]; - twofa::toggle(); - $twofa = !SESSION["2fa"]; - - if($twofa) - { - $recoveryCodes = twofa::generateRecoveryCodes(); - ob_start(); + if($twofa) + { + $recoveryCodes = TwoFactorAuth::GenerateRecoveryCodes(); + ob_start(); ?> Congratulations! Your account is now more secure. But before you go, there's one last thing: @@ -61,20 +97,22 @@ These are a set of static, one-time use codes that never expire unless they are This is the only time you'll ever see these here, so write them down somewhere now. "Two-Factor Authentication is active", - "body" => ob_get_clean(), - "buttons" => [["class" => "btn btn-primary", "dismiss" => true, "text" => "I understand"]] - ]); + pageBuilder::showStaticModal([ + "header" => "Two-Factor Authentication is active", + "body" => ob_get_clean(), + "buttons" => [["class" => "btn btn-primary", "dismiss" => true, "text" => "I understand"]], + "options" => ["show" => true, "backdrop" => "static"] + ]); + } + else + { + $twofaSecret = TwoFactorAuth::GenerateNewSecret($gauth); + } } } -elseif(!$userinfo->twofa) +else if(!$userinfo->twofa) { - $twofaSecret = $gauth->generateSecret(); - $query = $pdo->prepare("UPDATE users SET twofaSecret = :secret WHERE id = :uid"); - $query->bindParam(":uid", $userinfo->id, PDO::PARAM_INT); - $query->bindParam(":secret", $twofaSecret, PDO::PARAM_STR); - $query->execute(); + $twofaSecret = TwoFactorAuth::GenerateNewSecret($gauth); } pb: @@ -83,103 +121,106 @@ pageBuilder::buildHeader(); ?>

    My Account

    -
    -
    -
    -

    Settings

    -
    - -
    - -

    1000 characters max

    -
    -
    -
    - -
    - -

    Dark theme is very experimental, send me your suggestions!

    -
    -
    -
    -
    Filter
    -
    -
    - filter?' checked':''?> value="true"> -

    replaces words with baba booey

    +
    +
    +
    +

    General

    + +
    +
    +
    + +
    + +

    1000 characters max

    +
    -
    -
    - = 1) { ?> -
    -
    Debugging
    -
    -
    - debugging?' checked':''?> value="true"> -

    allows ingame debugging

    +
    + +
    + +

    Dark theme is very experimental, send me your suggestions!

    +
    +
    +
    Filter
    +
    +
    + filter?' checked':''?> value="true"> +

    replaces words with baba booey

    +
    +
    +
    + +
    +
    Debugging
    +
    +
    + debugging?' checked':''?> value="true"> +

    allows ingame debugging

    +
    +
    +
    + + + + +
    +
    + info == NULL) { ?> +

    Looks like you're not yet verified. If you haven't joined the server yet, you can find the Discord link up in the navbar.

    +

    Once you join, the verification bot should DM you asking for your key, which is here:

    + PolygonVerify:key?> +

    Just send this to the bot, and you'll be verified!

    +

    If the bot hasn't DMed you, it may be down. When it comes back online, just send a DM to the bot with your key.

    + +
    +
    +
    +
    + +
    +
    +

    info->username?>#info->tag?>

    +
    Verified timeVerified)?>
    +
    +
    +
    +
    +

    If you wish to have your Discord account unverified so you can use another account, message an admin.

    +
    - - - -
    -

    Security

    -
    diff --git a/register.php b/register.php deleted file mode 100644 index f29c94c..0000000 --- a/register.php +++ /dev/null @@ -1,154 +0,0 @@ - false, "password" => false, "confirmpassword" => false, "regpass" => false]; -$keys = ["hsgjhsogiuosyru" => 1, "e6a76346d3ece5e9891c4876b85174bf" => 4, "78af60b3e80630cc8b2f4372ab1e8c8d" => 5, "c8f51135774e7f4e4027921fe947f67f" => 4, "e6a76346b3ece5e9891c4876b85174bc" => 3]; -$username = $password = $confirmpassword = $regpass = false; -if($_SERVER['REQUEST_METHOD'] == 'POST') -{ - $username = $_POST['username'] ?? false; - $password = $_POST['password'] ?? false; - $confirmpassword = $_POST['confirmpassword'] ?? false; - $regpass = $_POST['regpass'] ?? false; - - if(!$username) $errors["username"] = "Please enter a username"; - elseif(strlen($username) < 3 || strlen($username) > 20) $errors["username"] = "Your username can only be 3 - 20 characters long"; - elseif(preg_match('/[^A-Za-z0-9]/', $username)) $errors["username"] = "Your username can only contain alphanumeric characters"; - else - { - $query = $pdo->prepare("SELECT COUNT(*) FROM blacklistednames WHERE (exact AND username = :name) OR (NOT exact AND username LIKE CONCAT('%', :name, '%'))"); - $query->bindParam(":name", $username, PDO::PARAM_STR); - $query->execute(); - if($query->fetchColumn()){ $errors["username"] = "That username is unavailable. Sorry!"; goto end; } - - $query = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username = :name"); - $query->bindParam(":name", $username, PDO::PARAM_STR); - $query->execute(); - if($query->fetchColumn()){ $errors["username"] = "Someone already has that username! Try choosing a different one."; goto end; } - } - - if(!$password) $errors["password"] = "Please enter a password"; - elseif(strlen(preg_replace('/[0-9]/', "", $password)) < 6) $errors["password"] = "Your password is too weak. Make sure it contains at least six non-numeric characters"; - elseif(strlen(preg_replace('/[^0-9]/', "", $password)) < 2) $errors["password"] = "Your password is too weak. Make sure it contains at least two numbers"; - - if(!$confirmpassword) $errors["confirmpassword"] = "Please confirm your password"; - elseif($password != $confirmpassword) $errors["confirmpassword"] = "Confirmation password does not match"; - - if(!isset($keys[$regpass])) $errors["regpass"] = "Invalid registration code"; - else - { - $query = $pdo->prepare("SELECT COUNT(*) FROM users WHERE keyUsed = :key"); - $query->bindParam(":key", $regpass, PDO::PARAM_STR); - $query->execute(); - if($query->fetchColumn() >= $keys[$regpass]) $errors["regpass"] = "Invalid registration code"; - } - - if(!$errors["username"] && !$errors["password"] && !$errors["confirmpassword"] && !$errors["regpass"]) - { - $auth = new auth($password); - $pwhash = $auth->createPassword(); - $ip = $_SERVER["REMOTE_ADDR"]; - $query = $pdo->prepare("INSERT INTO users (username, password, keyUsed, email, jointime, lastonline, regip, nextCurrencyStipend, status) VALUES (:name, :hash, :key, 'placeholder', UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :ip, UNIX_TIMESTAMP()+86400, 'I\'m new to Polygon!')"); - $query->bindParam(":name", $username, PDO::PARAM_STR); - $query->bindParam(":hash", $pwhash, PDO::PARAM_STR); - $query->bindParam(":key", $regpass, PDO::PARAM_STR); - $query->bindParam(":ip", $ip, PDO::PARAM_STR); - - if($query->execute()) - { - $userid = $pdo->lastInsertId(); - - $query = $pdo->prepare("INSERT INTO ownedAssets (assetId, userId, wearing, timestamp) VALUES (162, :uid, 1, unix_timestamp())"); - $query->bindParam(":uid", $userid, PDO::PARAM_INT); - $query->execute(); - - $query = $pdo->prepare("INSERT INTO ownedAssets (assetId, userId, wearing, timestamp) VALUES (310, :uid, 1, unix_timestamp())"); - $query->bindParam(":uid", $userid, PDO::PARAM_INT); - $query->execute(); - - session::createSession($userid); - polygon::requestRender("Avatar", $userid); - die(header("Location: /")); - } - else{ die("An unexpected error occured! We're sorry."); } - } -} - -end: -pageBuilder::$pageConfig["title"] = "Sign Up"; -pageBuilder::buildHeader(); -?> - -

    Sign up

    -
    -
    -
    -
    -
    -
    - -
    - " name="username" id="username" value="" autocomplete="username"> - > - 3 - 20 alphanumeric characters, no spaces or underscores. Check our terms of service to make sure it's suitable. -
    -
    -
    - -
    - " name="password" id="password" value="" autocomplete="new-password"> - > - minimum 8 characters, must have at least 6 characters and 2 numbers -
    -
    -
    - -
    - " name="confirmpassword" id="confirmpassword" value=""> - > -
    -
    -
    - -
    - " name="regpass" id="regpass" value=""> - > -
    -
    - -
    -
    -
    -
    - Already registered? Login -
    -
    -
    - By signing up to and using , you agree to our terms of service and privacy policy. -
    -
    -
    -
    -
    - - diff --git a/thumbs/Script.png b/thumbs/Script.png index b87fddc..77e00f1 100644 Binary files a/thumbs/Script.png and b/thumbs/Script.png differ diff --git a/thumbs/asset.php b/thumbs/asset.php index bf58b24..67ee0ad 100644 --- a/thumbs/asset.php +++ b/thumbs/asset.php @@ -1,5 +1,6 @@ id ?? false); - if(!$info || $moderation && (!SESSION || !SESSION["adminLevel"])) pageBuilder::errorCode(404); + $info = Users::GetInfoFromID($_GET['ID'] ?? $_GET['id'] ?? false); + $moderation = Users::GetUserModeration($info->id ?? false); + if(!$info || $moderation && !$isModerator) pageBuilder::errorCode(404); $selfProfile = false; $pronouns = ["your" => $info->username."'s", "do_not" => $info->username." doesn't", "have_not" => $info->username." hasn't"]; } else { - users::requireLogin(); - $info = users::getUserInfoFromUid(SESSION["userId"]); + Users::RequireLogin(); + $info = Users::GetInfoFromID(SESSION["userId"]); $moderation = false; $selfProfile = true; $pronouns = ["your" => "Your", "do_not" => "You don't", "have_not" => "You haven't"]; } -if(SESSION) $friendship = users::checkIfFriends(SESSION["userId"], $info->id); +$statistics = (object) +[ + "friends" => Users::GetFriendCount($info->id), + "posts" => Users::GetForumPostCount($info->id), + "joined" => date("F j Y", $info->jointime) +]; -if(SESSION && SESSION["adminLevel"]) +if(SESSION) $friendship = Users::CheckIfFriends(SESSION["userId"], $info->id); + +if(SESSION) { - $alts = []; - function recurseAlts($ip) - { - global $pdo; - global $alts; - $query = $pdo->prepare("SELECT users.username, userId, users.jointime, loginIp FROM sessions INNER JOIN users ON users.id = userId WHERE loginIp = :ip GROUP BY userId"); - $query->bindParam(":ip", $ip, PDO::PARAM_STR); - $query->execute(); - while($row = $query->fetch(PDO::FETCH_OBJ)) - $alts[] = ["username" => $row->username, "userid" => $row->userId, "created" => $row->jointime, "ip" => $row->loginIp]; - } - recurseAlts($info->regip); + pageBuilder::$JSdependencies[] = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"; + pageBuilder::$JSdependencies[] = "/js/protocolcheck.js?t=".time(); + pageBuilder::$polygonScripts[] = "/js/polygon/games.js?t=".time(); + pageBuilder::$polygonScripts[] = "/js/polygon/inventory.js?t=".time(); } +pageBuilder::$polygonScripts[] = "/js/polygon/profile.js?t=".time(); pageBuilder::$polygonScripts[] = "/js/polygon/friends.js?t=".time(); -pageBuilder::$polygonScripts[] = "/js/polygon/inventory.js?t=".time(); + pageBuilder::$pageConfig["title"] = $info->username; -pageBuilder::$pageConfig["og:description"] = $info->blurb; +pageBuilder::$pageConfig["og:description"] = Polygon::FilterText($info->blurb); pageBuilder::$pageConfig["og:image"] = Thumbnails::GetAvatar($info->id, 420, 420); -pageBuilder::$pageConfig["app-attributes"] = ' data-user-id="'.$info->id.'"'; +pageBuilder::$pageConfig["app-attributes"] = " data-user-id=\"{$info->id}\""; pageBuilder::buildHeader(); if($moderation) { ?> - + -
    -
    -
    +
    +
    +

    Profile

    (View Public Profile) - id); ?> + id); ?>

    mb-0">[ ]

    https://
    <?=$info->username?> -

    blurb, false)?>

    +

    blurb)?>

    id != SESSION["userId"]) { if(!$friendship) { ?> @@ -74,204 +76,221 @@ if($moderation) { status == 0) { ?> Friend Request Pending - Unfriend + Unfriend Send Friend Request Send Message
    - +
    - -
    + +
    +

    Alternate Accounts

    - + id) as $alt) { ?>

    "> (Created )

    - + +
    +

    +
    + +
    +
    +
    + $name +
    $name
    +
    + +
    +
    +
    + +
    +
    +

    Groups

    +
    +

    +
    + +
    +
    +
    + $Name +
    +

    $Name

    +
    +
    +
    +
    +
    +

    Rank: $Role

    +

    Members: $MemberCount

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Statistics

    +
    +
    +

    Friends:

    +

    forum.">Forum Posts:

    +

    account.">Joined:

    +
    +
    +

    friends?>

    +

    posts?>

    +

    joined?>

    +
    +
    +
    -
    +
    +

    Games

    have any games
    -
    -
    - +
    + +
    + +
    +

    $server_description

    + +
    -
    - +
    + +
    + friends?>

    Friends

    - -
    -
    -
    -

    -
    -
    -
    -
    - $username - $username -
    +
    +
    +

    +
    +
    +
    +
    + $username + $username
    -
    - +
    -
    -
    -

    Inventory

    -
    -
    - -
    -
    -
    -
    -

    -
    -
    - - -
    -
    -
    - $item_name -
    -

    $item_name

    -

    Creator: $creator_name

    -

    $price

    -
    -
    -
    -
    - -
    -
    -
    - $name -

    $name

    -
    -
    -
    - -