code
This commit is contained in:
parent
9772b05bcb
commit
7ea1e111cb
|
|
@ -0,0 +1,4 @@
|
|||
thumbs/assets/*
|
||||
thumbs/avatars/*
|
||||
asset/files/*
|
||||
api/private/config.php
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
*
|
||||
{
|
||||
font-size: 12px;
|
||||
font-family: 'Comic Sans MS', Verdana, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
H1
|
||||
{
|
||||
font-weight: bold;
|
||||
font-size: larger;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT']."/api/private/core.php";
|
||||
pageBuilder::buildHeader();
|
||||
?>
|
||||
Do not run <code>game:HttpGet("http://polygon.pizzaboxer.xyz/XD")</code> in studio
|
||||
please no
|
||||
Plz
|
||||
<?php pageBuilder::buildFooter();
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("System");
|
||||
|
||||
Users::RequireAdmin();
|
||||
|
||||
function getSetupUsage($clients)
|
||||
{
|
||||
$usage = 0;
|
||||
|
||||
if(is_array($clients))
|
||||
{
|
||||
foreach ($clients as $client)
|
||||
{
|
||||
$usage += getSetupUsage($client);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$usage = System::GetFolderSize("/var/www/pizzaboxer.xyz/setup$clients/", true);
|
||||
}
|
||||
|
||||
return $usage;
|
||||
}
|
||||
|
||||
$roles =
|
||||
[
|
||||
Users::STAFF_MODERATOR => "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))
|
||||
],
|
||||
|
||||
"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/"),
|
||||
"ThumbnailUsage" => System::GetFolderSize("/var/www/pizzaboxer.xyz/polygoncdn/"),
|
||||
"SetupUsage" => System::GetFileSize(getSetupUsage([2009, 2010, 2011, 2012]))
|
||||
]
|
||||
];
|
||||
|
||||
pageBuilder::$pageConfig["title"] = SITE_CONFIG["site"]["name"]." Administration";
|
||||
pageBuilder::buildHeader();
|
||||
?>
|
||||
|
||||
<!--h1 style="position:absolute;opacity:0.5;font-size:10rem;z-index:10000000">THIS IS NOT <br> MULTAKOS SCREENSHOT LOL</h1-->
|
||||
<h2 class="font-weight-normal"><?=SITE_CONFIG["site"]["name"]?> Administration</h2>
|
||||
<div class="row">
|
||||
<div class="col-md-7 divider-right">
|
||||
<h3 class="pb-2 font-weight-normal">You are <?=vowel($roles[SESSION["adminLevel"]])?></h3>
|
||||
<div class="row px-3 mb-2">
|
||||
<?php if(Users::IsAdmin([Users::STAFF_MODERATOR, Users::STAFF_ADMINISTRATOR, Users::STAFF_CATALOG])) { ?>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-danger btn-lg btn-block px-0" href="/admin/moderate-user"><i class="fal fa-gavel"></i> Moderate user</a>
|
||||
</div>
|
||||
<?php } if(Users::IsAdmin()) { ?>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-danger btn-lg btn-block px-0" href="/admin/moderate-assets"><i class="fal fa-file-exclamation"></i> Moderate assets</a>
|
||||
</div>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-primary btn-lg btn-block px-0" href="/admin/render-queue"><i class="fal fa-images"></i> Render queue</a>
|
||||
</div>
|
||||
<?php } if(Users::IsAdmin(Users::STAFF_ADMINISTRATOR)) { ?>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-primary btn-lg btn-block px-0" href="/admin/site-banners"><i class="fal fa-bullhorn"></i> Site banners</a>
|
||||
</div>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-primary btn-lg btn-block px-0" href="#" onclick="polygon.buildModal({header: 'coming soon', body: 'Sample Text', buttons: [{class:'btn btn-primary px-4', dismiss:true, text:'OK'}]});"><i class="fal fa-rss-square"></i> Newsfeed</a>
|
||||
</div>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-primary btn-lg btn-block px-0" href="/admin/staff-audit"><i class="fal fa-book"></i> Audit log</a>
|
||||
</div>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-primary btn-lg btn-block px-0" href="/admin/error-log"><i class="fal fa-exclamation-triangle"></i> Error log</a>
|
||||
</div>
|
||||
<?php } if(Users::IsAdmin([Users::STAFF_CATALOG, Users::STAFF_ADMINISTRATOR])) { ?>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-success btn-lg btn-block px-0" href="/admin/create-asset"><i class="fal fa-file-plus"></i> Create asset</a>
|
||||
</div>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-success btn-lg btn-block px-0" href="/admin/give-asset"><i class="fal fa-gift"></i> Give asset</a>
|
||||
</div>
|
||||
<?php } if(Users::IsAdmin(Users::STAFF_ADMINISTRATOR)) { ?>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-success btn-lg btn-block px-0" href="/admin/give-currency"><i class="fal fa-pizza-slice"></i> Give <?=SITE_CONFIG["site"]["currency"]?></a>
|
||||
</div>
|
||||
<div class="col-md-4 py-2 px-1">
|
||||
<a class="btn btn-outline-success btn-lg btn-block px-0" href="#" onclick="polygon.buildModal({header: 'Credentials', body: '<span>You\'ll have to enter these yourself!</span> <br> Username: <code>ProjectPolygon</code> <br> Password: <code>962e8f89341b4e5f208076b5d06fb1b6</code>', buttons: [{class:'btn btn-primary px-4', attributes: [{'attr': 'onclick', 'val': 'window.location = \'https://stats.pizzaboxer.xyz\''}], text:'Continue'}]})"><i class="fal fa-chart-pie"></i> Statistics</a>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<h3 class="pb-3 font-weight-normal">Website / Server Info</h3>
|
||||
<div class="card w-100 mt-2">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="font-weight-normal"><i class="fal fa-server"></i> <?=gethostname()?></h3>
|
||||
<small><?=php_uname()?></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-100 mt-2">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="font-weight-normal"><i class="fal fa-memory"></i> <?=$usage->Memory->SytemUsage?> / <?=$usage->Memory->Total?> In Use</h3>
|
||||
<small><?=$usage->Memory->PHPUsage?> is being used by PHP</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-100 mt-2">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="font-weight-normal"><i class="fal fa-hdd"></i> <?=$usage->Disk->SystemUsage?> / <?=$usage->Disk->Total?> Used</h3>
|
||||
<small><?=SITE_CONFIG["site"]["name"]?> is using <?=$usage->Disk->PolygonUsage?></small><br>
|
||||
<small>Thumbnail CDN is using <?=$usage->Disk->ThumbnailUsage?></small><br>
|
||||
<small>Client setup (2009-2012) is using <?=$usage->Disk->SetupUsage?> total</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-100 mt-2">
|
||||
<div class="card-body text-center">
|
||||
<?php if(SITE_CONFIG["site"]["thumbserver"]) { ?>
|
||||
<h3 class="font-weight-normal"><i class="fal fa-images"></i> <?=$pendingRenders?> asset renders pending</h3>
|
||||
<?php if($thumbPing+35 > time()) { ?>
|
||||
<small>The thumbnail server is currently online</small>
|
||||
<?php } else { ?>
|
||||
<small>The thumbnail server last registered online at <?=date("j/n/Y g:i:s A", $thumbPing)?></small>
|
||||
<?php } } else { ?>
|
||||
<h3 class="font-weight-normal"><i class="fal fa-images"></i> Thumbserver is disabled</h3>
|
||||
<small>The thumbnail server has been manually disabled. <br> Go to /api/private/config.php to re-enable it.</small>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-100 mt-2">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="font-weight-normal"><i class="fal fa-user"></i> <?=$usersOnline?> user<?=$usersOnline>1?'s':''?> currently online</h3>
|
||||
<?php if($usersOnline == 1) { ?><small>dead much?</small><?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php pageBuilder::buildFooter(); ?>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$assetid = $_POST['assetID'] ?? false;
|
||||
$userid = SESSION["userId"];
|
||||
|
||||
$query = $pdo->prepare("DELETE FROM ownedAssets WHERE assetId = :aid AND userId = :uid");
|
||||
$query->bindParam(":aid", $assetid, PDO::PARAM_INT);
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if(!$query->rowCount()) api::respond(400, false, "You do not own this asset");
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$Wearing = ($_POST["Wearing"] ?? "false") == "true";
|
||||
$Type = $_POST["Type"] ?? false;
|
||||
$Items = [];
|
||||
|
||||
if($Wearing)
|
||||
{
|
||||
$AssetCount = db::run(
|
||||
"SELECT COUNT(*) FROM ownedAssets WHERE userId = :UserID AND wearing = 1",
|
||||
[":UserID" => SESSION["userId"]]
|
||||
)->fetchColumn();
|
||||
}
|
||||
else
|
||||
{
|
||||
$TypeString = Catalog::GetTypeByNum($Type);
|
||||
if(!Catalog::GetTypeByNum($Type)) api::respond(400, false, "Invalid asset type");
|
||||
|
||||
$AssetCount = db::run(
|
||||
"SELECT COUNT(*) FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :UserID AND assets.type = :AssetType AND wearing = 0",
|
||||
[":UserID" => SESSION["userId"], ":AssetType" => $Type]
|
||||
)->fetchColumn();
|
||||
}
|
||||
|
||||
$Pagination = Pagination($_POST["Page"] ?? 1, $AssetCount, 8);
|
||||
|
||||
if($Pagination->Pages == 0)
|
||||
{
|
||||
api::respond(200, true, $Wearing ? "You are not currently wearing anything" : "You don't have any unequipped ".plural($TypeString)." to wear");
|
||||
}
|
||||
|
||||
if($Wearing)
|
||||
{
|
||||
$Assets = db::run(
|
||||
"SELECT assets.* FROM ownedAssets
|
||||
INNER JOIN assets ON assets.id = assetId
|
||||
WHERE userId = :UserID AND wearing = 1
|
||||
ORDER BY last_toggle DESC LIMIT 8 OFFSET :Offset",
|
||||
[":UserID" => SESSION["userId"], ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$Assets = db::run(
|
||||
"SELECT assets.* FROM ownedAssets
|
||||
INNER JOIN assets ON assets.id = assetId
|
||||
WHERE userId = :UserID AND assets.type = :AssetType AND wearing = 0
|
||||
ORDER BY timestamp DESC LIMIT 8 OFFSET :Offset",
|
||||
[":UserID" => SESSION["userId"], ":AssetType" => $Type, ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
}
|
||||
|
||||
while($asset = $Assets->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$Items[] =
|
||||
[
|
||||
"url" => "/".encode_asset_name($asset->name)."-item?id=".$asset->id,
|
||||
"item_id" => $asset->id,
|
||||
"item_name" => htmlspecialchars($asset->name),
|
||||
"item_thumbnail" => Thumbnails::GetAsset($asset, 420, 420)
|
||||
];
|
||||
}
|
||||
|
||||
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pagination->Pages, "items" => $Items]));
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
Polygon::ImportClass("RBXClient");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$bodyColors = json_decode(SESSION["userInfo"]["bodycolors"]);
|
||||
$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 = RBXClient::HexToBrickColor(rgbtohex($color));
|
||||
if(!$brickcolor) api::respond(200, false, "Invalid body color #".rgbtohex($color));
|
||||
|
||||
$bodyColors->{$bodyPart} = $brickcolor;
|
||||
$bodyColors = json_encode($bodyColors);
|
||||
|
||||
$query = $pdo->prepare("UPDATE users SET bodycolors = :bodycolors WHERE id = :uid");
|
||||
$query->bindParam(":bodycolors", $bodyColors, PDO::PARAM_STR);
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
Polygon::RequestRender("Avatar", SESSION["userId"]);
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$assetid = $_POST['assetID'] ?? false;
|
||||
$userid = SESSION["userId"];
|
||||
|
||||
$query = $pdo->prepare("SELECT wearing, assets.* FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :uid AND assetId = :aid");
|
||||
$query->bindParam(":aid", $assetid, PDO::PARAM_INT);
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$info = $query->fetch(PDO::FETCH_OBJ);
|
||||
if(!$info) api::respond(400, false, "You do not own this asset");
|
||||
|
||||
$wear = !$info->wearing;
|
||||
|
||||
if(in_array($info->type, [2, 11, 12, 17, 18])) //asset types that can only have one worn at a time
|
||||
{
|
||||
$query = $pdo->prepare("UPDATE ownedAssets INNER JOIN assets ON assets.id = assetId SET wearing = 0 WHERE userId = :uid AND type = :type");
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->bindParam(":type", $info->type, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
if($wear)
|
||||
{
|
||||
$query = $pdo->prepare("UPDATE ownedAssets SET wearing = 1, last_toggle = UNIX_TIMESTAMP() WHERE userId = :uid AND assetId = :aid");
|
||||
$query->bindParam(":aid", $assetid, PDO::PARAM_INT);
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
}
|
||||
}
|
||||
elseif($info->type == 8) //up to 3 hats can be worn at the same time
|
||||
{
|
||||
if($wear)
|
||||
{
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :uid AND type = 8 AND wearing");
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
if($query->fetchColumn() >= 5) api::respond(400, false, "You cannot wear more than 5 hats at a time");
|
||||
}
|
||||
|
||||
$query = $pdo->prepare("UPDATE ownedAssets SET wearing = :wear, last_toggle = UNIX_TIMESTAMP() WHERE userId = :uid AND assetId = :aid");
|
||||
$query->bindParam(":wear", $wear, PDO::PARAM_INT);
|
||||
$query->bindParam(":aid", $assetid, PDO::PARAM_INT);
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
}
|
||||
elseif($info->type == 19) //no limit to how many gears can be equipped
|
||||
{
|
||||
$query = $pdo->prepare("UPDATE ownedAssets SET wearing = :wear, last_toggle = UNIX_TIMESTAMP() WHERE userId = :uid AND assetId = :aid");
|
||||
$query->bindParam(":wear", $wear, PDO::PARAM_INT);
|
||||
$query->bindParam(":aid", $assetid, PDO::PARAM_INT);
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
api::respond(400, false, "You cannot wear this asset!");
|
||||
}
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "secure" => true, "logged_in" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$sessionkey = SESSION['sessionKey'];
|
||||
|
||||
$sesscount = $pdo->prepare("SELECT COUNT(*) FROM sessions WHERE userId = :uid AND valid AND NOT sessionKey = :key");
|
||||
$sesscount->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$sesscount->bindParam(":key", $sessionkey, PDO::PARAM_STR);
|
||||
$sesscount->execute();
|
||||
|
||||
if(!$sesscount->fetchColumn()) api::respond(400, false, "There are no other sessions to log out of");
|
||||
|
||||
$query = $pdo->prepare("UPDATE sessions SET valid = 0 WHERE userId = :uid AND valid AND NOT sessionKey = :key");
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->bindParam(":key", $sessionkey, PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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 :uid IN (requesterId, receiverId) 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" => '<h4 class="font-weight-normal">lol</h4>',
|
||||
"message" => 'fucked your mom'
|
||||
];*/
|
||||
|
||||
/*$news[] =
|
||||
[
|
||||
"header" => '<h4 class="font-weight-normal">this isn\'t dead!!!! (probably)</h4>',
|
||||
"message" => "ive been more inclined to work on polygon now after like 4 months, so i guess development has resumed <br><br> 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. <br> If you haven't yet, come join the <a href=\"/groups?gid=1\">official group</a>!"
|
||||
]; */
|
||||
|
||||
$news[] =
|
||||
[
|
||||
"header" => "",
|
||||
"img" => "https://media.discordapp.net/attachments/745025397749448814/835635922590629888/HDKolobok-256px-3.gif",
|
||||
"message" => "What you know about KOLONBOK. ™ "
|
||||
];
|
||||
|
||||
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" => "<p class=\"m-0\"><a href=\"/user?ID={$row->userId}\">{$row->username}</a> - <small>{$timestamp}</small></p>",
|
||||
"message" => Polygon::FilterText($row->text)
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$GroupInfo = Groups::GetGroupInfo($row->groupId, true, true);
|
||||
$GroupInfo->name = htmlspecialchars($GroupInfo->name);
|
||||
|
||||
$feed[] =
|
||||
[
|
||||
"userName" => $GroupInfo->name,
|
||||
"img" => Thumbnails::GetAssetFromID($GroupInfo->emblem, 420, 420),
|
||||
"header" => "<p class=\"m-0\"><a href=\"/groups?gid={$GroupInfo->id}\">{$GroupInfo->name}</a> - <small>posted by <a href=\"/user?ID={$row->userId}\">{$row->username}</a></small> - <small>{$timestamp}</small></p>",
|
||||
"message" => Polygon::FilterText($row->text)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$FeedCount = $FeedResults->rowCount();
|
||||
|
||||
if($FeedCount < 15)
|
||||
{
|
||||
$feed[] =
|
||||
[
|
||||
"userName" => "Your feed is currently empty!",
|
||||
"img" => "/img/feed/friends.png",
|
||||
"header" => "<h4 class=\"font-weight-normal\">Looks like your feed's empty</h4>",
|
||||
"message" => "If you haven't made any friends yet, <a href='/browse'>go make some</a>! <br> 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" => "<h4 class=\"font-weight-normal\">Customize your character</h4>",
|
||||
"message" => "Log in every day and earn 10 pizzas. Pizzas can be used to buy clothing in our <a href=\"/catalog\">catalog</a>. You can also create your own clothing on the <a href=\"/develop\">Build page</a>."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "feed" => $feed, "news" => $news]);
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Games");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$items = [];
|
||||
|
||||
$query = $pdo->prepare("
|
||||
SELECT selfhosted_servers.* FROM client_sessions
|
||||
INNER JOIN selfhosted_servers ON selfhosted_servers.id = serverID
|
||||
WHERE uid = :uid AND used
|
||||
GROUP BY serverID ORDER BY client_sessions.id DESC LIMIT 8");
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
while($game = $query->fetch(PDO::FETCH_OBJ))
|
||||
$items[] =
|
||||
[
|
||||
"game_name" => Polygon::FilterText($game->name),
|
||||
"game_id" => $game->id,
|
||||
"game_thumbnail" => Thumbnails::GetAvatar($game->hoster, 250, 250),
|
||||
"playing" => Games::GetPlayersInServer($game->id)->rowCount()
|
||||
];
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "items" => $items]);
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$type = $_POST["type"] ?? false;
|
||||
$Items = [];
|
||||
|
||||
if (!in_array($type, ["Purchases", "Sales"])) api::respond(400, false, "Bad Request");
|
||||
|
||||
if ($type == "Sales")
|
||||
{
|
||||
$SelfIdentifier = "seller";
|
||||
$MemberIdentifier = "purchaser";
|
||||
$Action = "sold";
|
||||
}
|
||||
else
|
||||
{
|
||||
$SelfIdentifier = "purchaser";
|
||||
$MemberIdentifier = "seller";
|
||||
$Action = "purchased";
|
||||
}
|
||||
|
||||
$TransactionCount = db::run(
|
||||
"SELECT COUNT(*) FROM transactions WHERE {$SelfIdentifier} = :UserID",
|
||||
[":UserID" => SESSION["userId"]]
|
||||
)->fetchColumn();
|
||||
|
||||
$Pagination = Pagination($_POST["page"] ?? 1, $TransactionCount, 15);
|
||||
|
||||
if($Pagination->Pages == 0) api::respond(200, true, "You have not {$Action} any items!");
|
||||
|
||||
$Transactions = db::run(
|
||||
"SELECT transactions.*, users.username, assets.name FROM transactions
|
||||
INNER JOIN users ON users.id = {$MemberIdentifier} INNER JOIN assets ON assets.id = transactions.assetId
|
||||
WHERE {$SelfIdentifier} = :UserID ORDER BY id DESC LIMIT 15 OFFSET :Offset",
|
||||
[":UserID" => SESSION["userId"], ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
|
||||
while($Transaction = $Transactions->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$MemberID = $type == "Sales" ? $Transaction->purchaser : $Transaction->seller;
|
||||
|
||||
$Items[] =
|
||||
[
|
||||
"type" => $type == "Sales" ? "Sold" : "Purchased",
|
||||
"date" => date('j/n/y', $Transaction->timestamp),
|
||||
"member_name" => $Transaction->username,
|
||||
"member_id" => $MemberID,
|
||||
"member_avatar" => Thumbnails::GetAvatar($MemberID, 48, 48),
|
||||
"asset_name" => Polygon::FilterText($Transaction->name),
|
||||
"asset_id" => $Transaction->assetId,
|
||||
"amount" => $Transaction->amount
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "items" => $Items, "pages" => $Pagination->Pages]);
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Auth");
|
||||
|
||||
api::initialize(["method" => "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']);
|
||||
|
||||
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($_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);
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
Users::UpdatePing();
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "friendRequests" => (int)SESSION["friendRequests"]]);
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "secure" => true, "logged_in" => true]);
|
||||
|
||||
if(!isset($_POST['blurb']) || !isset($_POST['theme']) || !isset($_POST['filter'])) api::respond(400, false, "Bad Request");
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$filter = (int)($_POST['filter'] == 'true');
|
||||
$debugging = (int)(isset($_POST['debugging']) && $_POST['debugging'] == 'true');
|
||||
|
||||
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(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",
|
||||
[":uid" => $userid, ":blurb" => $_POST['blurb'], ":filter" => $filter, ":theme" => $_POST['theme'], ":debugging" => $debugging]
|
||||
);
|
||||
|
||||
api::respond(200, true, "Your settings have been updated");
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
Polygon::ImportClass("Discord");
|
||||
|
||||
api::initialize(["method" => "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");
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Forum");
|
||||
|
||||
api::initialize(["method" => "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"); }
|
||||
if(!isset($_POST['postId'])){ api::respond(400, false, "Bad Request"); }
|
||||
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']);
|
||||
|
||||
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"); }
|
||||
else{ api::respond(500, false, "Internal Server Error"); }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Auth");
|
||||
|
||||
$password = $_GET["password"] ?? "";
|
||||
$auth = new Auth($password);
|
||||
echo $auth->CreatePassword();
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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");
|
||||
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM assets WHERE creator = 2 AND type = :type ORDER BY id DESC");
|
||||
$query->bindParam(":type", $type, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$pages = ceil($query->fetchColumn()/15);
|
||||
$offset = ($page - 1)*15;
|
||||
|
||||
$query = $pdo->prepare("SELECT * FROM assets WHERE creator = 2 AND type = :type ORDER BY id DESC LIMIT 15 OFFSET $offset");
|
||||
$query->bindParam(":type", $type, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
while($asset = $query->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$info = Catalog::GetAssetInfo($asset->id);
|
||||
|
||||
$assets[] =
|
||||
[
|
||||
"name" => htmlspecialchars($asset->name),
|
||||
"id" => $asset->id,
|
||||
"thumbnail" => Thumbnails::GetAsset($asset, 420, 420),
|
||||
"item_url" => "/".encode_asset_name($asset->name)."-item?id=".$asset->id,
|
||||
"config_url" => "/my/item?ID=".$asset->id,
|
||||
"created" => date("n/j/Y", $asset->created),
|
||||
"sales-total" => $info->sales_total,
|
||||
"sales-week" => $info->sales_week
|
||||
];
|
||||
}
|
||||
|
||||
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "assets" => $assets, "pages" => $pages]));
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "admin" => Users::STAFF, "secure" => true]);
|
||||
|
||||
$id = $_POST["id"];
|
||||
$category = $_POST["category"];
|
||||
$type = $_POST["type"] ?? false;
|
||||
$page = $_POST["page"] ?? 1;
|
||||
$result = [];
|
||||
|
||||
if(!in_array($category, ["User", "Asset"])) api::respond(400, false, "Bad Request");
|
||||
if(!in_array($type, ["Purchases", "Sales"])) api::respond(400, false, "Bad Request");
|
||||
|
||||
if ($category == "User")
|
||||
{
|
||||
$selector = $type == "Sales" ? "seller" : "purchaser";
|
||||
$member = $type == "Sales" ? "purchaser" : "seller";
|
||||
|
||||
}
|
||||
else if ($category == "Asset")
|
||||
{
|
||||
$selector = "assetId";
|
||||
$member = "purchaser";
|
||||
}
|
||||
|
||||
$count = db::run("SELECT COUNT(*) FROM transactions WHERE $selector = :id", [":id" => $id])->fetchColumn();
|
||||
|
||||
$pages = ceil($count/15);
|
||||
$offset = ($page - 1)*15;
|
||||
|
||||
$transactions = db::run(
|
||||
"SELECT transactions.*, users.username, assets.name FROM transactions
|
||||
INNER JOIN users ON $member = users.id
|
||||
INNER JOIN assets ON transactions.assetId = assets.id
|
||||
WHERE $selector = :id ORDER BY id DESC LIMIT 15 OFFSET $offset",
|
||||
[":id" => $id]
|
||||
);
|
||||
|
||||
if(!$transactions->rowCount()) api::respond(200, true, "No transactions have been logged");
|
||||
|
||||
while($transaction = $transactions->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$memberID = $member == "purchaser" ? $transaction->purchaser : $transaction->seller;
|
||||
|
||||
$result[] =
|
||||
[
|
||||
"type" => $type == "Sales" ? "Sold" : "Purchased",
|
||||
"date" => date('j/n/y', $transaction->timestamp),
|
||||
"member_name" => $transaction->username,
|
||||
"member_id" => $memberID,
|
||||
"member_avatar" => Thumbnails::GetAvatar($memberID, 48, 48),
|
||||
"asset_name" => htmlspecialchars($transaction->name),
|
||||
"asset_id" => $transaction->assetId,
|
||||
"amount" => $transaction->amount,
|
||||
"flagged" => (bool) $transaction->flagged
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "transactions" => $result, "pages" => $pages]);
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "admin" => Users::STAFF, "secure" => true]);
|
||||
|
||||
$page = $_POST["page"] ?? 1;
|
||||
$assets = [];
|
||||
|
||||
$query = $pdo->query("SELECT COUNT(*) FROM assets WHERE NOT approved AND type != 1");
|
||||
$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->bindParam(":offset", $offset, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
while($asset = $query->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$assets[] =
|
||||
[
|
||||
"url" => "item?ID=".$asset->id,
|
||||
"item_id" => $asset->id,
|
||||
"item_name" => htmlspecialchars($asset->name),
|
||||
"item_thumbnail" => Thumbnails::GetAsset($asset, 420, 420, true),
|
||||
"texture_id" => $asset->type == 22 ? $asset->id : $asset->imageID,
|
||||
"creator_id" => $asset->creator,
|
||||
"creator_name" => $asset->username,
|
||||
"type" => Catalog::GetTypeByNum($asset->type),
|
||||
"created" => date("j/n/y G:i A", $asset->created),
|
||||
"price" => $asset->sale ? $asset->price ? '<i class="fal fa-pizza-slice"></i> '.$asset->price : "Free" : "Off-Sale"
|
||||
];
|
||||
}
|
||||
|
||||
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $pages, "assets" => $assets]));
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
header("content-type: text/plain");
|
||||
|
||||
// this should only be used if core.php does not work
|
||||
$emergency = ($_GET["key"] ?? false) == "D5F6E2EAA6C07C991CA2895920A8BBA8BB66CA16";
|
||||
$output = "";
|
||||
$webhook = "";
|
||||
$output_array = [];
|
||||
|
||||
if($emergency)
|
||||
{
|
||||
require $_SERVER["DOCUMENT_ROOT"]."/api/private/components/Discord.php";
|
||||
|
||||
$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";
|
||||
Polygon::ImportClass("Discord");
|
||||
|
||||
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);
|
||||
|
||||
foreach($output_array as $line) $output .= "$line\n";
|
||||
if($exitcode != 0) $output .= "\n\nGit exited with code $exitcode";
|
||||
|
||||
echo $output;
|
||||
|
||||
$webhook .= "```yaml\n";
|
||||
$webhook .= $output;
|
||||
$webhook .= "```";
|
||||
|
||||
Discord::SendToWebhook(["content" => $webhook], Discord::WEBHOOK_POLYGON_GITPULL, false);
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
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"); }
|
||||
if(!trim($_POST["username"])){ api::respond(400, false, "You haven't set a username"); }
|
||||
|
||||
if(!$_POST["amount"]){ api::respond(400, false, "You haven't set the amount of ".SITE_CONFIG["site"]["currency"]." to give"); }
|
||||
if(!is_numeric($_POST["amount"])){ api::respond(400, false, "The amount of ".SITE_CONFIG["site"]["currency"]." to give must be numerical"); }
|
||||
if($_POST["amount"] > 500 || $_POST["amount"] < -500){ api::respond(400, false, "Maximum amount of ".SITE_CONFIG["site"]["currency"]." you can give/take is 500 at a time"); }
|
||||
|
||||
if(!trim($_POST["reason"])){ api::respond(400, false, "You must set a reason"); }
|
||||
|
||||
$amount = $_POST["amount"];
|
||||
$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!"); }
|
||||
|
||||
$query = $pdo->prepare("UPDATE users SET currency = currency+:amount WHERE id = :uid");
|
||||
$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"]." )");
|
||||
api::respond(200, true, "Gave ".$_POST["amount"]." ".SITE_CONFIG["site"]["currency"]." to ".$userInfo->username);
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "admin" => Users::STAFF, "admin_ratelimit" => true, "secure" => true]);
|
||||
|
||||
$assetId = $_POST['assetID'] ?? false;
|
||||
$action = $_POST['action'] ?? false;
|
||||
$action_sql = $action == "approve" ?: 2;
|
||||
$reason = $_POST['reason'] ?? false;
|
||||
$asset = Catalog::GetAssetInfo($assetId);
|
||||
|
||||
if (!in_array($action, ["approve", "decline"])) api::respond(400, false, "Invalid request");
|
||||
if (!$asset) api::respond(200, false, "Asset does not exist");
|
||||
if ($action == "approve" && $asset->approved == 1) api::respond(200, false, "This asset has already been approved");
|
||||
if ($action == "disapprove" && $asset->approved == 2) api::respond(200, false, "This asset has already been disapproved");
|
||||
if ($action == "approve" && $asset->approved == 2) api::respond(200, false, "Disapproved assets cannot be reapproved");
|
||||
|
||||
db::run(
|
||||
"UPDATE assets SET approved = :action WHERE id IN (:id, :image)",
|
||||
[":action" => $action_sql, ":id" => $asset->id, ":image" => $asset->imageID]
|
||||
);
|
||||
|
||||
if ($action == "decline")
|
||||
{
|
||||
Thumbnails::DeleteAsset($asset->id);
|
||||
Catalog::DeleteAsset($asset->id);
|
||||
|
||||
if ($asset->imageID != NULL)
|
||||
{
|
||||
Catalog::DeleteAsset($asset->imageID);
|
||||
Thumbnails::DeleteAsset($asset->imageID);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "admin" => [Users::STAFF_MODERATOR, Users::STAFF_CATALOG, Users::STAFF_ADMINISTRATOR], "admin_ratelimit" => true, "secure" => true]);
|
||||
|
||||
if(!isset($_POST["username"]) || !isset($_POST["banType"]) || !isset($_POST["moderationNote"]) || !isset($_POST["until"]) || !isset($_POST["deleteUsername"])) api::respond(400, false, "Bad Request");
|
||||
if($_POST["banType"] < 1 || $_POST["banType"] > 4) api::respond(400, false, "Bad Request");
|
||||
if($_POST["banType"] != 4 && empty($_POST["moderationNote"])) api::respond(200, false, "You must supply a reason");
|
||||
if(!trim($_POST["username"])) api::respond(200, false, "You haven't set the username to ban");
|
||||
if($_POST["banType"] == 2 && empty($_POST["until"])) api::respond(200, false, "Ban time not set");
|
||||
|
||||
$banType = $_POST["banType"];
|
||||
$staffNote = isset($_POST["staffNote"]) && $_POST["staffNote"] ? $_POST["staffNote"] : "";
|
||||
$userId = SESSION["userId"];
|
||||
$reason = $_POST["moderationNote"];
|
||||
$bannedUntil = $_POST["banType"] == 2 ? strtotime($_POST["until"]." ".date('G:i:s')) : 0;
|
||||
$deleteUsername = (int)($_POST["deleteUsername"] == "true");
|
||||
|
||||
if (strpos($_POST["username"], ",") === false)
|
||||
{
|
||||
$result = BanUser(Users::GetInfoFromName($_POST["username"]));
|
||||
if($result !== true) api::respond(200, false, $result);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (explode(",", $_POST["username"]) as $BannerID)
|
||||
{
|
||||
BanUser(Users::GetInfoFromID($BannerID));
|
||||
}
|
||||
}
|
||||
|
||||
function BanUser($bannerInfo)
|
||||
{
|
||||
global $banType, $staffNote, $userId, $reason, $bannedUntil, $deleteUsername;
|
||||
|
||||
if(!$bannerInfo) return "User does not exist";
|
||||
|
||||
if($banType == 4)
|
||||
{
|
||||
if(!Users::GetUserModeration($bannerInfo->id)) return "That user isn't banned!";
|
||||
Users::UndoUserModeration($bannerInfo->id, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if($bannerInfo->id == $userId) return "You cannot moderate yourself";
|
||||
if($bannerInfo->adminlevel > 0) return "You cannot moderate a staff member";
|
||||
if(Users::GetUserModeration($bannerInfo->id)) return "That user is already banned!";
|
||||
if($banType == 2 && $bannedUntil < strtotime('tomorrow')) return "Ban time must be at least 1 day long";
|
||||
|
||||
db::run(
|
||||
"INSERT INTO bans (userId, bannerId, timeStarted, timeEnds, reason, banType, note)
|
||||
VALUES (:bid, :uid, UNIX_TIMESTAMP(), :ends, :reason, :type, :note)",
|
||||
[":bid" => $bannerInfo->id, ":uid" => $userId, ":ends" => $bannedUntil, ":reason" => $reason, ":type" => $banType, ":note" => $staffNote]
|
||||
);
|
||||
}
|
||||
|
||||
if ($deleteUsername && $banType != 4)
|
||||
{
|
||||
db::run("UPDATE users SET username = :Username WHERE id = :UserID", [":Username" => "[ Content Deleted {$bannerInfo->id} ]", ":UserID" => $bannerInfo->id]);
|
||||
}
|
||||
|
||||
$staff =
|
||||
[
|
||||
1 => "Warned " . $bannerInfo->username,
|
||||
2 => "Banned " . $bannerInfo->username . " for " . GetReadableTime($bannedUntil, ["Ending" => false]),
|
||||
3 => "Permanently banned " . $bannerInfo->username,
|
||||
4 => "Unbanned " . $bannerInfo->username
|
||||
];
|
||||
|
||||
Users::LogStaffAction("[ User Moderation ] ".$staff[$banType]." ( user ID ".$bannerInfo->id." )");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$text =
|
||||
[
|
||||
1 => "warned",
|
||||
2 => "banned for " . GetReadableTime($bannedUntil, ["Ending" => false]),
|
||||
3 => "permanently banned",
|
||||
4 => "unbanned"
|
||||
];
|
||||
|
||||
api::respond(200, true, $_POST["username"]." has been ".$text[$banType]);
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "admin" => [Users::STAFF_MODERATOR, Users::STAFF_CATALOG, 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"); }
|
||||
if(!trim($_POST["moderationNote"])){ api::respond(400, false, "You must supply a reason"); }
|
||||
if($_POST["banType"] == 2 && !trim($_POST["until"])){ api::respond(400, false, "Ban time not set"); }
|
||||
|
||||
$banType = $_POST["banType"];
|
||||
$bannedUntil = strtotime($_POST["until"]." ".date('G:i:s'));
|
||||
|
||||
if($bannedUntil < strtotime('tomorrow')){ api::respond(400, false, "Ban time must be at least 1 day long"); }
|
||||
|
||||
//markdown
|
||||
$markdown = new Parsedown();
|
||||
$markdown->setMarkupEscaped(true);
|
||||
$markdown->setBreaksEnabled(true);
|
||||
$markdown->setSafeMode(true);
|
||||
$markdown->setUrlsLinked(true);
|
||||
|
||||
$text =
|
||||
[
|
||||
"title" =>
|
||||
[
|
||||
1 => "Warning",
|
||||
2 => "Banned for ".timeSince("@".($bannedUntil+1), false, false),
|
||||
3 => "Account Deleted"
|
||||
],
|
||||
|
||||
"header" =>
|
||||
[
|
||||
1 => "This is just a heads-up to remind you to follow the rules",
|
||||
2 => "Your account has been banned for violating our rules",
|
||||
3 => "Your account has been permanently banned for violating our rules"
|
||||
],
|
||||
|
||||
"footer" =>
|
||||
[
|
||||
1 => "Please re-read the <a href='/info/rules'>rules</a> and abide by them to prevent yourself from facing a ban",
|
||||
2 => "Your ban ends at ".date('j/n/Y g:i:s A \G\M\T', $bannedUntil).", or in ".timeSince("@".($bannedUntil+1), true, false)." <br><br> Circumventing your ban on an alternate account while it is active may cause your ban time to be extended",
|
||||
3 => "Circumventing your ban by using an alternate account will lower your chance of appeal (if your ban was appealable) and potentially warrant you an IP ban"
|
||||
]
|
||||
];
|
||||
|
||||
ob_start(); ?>
|
||||
<h2 class="font-weight-normal"><?=$text["title"][$banType]?></h2>
|
||||
<p class="card-text"><?=$text["header"][$banType]?></p>
|
||||
<p class="card-text">Done at: <?=date('j/n/Y g:i:s A \G\M\T')?></p>
|
||||
<p class="card-text mb-0">Reason:</p>
|
||||
<div class="card">
|
||||
<div class="card-body p-2">
|
||||
<?=str_replace('<p>', '<p class="mb-0">', $markdown->text($_POST["moderationNote"], true))?>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<p class="card-text"><?=$text["footer"][$banType]?></p>
|
||||
<?php if($banType == 1) { ?>
|
||||
<a href="#" class="btn btn-primary disabled">Reactivate</a>
|
||||
<?php } api::respond(200, true, ob_get_clean());
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
Polygon::ImportClass("Image");
|
||||
Polygon::ImportLibrary("class.upload");
|
||||
|
||||
api::initialize(["method" => "POST", "admin" => Users::STAFF, "secure" => true]);
|
||||
|
||||
$renderType = $_POST['renderType'] ?? false;
|
||||
$assetID = $_POST['assetID'] ?? false;
|
||||
|
||||
if(!$renderType) api::respond(400, false, "Bad Request");
|
||||
if(!in_array($renderType, ["Avatar", "Asset"])) api::respond(400, false, "Invalid render type");
|
||||
if(!$assetID || !is_numeric($assetID)) api::respond(400, false, "Bad Request");
|
||||
|
||||
if($renderType == "Asset")
|
||||
{
|
||||
$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 2: // t-shirt
|
||||
$image = new Upload(SITE_CONFIG['paths']['assets'].$asset->imageID);
|
||||
|
||||
Thumbnails::UploadAsset($image, $asset->imageID, 60, 62, ["keepRatio" => true, "align" => "T"]);
|
||||
Thumbnails::UploadAsset($image, $asset->imageID, 420, 420, ["keepRatio" => true, "align" => "T"]);
|
||||
|
||||
//process initial tshirt thumbnail
|
||||
$template = imagecreatefrompng($_SERVER['DOCUMENT_ROOT']."/img/tshirt-template.png");
|
||||
$shirtdecal = Image::Resize(SITE_CONFIG['paths']['thumbs_assets']."{$asset->imageID}-420x420.png", 250, 250);
|
||||
imagesavealpha($template, true);
|
||||
imagesavealpha($shirtdecal, true);
|
||||
Image::MergeLayers($template, $shirtdecal, 85, 85, 0, 0, 250, 250, 100);
|
||||
|
||||
imagepng($template, SITE_CONFIG['paths']['thumbs_assets']."$assetID-420x420.png");
|
||||
Image::Resize(SITE_CONFIG['paths']['thumbs_assets']."$assetID-420x420.png", 100, 100, SITE_CONFIG['paths']['thumbs_assets']."$assetID-100x100.png");
|
||||
Image::Resize(SITE_CONFIG['paths']['thumbs_assets']."$assetID-420x420.png", 110, 110, SITE_CONFIG['paths']['thumbs_assets']."$assetID-110x110.png");
|
||||
|
||||
Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."$assetID-100x100.png");
|
||||
Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."$assetID-110x110.png");
|
||||
Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."$assetID-420x420.png");
|
||||
break;
|
||||
case 13: // decal
|
||||
$image = new Upload(SITE_CONFIG['paths']['assets'].$asset->imageID);
|
||||
|
||||
Thumbnails::UploadAsset($image, $asset->imageID, 60, 62, ["keepRatio" => true, "align" => "C"]);
|
||||
Thumbnails::UploadAsset($image, $asset->imageID, 420, 420, ["keepRatio" => true, "align" => "C"]);
|
||||
|
||||
Thumbnails::UploadAsset($image, $assetID, 48, 48);
|
||||
Thumbnails::UploadAsset($image, $assetID, 75, 75);
|
||||
Thumbnails::UploadAsset($image, $assetID, 100, 100);
|
||||
Thumbnails::UploadAsset($image, $assetID, 110, 110);
|
||||
Thumbnails::UploadAsset($image, $assetID, 250, 250);
|
||||
Thumbnails::UploadAsset($image, $assetID, 352, 352);
|
||||
Thumbnails::UploadAsset($image, $assetID, 420, 230);
|
||||
Thumbnails::UploadAsset($image, $assetID, 420, 420);
|
||||
break;
|
||||
case 3: // audio
|
||||
Image::RenderFromStaticImage("audio", $assetID);
|
||||
break;
|
||||
default: api::respond(200, false, "This asset cannot be re-rendered");
|
||||
}
|
||||
}
|
||||
else if($renderType == "Avatar")
|
||||
{
|
||||
$user = Users::GetInfoFromID($assetID);
|
||||
if(!$user) api::respond(200, false, "The user you requested does not exist");
|
||||
Polygon::RequestRender("Avatar", $assetID);
|
||||
}
|
||||
|
||||
Users::LogStaffAction("[ Render ] Re-rendered $renderType ID $assetID");
|
||||
api::respond(200, true, "Render request has been successfully submitted! See render status <a href='/admin/render-queue'>here</a>");
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Image");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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::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");
|
||||
|
||||
//$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");
|
||||
|
||||
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");
|
||||
|
||||
$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/"]);
|
||||
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"])) 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::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]);
|
||||
copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$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]);
|
||||
copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$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]);
|
||||
copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$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]);
|
||||
copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$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");
|
||||
|
||||
$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/"]);
|
||||
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]);
|
||||
|
||||
file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, Catalog::GenerateGraphicXML("Face", $imageId));
|
||||
|
||||
Thumbnails::UploadAsset($image, $itemId, 420, 230);
|
||||
Thumbnails::UploadAsset($image, $itemId, 420, 420);
|
||||
Thumbnails::UploadAsset($image, $itemId, 352, 352);
|
||||
Thumbnails::UploadAsset($image, $itemId, 250, 250);
|
||||
Thumbnails::UploadAsset($image, $itemId, 110, 110);
|
||||
Thumbnails::UploadAsset($image, $itemId, 100, 100);
|
||||
Thumbnails::UploadAsset($image, $itemId, 75, 75);
|
||||
Thumbnails::UploadAsset($image, $itemId, 48, 48);
|
||||
}
|
||||
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}']);
|
||||
copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId);
|
||||
Polygon::RequestRender("Model", $assetId);
|
||||
}
|
||||
|
||||
Users::LogStaffAction("[ Asset creation ] Created \"$name\" [ID ".($itemId ?? $assetId ?? $imageId)."]");
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "<a href='/item?ID=".($itemId ?? $assetId ?? $imageId)."'>".Catalog::GetTypeByNum($type)."</a> successfully created!"]);
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize();
|
||||
|
||||
$AssetID = api::GetParameter("GET", "assetID", "int");
|
||||
$Page = api::GetParameter("GET", "page", "int", 1);
|
||||
|
||||
$CommentsCount = db::run("SELECT COUNT(*) FROM asset_comments WHERE assetID = :AssetID", [":AssetID" => $AssetID])->fetchColumn();
|
||||
if($CommentsCount == 0) api::respond(200, true, "This item does not have any comments");
|
||||
|
||||
$Pagination = Pagination($Page, $CommentsCount, 15);
|
||||
|
||||
$Comments = db::run(
|
||||
"SELECT asset_comments.*, users.username FROM asset_comments
|
||||
INNER JOIN users ON users.id = asset_comments.author
|
||||
WHERE assetID = :AssetID
|
||||
ORDER BY id DESC LIMIT 15 OFFSET :Offset",
|
||||
[":AssetID" => $AssetID, ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
|
||||
$Items = [];
|
||||
|
||||
while($Comment = $Comments->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$Items[] =
|
||||
[
|
||||
"time" => strtolower(timeSince($Comment->time)),
|
||||
"commenter_name" => $Comment->username,
|
||||
"commenter_id" => $Comment->author,
|
||||
"commenter_avatar" => Thumbnails::GetAvatar($Comment->author, 110, 110),
|
||||
"content" => nl2br(Polygon::FilterText($Comment->content))
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "comments" => $Items, "pages" => $Pagination->Pages]);
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
if(!isset($_POST['assetID']) || !isset($_POST['content']));
|
||||
|
||||
$uid = SESSION["userId"];
|
||||
$id = $_POST['assetID'];
|
||||
$content = $_POST['content'];
|
||||
|
||||
$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");
|
||||
if(strlen($content) > 100) api::respond(400, false, "Comment cannot be longer than 128 characters");
|
||||
|
||||
$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();
|
||||
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);
|
||||
$query->bindParam(":content", $content, PDO::PARAM_STR);
|
||||
$query->bindParam(":aid", $id, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
function getPrice($price)
|
||||
{
|
||||
return $price ? '<span class="text-success"><i class="fal fa-pizza-slice"></i> '.$price.'</span>' : '<span class="text-success">Free</span>';
|
||||
}
|
||||
|
||||
$uid = SESSION["userId"];
|
||||
$id = $_POST['id'] ?? false;
|
||||
$price = $_POST['price'] ?? 0;
|
||||
|
||||
$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(!$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,
|
||||
"success" => true,
|
||||
"message" => "Item price changed",
|
||||
"header" => "Item Price Has Changed",
|
||||
"text" => 'While you were shopping, the price of this item changed from '.getPrice($price).' to '.getPrice($item->price).'.',
|
||||
"buttons" => [['class'=>'btn btn-success btn-confirm-purchase', 'text'=>'Buy Now'], ['class'=>'btn btn-secondary', 'dismiss'=>true, 'text'=>'Cancel']],
|
||||
"footer" => 'Your balance after this transaction will be <i class="fal fa-pizza-slice"></i> '.(SESSION["currency"] - $item->price),
|
||||
"newprice" => $item->price
|
||||
]));
|
||||
}
|
||||
|
||||
$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);
|
||||
$query->bindParam(":uid", $uid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$query = $pdo->prepare("INSERT INTO transactions (purchaser, seller, assetId, amount, flagged, timestamp) VALUES (:uid, :sid, :aid, :price, :flagged, UNIX_TIMESTAMP())");
|
||||
$query->bindParam(":uid", $uid, PDO::PARAM_INT);
|
||||
$query->bindParam(":sid", $item->creator, PDO::PARAM_INT);
|
||||
$query->bindParam(":aid", $id, PDO::PARAM_INT);
|
||||
$query->bindParam(":price", $item->price, PDO::PARAM_INT);
|
||||
$query->bindParam(":flagged", $IsAlt, 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,
|
||||
"success" => true,
|
||||
"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),
|
||||
"buttons" => [['class' => 'btn btn-primary continue-shopping', 'dismiss' => true, 'text' => 'Continue Shopping']],
|
||||
]));
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$type = $_POST["type"] ?? false;
|
||||
$page = $_POST["page"] ?? 1;
|
||||
$assets = [];
|
||||
|
||||
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);
|
||||
$query->bindParam(":type", $type, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
while($asset = $query->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$info = Catalog::GetAssetInfo($asset->id);
|
||||
|
||||
$assets[] =
|
||||
[
|
||||
"name" => htmlspecialchars($asset->name),
|
||||
"id" => $asset->id,
|
||||
"thumbnail" => Thumbnails::GetAsset($asset, 110, 110),
|
||||
"item_url" => "/".encode_asset_name($asset->name)."-item?id=".$asset->id,
|
||||
"config_url" => "/my/item?ID=".$asset->id,
|
||||
"created" => date("n/j/Y", $asset->created),
|
||||
"sales-total" => $info->sales_total,
|
||||
"sales-week" => $info->sales_week
|
||||
];
|
||||
}
|
||||
|
||||
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "assets" => $assets]));
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Image");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$file = $_FILES["file"] ?? false;
|
||||
$name = $_POST["name"] ?? false;
|
||||
$type = $_POST["type"] ?? false;
|
||||
|
||||
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(empty($name)) api::respond(200, false, "You must specify a name");
|
||||
if(strlen($name) > 50) api::respond(200, false, "The name is too long");
|
||||
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($userid != 1 && $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
|
||||
// the image thumbnail should have the texture positioned top
|
||||
//
|
||||
// shirts and pants should ideally be 585x559 but it doesnt really matter -
|
||||
// just as long as it looks right on the avatar. if it doesnt then disapprove
|
||||
//
|
||||
// decals are a lot more messy:
|
||||
// the image asset itself is scaled to be 256 pixels in width, while preserving the texture ratio
|
||||
// the image thumbnail should have the texture positioned center
|
||||
// the decal asset however must have the texture stretched to 1:1 for all its respective sizes
|
||||
// [example: https://www.roblox.com/Item.aspx?ID=8553820]
|
||||
//
|
||||
// we won't have to worry about image size constraints as they're always gonna be
|
||||
// resized to fit in a smaller resolution
|
||||
//
|
||||
// refer to here for the thumbnail sizes: https://github.com/matthewdean/roblox-web-apis
|
||||
//
|
||||
// THUMBNAIL SIZES FOR EACH ITEM TYPE
|
||||
// legend: [f = fit] [t = top] [c = center] [s = stretch] // [M = Model] [He = Head] [S = Shirt] [P = Pants]
|
||||
//
|
||||
// | 48x48 | 60x62 | 75x75 | 100x100 | 110x110 | 160x100 | 250x250 | 352x352 | 420x230 | 420x420 |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// Image | |yes (f)| | | | | | | | yes (t) |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// T-Shirt | | | | yes | yes | | | | | yes |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// Audio | | | yes | yes | yes | | yes | yes | yes | yes |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// Hat/Gear | yes | | yes | yes | yes | | yes | yes | yes (fc)| yes |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// Place | yes |yes(fc)| yes | yes | yes | yes | yes | yes | yes | yes |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// M/He/S/P | yes | | yes | yes | yes | | yes | yes | | yes |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
// Decal/Face |yes (s)| |yes (s)| yes (s) | yes (s) | | yes (s) | yes (s) | yes (s) | yes (s) |
|
||||
// +-------+-------+-------+---------+---------+---------+---------+---------+---------+---------+
|
||||
|
||||
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"]);
|
||||
|
||||
if($type == 2) //tshirt
|
||||
{
|
||||
$Processed = Image::Process($image, ["name" => "$imageId", "keepRatio" => true, "align" => "T", "x" => 128, "y" => 128, "dir" => "/asset/files/"]);
|
||||
if ($Processed !== true) api::respond(200, false, "Image processing failed: $Processed");
|
||||
|
||||
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]);
|
||||
|
||||
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);
|
||||
imagesavealpha($template, true);
|
||||
imagesavealpha($shirtdecal, true);
|
||||
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");
|
||||
|
||||
Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-100x100.png");
|
||||
Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-110x110.png");
|
||||
Thumbnails::UploadToCDN(SITE_CONFIG['paths']['thumbs_assets']."/$itemId-420x420.png");
|
||||
}
|
||||
elseif($type == 11 || $type == 12) //shirt / pants
|
||||
{
|
||||
$Processed = Image::Process($image, ["name" => "$imageId", "x" => 585, "y" => 559, "dir" => "/asset/files/"]);
|
||||
if ($Processed !== true) api::respond(200, false, "Image processing failed: $Processed");
|
||||
|
||||
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);
|
||||
}
|
||||
elseif($type == 13) //decal
|
||||
{
|
||||
$Processed = Image::Process($image, ["name" => "$imageId", "x" => 256, "scaleY" => true, "dir" => "/asset/files/"]);
|
||||
if ($Processed !== true) api::respond(200, false, "Image processing failed: $Processed");
|
||||
|
||||
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]);
|
||||
|
||||
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(200, true, Catalog::GetTypeByNum($type)." successfully created!");
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
require $_SERVER["DOCUMENT_ROOT"]."/api/private/core.php";
|
||||
api::initialize(["method" => "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");
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php require $_SERVER["DOCUMENT_ROOT"]."/api/private/core.php";
|
||||
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "GET", "api" => "DiscordBot"]);
|
||||
|
||||
if (isset($_GET["UserName"]))
|
||||
{
|
||||
$userInfo = db::run(
|
||||
"SELECT id, username, blurb, adminlevel, jointime, lastonline, discordID, discordVerifiedTime 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, discordVerifiedTime 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->thumbnail = Thumbnails::GetAvatar($userInfo->id, 250, 250, true);
|
||||
|
||||
$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);
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$RequestsCount = db::run(
|
||||
"SELECT COUNT(*) FROM friends WHERE receiverId = :UserID AND status = 0", [":UserID" => SESSION["userId"]]
|
||||
)->fetchColumn();
|
||||
|
||||
if($RequestsCount == 0) api::respond(200, false, "You don't have any friend requests to accept right now");
|
||||
|
||||
db::run("UPDATE friends SET status = 1 WHERE receiverId = :UserID AND status = 0", [":UserID" => SESSION["userId"]]);
|
||||
|
||||
api::respond(200, true, "All your friend requests have been accepted");
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$FriendID = api::GetParameter("POST", "FriendID", "int");
|
||||
|
||||
$FriendRequest = db::run("SELECT * FROM friends WHERE id = :FriendID AND status = 0", [":FriendID" => $FriendID]);
|
||||
$FriendRequestInfo = $FriendRequest->fetch(PDO::FETCH_OBJ);
|
||||
|
||||
if($FriendRequest->rowCount() == 0) api::respond(200, false, "Friend request doesn't exist");
|
||||
if((int) $FriendRequestInfo->receiverId != SESSION["userId"]) api::respond(200, false, "You are not the recipient of this friend request");
|
||||
|
||||
db::run("UPDATE friends SET status = 1 WHERE id = :FriendID", [":FriendID" => $FriendID]);
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$Page = api::GetParameter("POST", "Page", "int", 1);
|
||||
|
||||
$RequestsCount = db::run(
|
||||
"SELECT COUNT(*) FROM friends WHERE receiverId = :UserID AND status = 0",
|
||||
[":UserID" => SESSION["userId"]]
|
||||
)->fetchColumn();
|
||||
if($RequestsCount == 0) api::respond(200, true, "You're all up-to-date with your friend requests");
|
||||
|
||||
$Pagination = Pagination($Page, $RequestsCount, 18);
|
||||
|
||||
$Requests = db::run(
|
||||
"SELECT * FROM friends WHERE receiverId = :UserID AND status = 0 LIMIT 18 OFFSET :Offset",
|
||||
[":UserID" => SESSION["userId"], ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
|
||||
while($Request = $Requests->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$Items[] =
|
||||
[
|
||||
"Username" => Users::GetNameFromID($Request->requesterId),
|
||||
"UserID" => $Request->requesterId,
|
||||
"Avatar" => Thumbnails::GetAvatar($Request->requesterId, 250, 250),
|
||||
"FriendID" => $Request->id
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "items" => $Items, "count" => (int) $RequestsCount, "pages" => (int) $Pagination->Pages]);
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$userid = SESSION["userId"];
|
||||
$page = $_POST['page'] ?? 1;
|
||||
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM friends WHERE receiverId = :uid AND status = 0");
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$pages = ceil($query->fetchColumn()/18);
|
||||
$offset = ($page - 1)*18;
|
||||
|
||||
if(!$pages) api::respond(200, true, "You're all up-to-date with your friend requests!");
|
||||
|
||||
$query = $pdo->prepare("SELECT * FROM friends WHERE receiverId = :uid AND status = 0 LIMIT 18 OFFSET :offset");
|
||||
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
|
||||
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$friends = [];
|
||||
|
||||
while($row = $query->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$friends[] =
|
||||
[
|
||||
"username" => Users::GetNameFromID($row->requesterId),
|
||||
"userid" => $row->requesterId,
|
||||
"avatar" => Thumbnails::GetAvatar($row->requesterId, 250, 250),
|
||||
"friendid" => $row->id
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "requests" => $friends, "pages" => $pages]);
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST"]);
|
||||
|
||||
$url = $_SERVER['HTTP_REFERER'] ?? false;
|
||||
$userId = $_POST['userID'] ?? false;
|
||||
$page = $_POST['page'] ?? 1;
|
||||
$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::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);
|
||||
$query->execute();
|
||||
|
||||
$pages = ceil($query->fetchColumn()/$limit);
|
||||
$offset = ($page - 1)*$limit;
|
||||
|
||||
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
|
||||
INNER JOIN users ON users.id = (CASE WHEN requesterId = :uid THEN receiverId ELSE requesterId END)
|
||||
WHERE :uid IN (requesterId, receiverId) AND friends.status = 1
|
||||
ORDER BY $order LIMIT :limit OFFSET :offset");
|
||||
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
|
||||
$query->bindParam(":limit", $limit, PDO::PARAM_INT);
|
||||
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$friends = [];
|
||||
|
||||
while($row = $query->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$friends[] =
|
||||
[
|
||||
"username" => $row->username,
|
||||
"userid" => $row->userId,
|
||||
"avatar" => Thumbnails::GetAvatar($row->userId, 250, 250),
|
||||
"friendid" => $row->id,
|
||||
"status" => Polygon::FilterText($row->status)
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "friends" => $friends, "pages" => $pages]);
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$RequestsCount = db::run(
|
||||
"SELECT COUNT(*) FROM friends WHERE receiverId = :UserID AND status = 0", [":UserID" => SESSION["userId"]]
|
||||
)->fetchColumn();
|
||||
|
||||
if($RequestsCount == 0) api::respond(200, false, "You don't have any friend requests to decline right now");
|
||||
|
||||
db::run("UPDATE friends SET status = 2 WHERE receiverId = :UserID AND status = 0", [":UserID" => SESSION["userId"]]);
|
||||
|
||||
api::respond(200, true, "All your friend requests have been decline");
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$FriendID = api::GetParameter("POST", "FriendID", "int");
|
||||
|
||||
$FriendConnection = db::run("SELECT * FROM friends WHERE id = :FriendID AND NOT status = 2", [":FriendID" => $FriendID]);
|
||||
$FriendConnectionInfo = $FriendConnection->fetch(PDO::FETCH_OBJ);
|
||||
|
||||
if($FriendConnection->rowCount() == 0) api::respond(200, false, "Friend connection doesn't exist");
|
||||
if(!in_array(SESSION["userId"], [$FriendConnectionInfo->requesterId, $FriendConnectionInfo->receiverId])) api::respond(200, false, "You are not a part of this friend connection");
|
||||
|
||||
db::run("UPDATE friends SET status = 2 WHERE id = :FriendID", [":FriendID" => $FriendID]);
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$UserID = api::GetParameter("POST", "UserID", "int");
|
||||
|
||||
if($UserID == SESSION["userId"]) api::respond(200, false, "You can't perform friend operations on yourself");
|
||||
|
||||
$FriendConnection = db::run(
|
||||
"SELECT status FROM friends WHERE :UserID IN (requesterId, receiverId) AND :ReceiverID IN (requesterId, receiverId) AND NOT status = 2",
|
||||
[":UserID" => SESSION["userId"], ":ReceiverID" => $UserID]
|
||||
);
|
||||
if($FriendConnection->rowCount() != 0) api::respond(200, false, "Friend connection already exists");
|
||||
|
||||
$LastRequest = db::run(
|
||||
"SELECT timeSent FROM friends WHERE requesterId = :UserID AND timeSent+300 > UNIX_TIMESTAMP()",
|
||||
[":UserID" => SESSION["userId"]]
|
||||
);
|
||||
if($LastRequest->rowCount() != 0) api::respond(200, false, "Please wait ".GetReadableTime($LastRequest->fetchColumn(), ["RelativeTime" => "5 minutes"])." before sending another request");
|
||||
|
||||
db::run(
|
||||
"INSERT INTO friends (requesterId, receiverId, timeSent) VALUES (:UserID, :ReceiverID, UNIX_TIMESTAMP())",
|
||||
[":UserID" => SESSION["userId"], ":ReceiverID" => $UserID]
|
||||
);
|
||||
|
||||
api::respond(200, true, "OK");
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
if (!Polygon::$GamesEnabled) api::respond(200, false, "Games are currently closed. See <a href=\"/forum\">this announcement</a> for more information.");
|
||||
|
||||
$ServerID = api::GetParameter("POST", "ServerID", "int");
|
||||
$Username = api::GetParameter("POST", "Username", "string");
|
||||
$Action = api::GetParameter("POST", "Action", ["Add", "Remove"]);
|
||||
|
||||
$ServerInfo = db::run("SELECT * FROM selfhosted_servers WHERE id = :ServerID", [":ServerID" => $ServerID])->fetch(PDO::FETCH_OBJ);
|
||||
if (!$ServerInfo || !Users::IsAdmin(Users::STAFF_ADMINISTRATOR) && $ServerInfo->hoster != SESSION["userId"]) api::respond(200, false, "You do not have permission to configure this server");
|
||||
if ($ServerInfo->Privacy != "Private") api::respond(200, false, "The privacy of this server must first be set to Private");
|
||||
if ($ServerInfo->LastWhitelistEdit+30 > time()) api::respond(200, false, "Please wait ".GetReadableTime($ServerInfo->LastWhitelistEdit, ["RelativeTime" => "30 seconds"])." before editing your whitelist");
|
||||
|
||||
$Whitelist = ($ServerInfo->PrivacyWhitelist == null) ? [] : json_decode($ServerInfo->PrivacyWhitelist);
|
||||
|
||||
$UserInfo = Users::GetInfoFromName($Username);
|
||||
if (!$UserInfo) api::respond(200, false, "That username is not on Project Polygon");
|
||||
|
||||
if ($Action == "Add")
|
||||
{
|
||||
if ((int) $UserInfo->id == SESSION["userId"]) api::respond(200, false, "You cannot add yourself to the whitelist");
|
||||
if (in_array((int) $UserInfo->id, $Whitelist)) api::respond(200, false, "That user is already on the whitelist");
|
||||
$Whitelist[] = (int) $UserInfo->id;
|
||||
}
|
||||
else if ($Action == "Remove")
|
||||
{
|
||||
if (!in_array((int) $UserInfo->id, $Whitelist)) api::respond(200, false, "That user is not on the whitelist");
|
||||
|
||||
$Location = array_search((int) $UserInfo->id, $Whitelist);
|
||||
if ($Location === false) api::respond(200, false, "An unexpected error occurred");
|
||||
|
||||
unset($Whitelist[$Location]);
|
||||
}
|
||||
|
||||
db::run(
|
||||
"UPDATE selfhosted_servers SET LastWhitelistEdit = UNIX_TIMESTAMP(), PrivacyWhitelist = :Whitelist WHERE id = :ServerID",
|
||||
[":ServerID" => $ServerID, ":Whitelist" => json_encode($Whitelist)]
|
||||
);
|
||||
|
||||
if ($Action == "Add")
|
||||
api::respond(200, true, "$Username has been added to the whitelist");
|
||||
else if ($Action == "Remove")
|
||||
api::respond(200, true, "$Username has been removed from the whitelist");
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
Polygon::ImportClass("Games");
|
||||
Polygon::ImportClass("Catalog");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true]);
|
||||
|
||||
$Version = api::GetParameter("POST", "Version", ["Any", "2009", "2010", "2011", "2012"], "Any");
|
||||
$CreatorID = api::GetParameter("POST", "CreatorID", "int", false);
|
||||
|
||||
if (!Polygon::$GamesEnabled) api::respond(200, false, "Games are currently closed. See <a href=\"/forum/showpost?PostID=2380\">this announcement</a> for more information.");
|
||||
|
||||
$query_params = "1 AND (Privacy = \"Public\" OR hoster = :UserID OR JSON_CONTAINS(PrivacyWhitelist, :UserID, \"$\"))";
|
||||
$value_params = [":UserID" => SESSION["userId"]];
|
||||
|
||||
if($Version != "Any")
|
||||
{
|
||||
$query_params .= " AND version = :Version";
|
||||
$value_params[":Version"] = (int) $Version;
|
||||
}
|
||||
|
||||
if($CreatorID !== false)
|
||||
{
|
||||
$query_params .= " AND hoster = :HosterID";
|
||||
$value_params[":HosterID"] = $CreatorID;
|
||||
}
|
||||
|
||||
$ServersCount = db::run("SELECT COUNT(*) FROM selfhosted_servers WHERE $query_params", $value_params)->fetchColumn();
|
||||
|
||||
$Pagination = Pagination(api::GetParameter("POST", "Page", "int", 1), $ServersCount, 10);
|
||||
$value_params[":Offset"] = $Pagination->Offset;
|
||||
|
||||
$Servers = db::run(
|
||||
"SELECT selfhosted_servers.*, users.username,
|
||||
(ping+35 > UNIX_TIMESTAMP()) AS online,
|
||||
(
|
||||
CASE WHEN ping+35 > UNIX_TIMESTAMP() THEN
|
||||
(SELECT COUNT(DISTINCT uid) FROM client_sessions WHERE ping+35 > UNIX_TIMESTAMP() AND serverID = selfhosted_servers.id AND verified AND valid)
|
||||
ELSE 0 END
|
||||
) AS players
|
||||
FROM selfhosted_servers
|
||||
INNER JOIN users ON users.id = selfhosted_servers.hoster
|
||||
WHERE $query_params
|
||||
ORDER BY online DESC, players DESC, ping DESC, created DESC LIMIT 10 OFFSET :Offset",
|
||||
$value_params
|
||||
);
|
||||
|
||||
if ($Servers->rowCount() == 0)
|
||||
{
|
||||
if ($CreatorID === false)
|
||||
{
|
||||
api::respond(200, true, "No servers matched your query");
|
||||
}
|
||||
else
|
||||
{
|
||||
api::respond(200, true, Users::GetNameFromID($CreatorID)." does not have any games");
|
||||
}
|
||||
}
|
||||
|
||||
while ($Server = $Servers->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$Gears = [];
|
||||
foreach (json_decode($Server->allowed_gears, true) as $GearName => $GearEnabled)
|
||||
{
|
||||
if (!$GearEnabled) continue;
|
||||
$Gears[] = ["name" => Catalog::$GearAttributesDisplay[$GearName]["text_sel"], "icon" => Catalog::$GearAttributesDisplay[$GearName]["icon"]];
|
||||
}
|
||||
|
||||
$Items[] =
|
||||
[
|
||||
"server_id" => (int) $Server->id,
|
||||
"server_name" => Polygon::FilterText($Server->name),
|
||||
"server_description" => empty($Server->description) ? "No description available." : Polygon::FilterText($Server->description),
|
||||
"server_thumbnail" => Thumbnails::GetAvatar($Server->hoster, 420, 420),
|
||||
"hoster_name" => $Server->username,
|
||||
"hoster_id" => $Server->hoster,
|
||||
"date" => date('n/d/Y g:i:s A', $Server->created),
|
||||
"version" => (int) $Server->version,
|
||||
"server_online" => (bool) $Server->online,
|
||||
"players_online" => (int) $Server->players,
|
||||
"players_max" => (int) $Server->maxplayers,
|
||||
"privacy" => $Server->Privacy,
|
||||
"gears" => $Gears
|
||||
];
|
||||
}
|
||||
|
||||
db::run("INSERT INTO log (UserID, Timestamp, IPAddress) VALUES (:UserID, UNIX_TIMESTAMP(), :IPAddress)", [":UserID" => SESSION["userId"], ":IPAddress" => GetIPAddress()]);
|
||||
|
||||
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $Pagination->Pages, "items" => $Items]));
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
header("Pragma: no-cache");
|
||||
header("Cache-Control: no-cache");
|
||||
|
||||
Polygon::ImportClass("Games");
|
||||
Polygon::ImportClass("Discord");
|
||||
|
||||
api::initialize(["method" => "GET", "logged_in" => true, "secure" => true]);
|
||||
|
||||
if (!Polygon::$GamesEnabled) api::respond(200, false, "Games are currently closed. See <a href=\"/forum\">this announcement</a> for more information.");
|
||||
|
||||
$serverID = $_GET["serverID"] ?? $_GET['placeId'] ?? false;
|
||||
$isTeleport = isset($_GET["isTeleport"]) && $_GET['isTeleport'] == "true";
|
||||
|
||||
if($isTeleport && GetUserAgent() != "Roblox/WinInet")
|
||||
api::respond_custom([
|
||||
"Error" => "Request is not authorized from specified origin",
|
||||
"userAgent" => $_SERVER["HTTP_USER_AGENT"] ?? null,
|
||||
"referrer" => $_SERVER["HTTP_REFERER"] ?? null
|
||||
]);
|
||||
|
||||
/* if($isTeleport)
|
||||
{
|
||||
$ticket = $_COOKIE['ticket'] ?? false;
|
||||
$query = $pdo->prepare("SELECT uid FROM client_sessions WHERE ticket = :ticket");
|
||||
$query->bindParam(":ticket", $ticket, PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
if(!$query->rowCount()) api::respond_custom(["Error" => "You are not logged in"]);
|
||||
$userid = $query->fetchColumn();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!SESSION) api::respond(200, false, "You are not logged in");
|
||||
$userid = SESSION["userId"];
|
||||
} */
|
||||
|
||||
// if (!Discord::IsVerified(SESSION["userId"]))
|
||||
// api::respond(200, false, "You must verify yourself in the <a href=\"https://discord.com/invite/projectpolygon\">Discord server</a> before joining a game.");
|
||||
|
||||
/* $query = $pdo->prepare("SELECT *, (SELECT COUNT(*) FROM client_sessions WHERE ping+35 > UNIX_TIMESTAMP() AND serverID = selfhosted_servers.id AND valid) AS players FROM selfhosted_servers WHERE id = :sid");
|
||||
$query->bindParam(":sid", $serverID, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
$serverInfo = $query->fetch(PDO::FETCH_OBJ); */
|
||||
|
||||
$serverInfo = Games::GetServerInfo($serverID, SESSION["userId"], true);
|
||||
|
||||
if(!$serverInfo) api::respond(200, false, "Server does not exist");
|
||||
if(!$serverInfo->online) api::respond(200, false, "This server is currently offline.");
|
||||
if($serverInfo->players >= $serverInfo->maxplayers) api::respond(200, false, "This server is currently full. Please try again later");
|
||||
if($serverInfo->version == 2009 && SESSION["userId"] > 200) api::respond(200, false, "2009 games are currently disabled.");
|
||||
|
||||
$ticket = generateUUID();
|
||||
$securityTicket = generateUUID();
|
||||
db::run(
|
||||
"INSERT INTO client_sessions (ticket, securityTicket, uid, sessionType, serverID, created, isTeleport, RequesterIP)
|
||||
VALUES (:uuid, :security, :uid, 1, :sid, UNIX_TIMESTAMP(), :teleport, :ip)",
|
||||
[":uuid" => $ticket, ":security" => $securityTicket, ":uid" => SESSION["userId"], ":sid" => $serverID, ":teleport" => (int)$isTeleport, ":ip" => GetIPAddress()]
|
||||
);
|
||||
|
||||
$Protocol = "https";
|
||||
if($serverInfo->version == 2009) $Protocol = "http";
|
||||
|
||||
api::respond_custom([
|
||||
"status" => 200,
|
||||
"success" => true,
|
||||
"message" => "OK",
|
||||
"version" => $serverInfo->version,
|
||||
"joinScriptUrl" => "{$Protocol}://{$_SERVER['HTTP_HOST']}/game/join?ticket={$ticket}",
|
||||
// these last few params are for teleportservice and lack any function - just ignore
|
||||
"authenticationUrl" => "{$Protocol}://{$_SERVER['HTTP_HOST']}/Login/Negotiate.ashx",
|
||||
"authenticationTicket" => "0",
|
||||
"status" => 2
|
||||
]);
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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]));
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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]));
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> sent an ally request to <a href=\"/groups?gid=%d\">%s</a>",
|
||||
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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> declared <a href=\"/groups?gid=%d\">%s</a> 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");
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> %s <a href=\"/user?ID=%d\">%s</a> 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");
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> accepted an ally request from <a href=\"/groups?gid=%d\">%s</a>",
|
||||
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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> declined an ally request from <a href=\"/groups?gid=%d\">%s</a>",
|
||||
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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> removed <a href=\"/groups?gid=%d\">%s</a> 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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> removed <a href=\"/groups?gid=%d\">%s</a> 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");
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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"]));
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> deleted post \"%s\" by <a href=\"/user?ID=%d\">%s</a>",
|
||||
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");
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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]));
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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;
|
||||
$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();
|
||||
|
||||
$Pagination = Pagination($_POST["Page"] ?? 1, $MemberCount, 12);
|
||||
|
||||
if($Pagination->Pages == 0) 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, ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
|
||||
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" => $Pagination->Pages, "count" => $MemberCount, "items" => $Members]));
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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]));
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
api::initialize(["method" => "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;
|
||||
$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();
|
||||
|
||||
$Pagination = Pagination($_POST["Page"] ?? 1, $PostCount, 15);
|
||||
|
||||
if($Pagination->Pages == 0) 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, ":Offset" => $Pagination->Offset]
|
||||
);
|
||||
|
||||
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" => $Pagination->Pages, "count" => $PostCount, "items" => $Wall]));
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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");
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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");
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
Polygon::ImportClass("Discord");
|
||||
|
||||
api::initialize(["method" => "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(
|
||||
"<a href=\"/user?ID=%d\">%s</a> 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");
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Groups");
|
||||
|
||||
api::initialize(["method" => "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");
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
Polygon::ImportClass("Thumbnails");
|
||||
|
||||
$categories =
|
||||
[
|
||||
0=>"Bricks",
|
||||
1=>"Robots",
|
||||
2=>"Chassis",
|
||||
3=>"Furniture",
|
||||
4=>"Roads",
|
||||
5=>"Billboards",
|
||||
6=>"Game Objects",
|
||||
"MyDecals"=>"My Decals",
|
||||
"FreeDecals"=>"Free Decals",
|
||||
"MyModels"=>"My Models",
|
||||
"FreeModels"=>"Free Models"
|
||||
];
|
||||
|
||||
$category = isset($_POST['category']) && isset($categories[$_POST['category']]) ? $_POST['category'] : "FreeModels";
|
||||
$categoryText = $categories[$category];
|
||||
$type = strpos($category, "Decals") ? 13 : 10;
|
||||
$page = $_POST['page'] ?? 1;
|
||||
$keywd = $_POST['keyword'] ?? false;
|
||||
$keywd_sql = $keywd ? "%".$keywd."%" : "%";
|
||||
|
||||
if(is_numeric($category)) //static category
|
||||
{
|
||||
//$query = $pdo->prepare("SELECT COUNT(*) FROM catalog_items WHERE toolboxCategory = :category");
|
||||
//$query->bindParam(":category", $categoryText, PDO::PARAM_STR);
|
||||
}
|
||||
else //dynamic category - user assets, catalog assets
|
||||
{
|
||||
if(SESSION && strpos($categoryText, "My") !== false) //get assets from inventory
|
||||
{
|
||||
$userId = SESSION["userId"];
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM assets WHERE type = :type AND approved = 1 AND id IN (SELECT assetId FROM ownedAssets WHERE userId = :uid)");
|
||||
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
|
||||
}
|
||||
else //get assets from catalog
|
||||
{
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM assets WHERE type = :type AND approved = 1 AND (name LIKE :q OR description LIKE :q)");
|
||||
$query->bindParam(":q", $keywd_sql, PDO::PARAM_STR);
|
||||
}
|
||||
$query->bindParam(":type", $type, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
$query->execute();
|
||||
$items = $query->fetchColumn();
|
||||
$pages = ceil($items/20);
|
||||
$offset = ($page - 1)*20;
|
||||
|
||||
if(is_numeric($category)) //static category
|
||||
{
|
||||
//$query = $pdo->prepare("SELECT * FROM catalog_items WHERE toolboxCategory = :category ORDER BY id ASC LIMIT 20 OFFSET :offset");
|
||||
//$query->bindParam(":category", $categoryText, PDO::PARAM_STR);
|
||||
}
|
||||
else //dynamic category - user assets, catalog assets
|
||||
{
|
||||
if(strpos($categoryText, "My") !== false) //get assets from inventory
|
||||
{
|
||||
$userId = SESSION["userId"];
|
||||
$query = $pdo->prepare("SELECT assets.* FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :uid AND assets.type = :type ORDER BY timestamp DESC LIMIT 20 OFFSET :offset"); //all of this just to order by time bought...
|
||||
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
|
||||
}
|
||||
else //get assets from catalog
|
||||
{
|
||||
$query = $pdo->prepare("SELECT * FROM assets WHERE type = :type AND approved = 1 AND (name LIKE :q OR description LIKE :q) ORDER BY updated DESC LIMIT 20 OFFSET :offset");
|
||||
$query->bindParam(":q", $keywd_sql, PDO::PARAM_STR);
|
||||
$query->bindParam(":q2", $keywd_sql, PDO::PARAM_STR);
|
||||
}
|
||||
$query->bindParam(":type", $type, PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
?>
|
||||
<div id="ToolBoxPage">
|
||||
<div>
|
||||
<?php if($pages>1) { ?>
|
||||
<div id="pNavigation" style="display:table">
|
||||
<div class="Navigation">
|
||||
<div id="Previous">
|
||||
<a href="#" onclick="getToolbox('<?=$category?>', '<?=$keywd?>', <?=$page-1?>)" id="PreviousPage" <?=$page <= 1 ? 'style="visibility:hidden"':''?>><span class="NavigationIndicators"><<</span>
|
||||
Prev</a>
|
||||
</div>
|
||||
<div id="Next">
|
||||
<a href="#" onclick="getToolbox('<?=$category?>', '<?=$keywd?>', <?=$page+1?>)" id="NextPage" <?=$page >= $pages ? 'style="visibility:hidden"':''?>>Next <span class="NavigationIndicators">>></span></a>
|
||||
</div>
|
||||
<div id="Location">
|
||||
<span id="PagerLocation"><?=number_format((($page-1)*20)+1)?>-<?=number_format($page*20)?> of <?=number_format($items)?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div id="ToolboxItems">
|
||||
<?php while($row = $query->fetch(PDO::FETCH_OBJ)) { $name = Polygon::FilterText($row->name); ?>
|
||||
<a class="ToolboxItem" title="<?=$name?>" href="javascript:insertContent(<?=$row->id?>)" ondragstart="dragRBX(<?=$row->id?>)" onmouseover="this.style.borderStyle='outset'" onmouseout="this.style.borderStyle='solid'" style="border-style: solid;display:inline-block;height:60px;width:60px;cursor:pointer;">
|
||||
<img width="60" src="<?=Thumbnails::GetAsset($row, 75, 75)?>" border="0" id="img" alt="<?=$name?>">
|
||||
</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php if($pages>1) { ?>
|
||||
<div id="pNavigation" style="display:table">
|
||||
<div class="Navigation">
|
||||
<div id="Previous">
|
||||
<a href="#" onclick="getToolbox('<?=$category?>', '<?=$keywd?>', <?=$page-1?>)" id="PreviousPage" <?=$page <= 1 ? 'style="visibility:hidden"':''?>><span class="NavigationIndicators"><<</span>
|
||||
Prev</a>
|
||||
</div>
|
||||
<div id="Next">
|
||||
<a href="#" onclick="getToolbox('<?=$category?>', '<?=$keywd?>', <?=$page+1?>)" id="NextPage" <?=$page >= $pages ? 'style="visibility:hidden"':''?>>Next <span class="NavigationIndicators">>></span></a>
|
||||
</div>
|
||||
<div id="Location">
|
||||
<span id="PagerLocation"><?=number_format((($page-1)*20)+1)?>-<?=number_format($page*20)?> of <?=number_format($items)?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
|
||||
$UserId = SESSION["userId"];
|
||||
$page = $_POST['page'] ?? 1;
|
||||
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM messages WHERE ReceiverID = :uid AND TimeArchived = 0");
|
||||
$query->bindParam(":uid", $UserId, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$pages = ceil($query->fetchColumn()/18);
|
||||
$offset = ($page - 1)*18;
|
||||
|
||||
if(!$pages) api::respond(200, true, "Messages you receive from other users will be shown here.");
|
||||
|
||||
$query = $pdo->prepare("SELECT * FROM messages WHERE ReceiverID = :uid AND TimeArchived = 0 LIMIT 13 OFFSET :offset");
|
||||
$query->bindParam(":uid", $UserId, PDO::PARAM_INT);
|
||||
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
|
||||
$query->execute();
|
||||
|
||||
$messages = [];
|
||||
|
||||
while($row = $query->fetch(PDO::FETCH_OBJ))
|
||||
{
|
||||
$messages[] =
|
||||
[
|
||||
"Username" => Users::GetNameFromID($row->SenderID),
|
||||
"UserId" => $row->SenderID,
|
||||
"MessageId" => $row->ID,
|
||||
"Subject" => Polygon::FilterText($row->Subject, true, false),
|
||||
"TimeSent" => date('d M Y h:m a', $row->TimeSent),
|
||||
"TimeRead" => $row->TimeRead
|
||||
];
|
||||
}
|
||||
|
||||
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "messages" => $messages, "pages" => $pages]);
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
|
||||
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
|
||||
Polygon::ImportClass("Messages");
|
||||
|
||||
$isReply = false;
|
||||
|
||||
|
||||
$messageId = $_POST["messageId"] ?? false;
|
||||
|
||||
if($messageId)
|
||||
$isReply = true;
|
||||
|
||||
if($isReply) {
|
||||
$replyInfo = Messages::getMessageInfoFromId($messageId);
|
||||
if(!$replyInfo) api::respond(400, false, "Invalid Request");
|
||||
}
|
||||
|
||||
|
||||
if(!isset($_POST["subject"]) && !$isReply || !isset($_POST["body"]) || !isset($_POST["recipientId"])) api::respond(400, false, "Invalid Request");
|
||||
|
||||
|
||||
if(!$isReply) {
|
||||
if(!trim($_POST["subject"])) api::respond(400, false, "You cannot leave the subject empty");
|
||||
if(strlen($_POST["subject"] > 128) || strlen($_POST["subject"]) < 2) api::respond(400, false, "Message subject must be under 2-128 characters long.");
|
||||
}
|
||||
|
||||
if(!trim($_POST["body"])) api::respond(400, false, "You cannot leave the body empty");
|
||||
if(strlen($_POST["body"] > 768) || strlen($_POST["body"]) < 3) api::respond(400, false, "Message body must be under 3-768 characters long.");
|
||||
|
||||
$RecipientId = $_POST["recipientId"];
|
||||
$UserId = SESSION["userId"];
|
||||
$RecipientInfo = Users::GetInfoFromID($RecipientId);
|
||||
if(!$RecipientInfo) api::respond(400, false, "Invalid Request");
|
||||
|
||||
if($isReply) {
|
||||
$Subject = htmlspecialchars("RE: " . $replyInfo->Subject);
|
||||
} else {
|
||||
$Subject = htmlspecialchars($_POST["subject"]);
|
||||
}
|
||||
|
||||
$Body = htmlspecialchars($_POST["body"]);
|
||||
|
||||
db::run("INSERT INTO messages (SenderID, ReceiverID, Subject, Body, TimeSent, TimeArchived, TimeRead) VALUES (:sid, :rid, :sub, :body, UNIX_TIMESTAMP(), 0, 0)",
|
||||
[":sid" => $UserId, ":rid" => $RecipientId, ":sub" => $Subject, ":body" => $Body]);
|
||||
|
||||
api::respond(200, true, "Message sent.");
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
class Auth
|
||||
{
|
||||
// i wonder if its worth putting the plain password only in the constructor
|
||||
// for the sake of efficiency - usually it works out well, however in the
|
||||
// change password api you end up having to instantiate two auth objects
|
||||
// oh well
|
||||
// by the way, this is like the only OOP thing in the entirety of polygon
|
||||
// (apart from third party libaries). maybe i should change that. todo?
|
||||
|
||||
private $plaintext = "";
|
||||
private $key = "";
|
||||
|
||||
function CreatePassword()
|
||||
{
|
||||
return \ParagonIE\PasswordLock\PasswordLock::hashAndEncrypt($this->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"]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
class Catalog
|
||||
{
|
||||
public static array $types =
|
||||
[
|
||||
1 => "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)
|
||||
22 => "Group Emblem", // (internal use only - these are basically just images really)
|
||||
24 => "Animation",
|
||||
25 => "Arms",
|
||||
26 => "Legs",
|
||||
27 => "Torso",
|
||||
28 => "Right Arm",
|
||||
29 => "Left Arm",
|
||||
30 => "Left Leg",
|
||||
31 => "Right Leg",
|
||||
32 => "Package",
|
||||
33 => "YoutubeVideo",
|
||||
34 => "Gamepass",
|
||||
35 => "App",
|
||||
37 => "Code",
|
||||
38 => "Plugin", // (ignore everything beyond this point)
|
||||
39 => "SolidModel",
|
||||
40 => "MeshPart",
|
||||
41 => "Hair Accessory",
|
||||
42 => "Face Accessory",
|
||||
43 => "Neck Accessory",
|
||||
44 => "Shoulder Accessory",
|
||||
45 => "Front Accessory",
|
||||
46 => "Back Accessory",
|
||||
47 => "Waist Accessory",
|
||||
48 => "Climb Animation",
|
||||
49 => "Death Animation",
|
||||
50 => "Fall Animation",
|
||||
51 => "Idle Animation",
|
||||
52 => "Jump Animation",
|
||||
53 => "Run Animation",
|
||||
54 => "Swim Animation",
|
||||
55 => "Walk Animation",
|
||||
56 => "Pose Animation",
|
||||
59 => "LocalizationTableManifest",
|
||||
60 => "LocalizationTableTranslation",
|
||||
61 => "Emote Animation",
|
||||
62 => "Video",
|
||||
63 => "TexturePack",
|
||||
64 => "T-Shirt Accessory",
|
||||
65 => "Shirt Accessory",
|
||||
66 => "Pants Accessory",
|
||||
67 => "Jacket Accessory",
|
||||
68 => "Sweater Accessory",
|
||||
69 => "Shorts Accessory",
|
||||
70 => "Left Shoe Accessory",
|
||||
71 => "Right Shoe Accessory",
|
||||
72 => "Dress Skirt Accessory",
|
||||
73 => "Font Family",
|
||||
74 => "Font Face",
|
||||
75 => "MeshHiddenSurfaceRemoval"
|
||||
];
|
||||
|
||||
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 DeleteAsset($AssetID)
|
||||
{
|
||||
$Location = SITE_CONFIG["paths"]["assets"].$AssetID;
|
||||
if (file_exists($Location)) unlink($Location);
|
||||
}
|
||||
|
||||
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(); ?>
|
||||
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
||||
<External>null</External>
|
||||
<External>nil</External>
|
||||
<Item class="<?=$strings[$type]["class"]?>" referent="RBX0">
|
||||
<Properties>
|
||||
<?php if($type == "Decal" || $type == "Face") { ?>
|
||||
<token name="Face">5</token>
|
||||
<string name="Name"><?=$strings[$type]["stringName"]?></string>
|
||||
<float name="Shiny">20</float>
|
||||
<float name="Specular">0</float>
|
||||
<Content name="Texture">
|
||||
<url>%ASSETURL%<?=$assetID?></url>
|
||||
</Content>
|
||||
<?php } else { ?>
|
||||
<Content name="<?=$strings[$type]["contentName"]?>">
|
||||
<url>%ASSETURL%<?=$assetID?></url>
|
||||
</Content>
|
||||
<string name="Name"><?=$strings[$type]["stringName"]?></string>
|
||||
<?php } ?>
|
||||
<bool name="archivable">true</bool>
|
||||
</Properties>
|
||||
</Item>
|
||||
</roblox>
|
||||
<?php return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
class Discord
|
||||
{
|
||||
const WEBHOOK_POLYGON_GITPULL = "https://discord.com/api/webhooks/";
|
||||
const WEBHOOK_POLYGON_JOINLOG = "https://discord.com/api/webhooks/";
|
||||
const WEBHOOK_POLYGON_ERRORLOG = "https://discord.com/api/webhooks/";
|
||||
const WEBHOOK_KUSH = "https://discord.com/api/webhooks/";
|
||||
|
||||
static function IsVerified($UserID)
|
||||
{
|
||||
$IsVerified = db::run("SELECT discordID FROM users WHERE id = :UserID", [":UserID" => $UserID])->fetchColumn();
|
||||
if ($IsVerified === NULL) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static function GetUserInfo($UserID)
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch,
|
||||
[
|
||||
CURLOPT_URL => "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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
Polygon::ImportClass("Discord");
|
||||
|
||||
class ErrorHandler
|
||||
{
|
||||
private $Exception;
|
||||
private $Type;
|
||||
private $String;
|
||||
private $File;
|
||||
private $Line;
|
||||
|
||||
private function GetType($Type)
|
||||
{
|
||||
switch ($Type)
|
||||
{
|
||||
case E_ERROR: case E_USER_ERROR: return "Fatal error";
|
||||
case E_WARNING: case E_USER_WARNING: return "Warning";
|
||||
case E_NOTICE: case E_USER_NOTICE: return "Notice";
|
||||
case E_DEPRECATED: case E_USER_DEPRECATED: return "Deprecated";
|
||||
default: return "Unknown error type $Type";
|
||||
}
|
||||
}
|
||||
|
||||
private function GetVerboseMessage()
|
||||
{
|
||||
$VerboseMessage = "";
|
||||
|
||||
if($this->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);
|
||||
$Message = $this->GetVerboseMessage();
|
||||
$Parameters = $_SERVER["REQUEST_URI"];
|
||||
|
||||
$Log[$LogID] =
|
||||
[
|
||||
"Timestamp" => time(),
|
||||
// "GETParameters" => $_GET,
|
||||
"GETParameters" => $Parameters,
|
||||
"Message" => $Message
|
||||
];
|
||||
|
||||
file_put_contents($LogFile, json_encode($Log));
|
||||
|
||||
Discord::SendToWebhook(["content" => "<@194171603049775113> An unexpected error occurred\nError ID: `{$LogID}`\nTime: `".date('d/m/Y h:i:s A')."`\nParameters: `$Parameters`\nMessage:\n```$Message```"], Discord::WEBHOOK_POLYGON_ERRORLOG, false);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
class Forum
|
||||
{
|
||||
static function GetThreadInfo($id)
|
||||
{
|
||||
return db::run("SELECT * FROM forum_threads WHERE id = :id", [":id" => $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;
|
||||
?>
|
||||
<nav>
|
||||
<ul class="pagination justify-content-end mb-0">
|
||||
<li class="page-item<?=self::$page<=1?' disabled':''?>">
|
||||
<a class="page-link" <?=self::$page>1?'href="'.self::$url.(self::$page-1).'"':''?>aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item<?=self::$page==1?' active':''?>"><a class="page-link"<?=self::$page!=1?' href="'.self::$url.'1" ':''?>>1</a></li>
|
||||
<?php if(self::$pages > 2){ if(self::$page > 3){ ?><li class="page-item disabled"><a class="page-link" href="#" tabindex="-1">…</a></li><?php } ?>
|
||||
<?php for($i=1; $i<4; $i++){ if(self::$page == $i-1 || self::$pages-self::$page == $i-2) break; ?>
|
||||
<li class="page-item<?=self::$page==self::$pager[$i]?' active':''?>"><a class="page-link"<?=self::$page!=self::$pager[$i]?' href="'.self::$url.self::$pager[$i].'" ':''?>><?=number_format(self::$pager[$i])?></a></li>
|
||||
<?php } ?>
|
||||
<?php if(self::$page < self::$pages-2){ ?><li class="page-item disabled"><a class="page-link" href="#" tabindex="-1">…</a></li><?php } } ?>
|
||||
<li class="page-item<?=self::$page==self::$pages?' active':''?>"><a class="page-link"<?=self::$page!=self::$pages?' href="'.self::$url.self::$pages.'" ':''?>><?=number_format(self::$pages)?></a></li>
|
||||
<li class="page-item<?=self::$page>self::$pages?' disabled':''?>">
|
||||
<a class="page-link" <?=self::$page<self::$pages?'href="'.self::$url.(self::$page+1).'"':''?>aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
class Games
|
||||
{
|
||||
static function GetServerInfo($id, $UserID = 0, $CheckIfWhitelisted = false)
|
||||
{
|
||||
if ($CheckIfWhitelisted)
|
||||
{
|
||||
return db::run(
|
||||
"SELECT selfhosted_servers.*,
|
||||
users.username, users.jointime,
|
||||
(SELECT COUNT(DISTINCT uid) FROM client_sessions WHERE ping+35 > UNIX_TIMESTAMP() AND serverID = :id AND valid AND verified) AS players,
|
||||
(ping+35 > UNIX_TIMESTAMP()) AS online
|
||||
FROM selfhosted_servers
|
||||
INNER JOIN users ON users.id = hoster
|
||||
WHERE selfhosted_servers.id = :id AND (Privacy = \"Public\" OR hoster = :UserID OR JSON_CONTAINS(PrivacyWhitelist, :UserID, \"$\"))",
|
||||
[":id" => $id, ":UserID" => $UserID]
|
||||
)->fetch(PDO::FETCH_OBJ);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db::run(
|
||||
"SELECT selfhosted_servers.*,
|
||||
users.username, users.jointime,
|
||||
(SELECT COUNT(DISTINCT uid) FROM client_sessions WHERE ping+35 > UNIX_TIMESTAMP() AND serverID = :id AND valid AND verified) 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]);
|
||||
}
|
||||
|
||||
static function GetPlayerCountInServer($ServerID)
|
||||
{
|
||||
$PlayerCount = db::run(
|
||||
"SELECT COUNT(DISTINCT uid) FROM client_sessions WHERE ping+35 > UNIX_TIMESTAMP() AND serverID = :ServerID AND valid AND verified)",
|
||||
[":ServerID" => $ServerID]
|
||||
)->fetchColumn();
|
||||
|
||||
return (int) $PlayerCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
class Groups
|
||||
{
|
||||
static function GetGroupInfo($GroupID, $Barebones = false, $Force = false)
|
||||
{
|
||||
if($Barebones)
|
||||
{
|
||||
$GroupInfo = db::run("SELECT * FROM groups WHERE groups.id = :id", [":id" => $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]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
class Gzip
|
||||
{
|
||||
// this is to compress models and places to help conserve space
|
||||
// this should be used only for models and places, nothing else
|
||||
|
||||
//http://stackoverflow.com/questions/6073397/how-do-you-create-a-gz-file-using-php
|
||||
static function Compress(string $inFilename, int $level = 9): string
|
||||
{
|
||||
// Is the file gzipped already?
|
||||
$extension = pathinfo($inFilename, PATHINFO_EXTENSION);
|
||||
if ($extension == "gz") { return $inFilename; }
|
||||
|
||||
// Open input file
|
||||
$inFile = fopen($inFilename, "rb");
|
||||
if ($inFile === false) { throw new \Exception("Unable to open input file: $inFilename"); }
|
||||
|
||||
// Open output file
|
||||
$gzFilename = $inFilename.".gz";
|
||||
$gzFile = gzopen($gzFilename, "wb".$level);
|
||||
if ($gzFile === false)
|
||||
{
|
||||
fclose($inFile);
|
||||
throw new \Exception("Unable to open output file: $gzFilename");
|
||||
}
|
||||
|
||||
// Stream copy
|
||||
$length = 65536 * 1024; // 512 kB
|
||||
while (!feof($inFile)) { gzwrite($gzFile, fread($inFile, $length)); }
|
||||
|
||||
// Close files
|
||||
fclose($inFile);
|
||||
gzclose($gzFile);
|
||||
|
||||
// Return the new filename
|
||||
//delete original
|
||||
unlink($inFilename);
|
||||
rename($gzFilename, $inFilename);
|
||||
return $gzFilename;
|
||||
}
|
||||
|
||||
static function Decompress($filename, $buffer_size = 8192)
|
||||
{
|
||||
$buffer = "";
|
||||
$file = gzopen($filename, 'rb');
|
||||
while(!gzeof($file)) { $buffer .= gzread($file, $buffer_size); }
|
||||
gzclose($file);
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
class Image
|
||||
{
|
||||
static function Process($handle, $options)
|
||||
{
|
||||
$image = $options["image"] ?? true;
|
||||
$resize = $options["resize"] ?? true;
|
||||
$keepRatio = $options["keepRatio"] ?? false;
|
||||
$scaleX = $options["scaleX"] ?? false;
|
||||
$scaleY = $options["scaleY"] ?? false;
|
||||
|
||||
$handle->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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
class Messages
|
||||
{
|
||||
static function getMessageInfoFromId($MessageId)
|
||||
{
|
||||
return db::run("SELECT * FROM messages WHERE ID = :msgId", [":msgId" => $MessageId])->fetch(PDO::FETCH_OBJ);
|
||||
}
|
||||
static function getAllSentMessages($userId)
|
||||
{
|
||||
return db::run("SELECT * FROM messages WHERE SenderID = :uId", [":uId" => $userId]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
class RBXClient
|
||||
{
|
||||
private static array $brickcolors =
|
||||
[
|
||||
"F2F3F3" => 1, "A1A5A2" => 2, "F9E999" => 3, "D7C59A" => 5, "C2DAB8" => 6, "E8BAC8" => 9, "80BBDC" => 11, "CB8442" => 12, "CC8E69" => 18, "C4281C" => 21, "C470A0" => 22, "0D69AC" => 23, "F5CD30" => 24, "624732" => 25, "1B2A35" => 26, "6D6E6C" => 27, "287F47" => 28, "A1C48C" => 29, "F3CF9B" => 36, "4B974B" => 37, "A05F35" => 38, "C1CADE" => 39, "ECECEC" => 40, "CD544B" => 41, "C1DFF0" => 42, "7BB6E8" => 43, "F7F18D" => 44, "B4D2E4" => 45, "D9856C" => 47, "84B68D" => 48, "F8F184" => 49, "ECE8DE" => 50, "EEC4B6" => 100, "DA867A" => 101, "6E99CA" => 102, "C7C1B7" => 103, "6B327C" => 104, "E29B40" => 105, "DA8541" => 106, "008F9C" => 107, "685C43" => 108, "435493" => 110, "BFB7B1" => 111, "6874AC" => 112, "E5ADC8" => 113, "C7D23C" => 115, "55A5AF" => 116, "B7D7D5" => 118, "A4BD47" => 119, "D9E4A7" => 120, "E7AC58" => 121, "D36F4C" => 123, "923978" => 124, "EAB892" => 125, "A5A5CB" => 126, "DCBC81" => 127, "AE7A59" => 128, "9CA3A8" => 131, "D5733D" => 133, "D8DD56" => 134, "74869D" => 135, "877C90" => 136, "E09864" => 137, "958A73" => 138, "203A56" => 140, "27462D" => 141, "CFE2F7" => 143, "7988A1" => 145, "958EA3" => 146, "938767" => 147, "575857" => 148, "161D32" => 149, "ABADAC" => 150, "789082" => 151, "957977" => 153, "7B2E2F" => 154, "FFF67B" => 157, "E1A4C2" => 158, "756C62" => 168, "97695B" => 176, "B48455" => 178, "898788" => 179, "D7A94B" => 180, "F9D62E" => 190, "E8AB2D" => 191, "694028" => 192, "CF6024" => 193, "A3A2A5" => 194, "4667A4" => 195, "23478B" => 196, "8E4285" => 198, "635F62" => 199, "828A5D" => 200, "E5E4DF" => 208, "B08E44" => 209, "709578" => 210, "79B5B5" => 211, "9FC3E9" => 212, "6C81B7" => 213, "904C2A" => 216, "7C5C46" => 217, "96709F" => 218, "6B629B" => 219, "A7A9CE" => 220, "CD6298" => 221, "E4ADC8" => 222, "DC9095" => 223, "F0D5A0" => 224, "EBB87F" => 225, "FDEA8D" => 226, "7DBBDD" => 232, "342B75" => 268, "506D54" => 301, "5B5D69" => 302, "0010B0" => 303, "2C651D" => 304, "527CAE" => 305, "335882" => 306, "102ADC" => 307, "3D1585" => 308, "348E40" => 309, "5B9A4C" => 310, "9FA1AC" => 311, "592259" => 312, "1F801D" => 313, "9FADC0" => 314, "0989CF" => 315, "7B007B" => 316, "7C9C6B" => 317, "8AAB85" => 318, "B9C4B1" => 319, "CACBD1" => 320, "A75E9B" => 321, "7B2F7B" => 322, "94BE81" => 323, "A8BD99" => 324, "DFDFDE" => 325, "970000" => 327, "B1E5A6" => 328, "98C2DB" => 329, "FF98DC" => 330, "FF5959" => 331, "750000" => 332, "EFB838" => 333, "F8D96D" => 334, "E7E7EC" => 335, "C7D4E4" => 336, "FF9494" => 337, "BE6862" => 338, "562424" => 339, "F1E7C7" => 340, "FEF3BB" => 341, "E0B2D0" => 342, "D490BD" => 343, "965555" => 344, "8F4C2A" => 345, "D3BE96" => 346, "E2DCBC" => 347, "EDEAEA" => 348, "E9DADA" => 349, "883E3E" => 350, "BC9B5D" => 351, "C7AC78" => 352, "CABFA3" => 353, "BBB3B2" => 354, "6C584B" => 355, "A0844F" => 356, "958988" => 357, "ABA89E" => 358, "AF9483" => 359, "966766" => 360, "564236" => 361, "7E683F" => 362, "69665C" => 363, "5A4C42" => 364, "6A3909" => 365, "F8F8F8" => 1001, "CDCDCD" => 1002, "111111" => 1003, "FF0000" => 1004, "FFB000" => 1005, "B480FF" => 1006, "A34B4B" => 1007, "C1BE42" => 1008, "FFFF00" => 1009, "0000FF" => 1010, "002060" => 1011, "2154B9" => 1012, "04AFEC" => 1013, "AA5500" => 1014, "AA00AA" => 1015, "FF66CC" => 1016, "FFAF00" => 1017, "12EED4" => 1018, "00FFFF" => 1019, "00FF00" => 1020, "3A7D15" => 1021, "7F8E64" => 1022, "8C5B9F" => 1023, "AFDDFF" => 1024, "FFC9C9" => 1025, "B1A7FF" => 1026, "9FF3E9" => 1027, "CCFFCC" => 1028, "FFFFCC" => 1029, "FFCC99" => 1030, "6225D1" => 1031, "FF00BF" => 1032
|
||||
];
|
||||
|
||||
static function CryptGetSignature($data)
|
||||
{
|
||||
openssl_sign($data, $signature, openssl_pkey_get_private("file://".ROOT."/../polygon_private.pem"));
|
||||
return base64_encode($signature);
|
||||
}
|
||||
|
||||
static function CryptSignScript($data, $assetID = false)
|
||||
{
|
||||
if($assetID) $data = "%{$assetID}%\n{$data}";
|
||||
else $data = "\n{$data}";
|
||||
$signedScript = "%" . self::CryptGetSignature($data) . "%{$data}";
|
||||
return $signedScript;
|
||||
}
|
||||
|
||||
static function HexToBrickColor($hex)
|
||||
{
|
||||
return self::$brickcolors[$hex] ?? false;
|
||||
}
|
||||
|
||||
static function BrickColorToHex($brickcolor)
|
||||
{
|
||||
return array_flip(self::$brickcolors)[$brickcolor] ?? false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
class System
|
||||
{
|
||||
static function GetFileSize($bytes, $binaryPrefix = true)
|
||||
{
|
||||
$unit=array('B','KB','MB','GB','TB','PB');
|
||||
if (!$bytes) return '0 ' . $unit[0];
|
||||
if ($binaryPrefix) return round($bytes/pow(1024,($i=floor(log($bytes,1024)))),2) .' '. ($unit[$i] ?? 'B');
|
||||
return round($bytes/pow(1000,($i=floor(log($bytes,1000)))),2) .' '. ($unit[$i] ?? 'B');
|
||||
}
|
||||
|
||||
static function GetFolderSize($path, $raw = false)
|
||||
{
|
||||
$io = popen("du -sb $path", "r");
|
||||
$size = (int)filter_var(explode($path, fgets($io, 4096), 2)[0], FILTER_SANITIZE_NUMBER_INT);
|
||||
pclose($io);
|
||||
|
||||
if($raw) return $size;
|
||||
return self::getFileSize($size);
|
||||
}
|
||||
|
||||
static function GetMemoryUsage()
|
||||
{
|
||||
$lines = explode("\n", file_get_contents('/proc/meminfo'));
|
||||
$total = (int) filter_var($lines[0], FILTER_SANITIZE_NUMBER_INT);
|
||||
$free = (int) filter_var($lines[1], FILTER_SANITIZE_NUMBER_INT);
|
||||
return (object)["total" => $total*1024, "free" => $free*1024];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
class Thumbnails
|
||||
{
|
||||
// this is for use with the new polygon cdn
|
||||
|
||||
// currently this just calculates the sha1 hash
|
||||
// of the user's current thumbnail on the fly
|
||||
|
||||
// from what ive seen it doesnt affect performance
|
||||
// too much but ideally i would have the hash cached
|
||||
// in the database, but for now this should do fine
|
||||
|
||||
private static string $BaseURL = "https://polygoncdn.pizzaboxer.xyz/";
|
||||
|
||||
private static array $StatusThumbnails =
|
||||
[
|
||||
"pending-100x100.png" => "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($Location)
|
||||
{
|
||||
$ThumbnailHash = sha1_file($Location);
|
||||
$CDNLocation = $_SERVER["DOCUMENT_ROOT"]."/../polygoncdn/{$ThumbnailHash}.png";
|
||||
|
||||
if (!file_exists($CDNLocation)) self::UploadToCDN($Location);
|
||||
return self::$BaseURL.$ThumbnailHash.".png";
|
||||
}
|
||||
|
||||
static function GetStatus($status, $x, $y)
|
||||
{
|
||||
return self::$BaseURL.self::$StatusThumbnails["{$status}-{$x}x{$y}.png"].".png";
|
||||
}
|
||||
|
||||
static function UploadToCDN($Location)
|
||||
{
|
||||
$ThumbnailHash = sha1_file($Location);
|
||||
file_put_contents($_SERVER["DOCUMENT_ROOT"]."/../polygoncdn/{$ThumbnailHash}.png", file_get_contents($Location));
|
||||
}
|
||||
|
||||
static function DeleteFromCDN($Location)
|
||||
{
|
||||
$ThumbnailHash = sha1_file($Location);
|
||||
$CDNLocation = $_SERVER["DOCUMENT_ROOT"]."/../polygoncdn/{$ThumbnailHash}.png";
|
||||
|
||||
if (file_exists($CDNLocation)) unlink($CDNLocation);
|
||||
}
|
||||
|
||||
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;
|
||||
$Location = SITE_CONFIG['paths']['thumbs_assets']."{$AssetID}-{$x}x{$y}.png";
|
||||
|
||||
if ($Force) $SQLResult->approved = 1;
|
||||
|
||||
if ($SQLResult->approved == 0) return self::GetStatus("pending", $x, $y);
|
||||
if ($SQLResult->approved == 2) return self::GetStatus("unapproved", $x, $y);
|
||||
|
||||
if (!file_exists($Location)) return self::GetStatus("rendering", $x, $y);
|
||||
if ($SQLResult->approved == 1) return self::GetCDNLocation($Location);
|
||||
|
||||
return self::GetStatus("rendering", $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, $force = false)
|
||||
{
|
||||
if(!$force && !SESSION) return self::GetStatus("rendering", $x, $y);
|
||||
|
||||
$Location = SITE_CONFIG['paths']['thumbs_avatars']."{$avatarID}-{$x}x{$y}.png";
|
||||
|
||||
if(!file_exists($Location)) return self::GetStatus("rendering", $x, $y);
|
||||
return self::GetCDNLocation($Location);
|
||||
}
|
||||
|
||||
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 DeleteAsset($AssetID)
|
||||
{
|
||||
$Thumbnails = glob(SITE_CONFIG["paths"]["thumbs_assets"]."{$AssetID}-*.png");
|
||||
|
||||
foreach ($Thumbnails as $Thumbnail)
|
||||
{
|
||||
self::DeleteFromCDN($Thumbnail);
|
||||
unlink($Thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
class TwoFactorAuth
|
||||
{
|
||||
static function Initialize()
|
||||
{
|
||||
require ROOT.'/api/private/vendors/2fa/FixedBitNotation.php';
|
||||
require ROOT.'/api/private/vendors/2fa/GoogleQrUrl.php';
|
||||
require ROOT.'/api/private/vendors/2fa/GoogleAuthenticatorInterface.php';
|
||||
require ROOT.'/api/private/vendors/2fa/GoogleAuthenticator.php';
|
||||
return new \Google\Authenticator\GoogleAuthenticator();
|
||||
}
|
||||
|
||||
static function Toggle()
|
||||
{
|
||||
if(!SESSION) return false;
|
||||
|
||||
db::run(
|
||||
"UPDATE users SET twofa = :2fa WHERE id = :uid",
|
||||
[":2fa" => (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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
try
|
||||
{
|
||||
$pdo = new PDO(
|
||||
'mysql:host='.SITE_CONFIG["database"]["host"].';
|
||||
dbname='.SITE_CONFIG["database"]["schema"].';
|
||||
charset=utf8mb4',
|
||||
SITE_CONFIG["database"]["username"],
|
||||
SITE_CONFIG["database"]["password"]
|
||||
);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
catch(PDOException $e)
|
||||
{
|
||||
echo "Project Polygon is currently undergoing maintenance. We will be back soon!";
|
||||
if(isset($_GET['showError'])) echo $e->getMessage();
|
||||
die();
|
||||
}
|
||||
|
||||
class db
|
||||
{
|
||||
static function run($sql, $params = false)
|
||||
{
|
||||
global $pdo;
|
||||
if(!$params) return $pdo->query($sql);
|
||||
|
||||
$query = $pdo->prepare($sql);
|
||||
|
||||
foreach ($params as $param => $value)
|
||||
{
|
||||
$query->bindValue($param, $value, is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR);
|
||||
}
|
||||
|
||||
$query->execute();
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,593 @@
|
|||
<?php
|
||||
|
||||
class pageBuilder
|
||||
{
|
||||
public static string $additionalFooterStuff = "";
|
||||
|
||||
public static array $JSdependencies =
|
||||
[
|
||||
"https://code.jquery.com/jquery-3.0.0.min.js",
|
||||
"/js/toastr.js"
|
||||
];
|
||||
|
||||
// this is separate from js dependencies as these MUST be loaded at the bottom
|
||||
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=15"
|
||||
];
|
||||
|
||||
public static array $pageConfig =
|
||||
[
|
||||
"title" => false,
|
||||
"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://polygon.pizzaboxer.xyz/img/ProjectPolygon.png",
|
||||
"includeNav" => true,
|
||||
"includeFooter" => true,
|
||||
"app-attributes" => ""
|
||||
];
|
||||
|
||||
// todo - in retrospect this shouldnt even be a thing and so
|
||||
// whatever uses this probably smells so uh should work on that
|
||||
static function showStaticNotification($type, $text)
|
||||
{
|
||||
self::$additionalFooterStuff .= '<script type="text/javascript">$(function(){ toastr["'.$type.'"]("'.$text.'"); });</script>';
|
||||
}
|
||||
|
||||
// todo - change this to use the newer polygon.buildmodal function
|
||||
static function showStaticModal($options)
|
||||
{
|
||||
//$body = trim(preg_replace('/\s\s+/', ' ', $body));
|
||||
//$body = str_replace('"', '\"', $body);
|
||||
self::$additionalFooterStuff .= '<script type="text/javascript">$(function(){ polygon.buildModal('.json_encode($options).'); });</script>';
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if(SESSION["adminLevel"])
|
||||
{
|
||||
$pendingAssets = db::run("SELECT COUNT(*) FROM assets WHERE NOT approved AND type != 1")->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();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="polygon-csrf" content="<?=SESSION?SESSION["csrfToken"]:'false'?>">
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/img/ProjectPolygon.ico' />
|
||||
<title><?=(self::$pageConfig["title"] ? self::$pageConfig["title"] . " - " : "") . SITE_CONFIG["site"]["name"]?></title>
|
||||
<meta name="theme-color" content="#eb4034">
|
||||
<meta property="og:title" content="<?=self::$pageConfig["title"]?>">
|
||||
<meta property="og:site_name" content="<?=self::$pageConfig["og:site_name"]?>">
|
||||
<meta property="og:url" content="<?=self::$pageConfig["og:url"]?>">
|
||||
<meta property="og:description" content="<?=self::$pageConfig["og:description"]?>">
|
||||
<meta property="og:type" content="Website">
|
||||
<meta property="og:image" content="<?=self::$pageConfig["og:image"]?>">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
|
||||
<?php foreach(self::$CSSdependencies as $url){ ?>
|
||||
<link rel="stylesheet" href="<?=$url?>">
|
||||
<?php } foreach(self::$JSdependencies as $url){ ?>
|
||||
<script type="text/javascript" src="<?=$url?>"></script>
|
||||
<?php } ?>
|
||||
<script>
|
||||
var polygon = {};
|
||||
|
||||
polygon.user =
|
||||
{
|
||||
<?php if(SESSION) { ?>
|
||||
logged_in: true,
|
||||
name: "<?=SESSION["userName"]?>",
|
||||
id: <?=SESSION["userId"]?>,
|
||||
money: <?=SESSION["currency"]?>,
|
||||
<?php } else { ?>
|
||||
logged_in: false,
|
||||
<?php } ?>
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<?php if($theme == "2014") { ?>
|
||||
<body class="layout-2014 mt-5">
|
||||
<div class="nav-container no-gutter-ads">
|
||||
<div class="navigation" id="navigation" onselectstart="return false;">
|
||||
<div class="navigation-container">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="user">
|
||||
<div class="menu-item">
|
||||
<div class="username"><a href="/user?ID=<?=SESSION["userId"]?>"><?=SESSION["userName"]?></a></div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav2014-my-roblox">
|
||||
<a class="menu-item" href="/home">
|
||||
<span class="icon"></span>Home
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav2014-profile">
|
||||
<a class="menu-item" href="/user">
|
||||
<span class="icon"></span>Profile
|
||||
</a>
|
||||
</li>
|
||||
<!--li class="nav2014-messages">
|
||||
<a class="menu-item" href="/user">
|
||||
<span class="icon"></span>Messages
|
||||
</a>
|
||||
</li-->
|
||||
<li class="nav2014-friends">
|
||||
<a class="menu-item" href="/friends">
|
||||
<span class="icon"></span>Friends
|
||||
<span class="notification-icon text-light friend-requests-indicator<?=!SESSION["friendRequests"]?' d-none':''?>"><?=SESSION["friendRequests"]?></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav2014-character">
|
||||
<a class="menu-item" href="/my/character">
|
||||
<span class="icon"></span>Character
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav2014-inventory">
|
||||
<a class="menu-item" href="/my/stuff">
|
||||
<span class="icon"></span>Inventory
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav2014-develop">
|
||||
<a class="menu-item" href="/develop">
|
||||
<span class="icon"></span>Develop
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav2014-groups">
|
||||
<a class="menu-item" href="/groups">
|
||||
<span class="icon"></span>Groups
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav2014-forum">
|
||||
<a class="menu-item" href="/forum">
|
||||
<span class="icon"></span>Forum
|
||||
</a>
|
||||
</li>
|
||||
<?php if(SESSION["adminLevel"]) { ?>
|
||||
<li class="nav2014-profile">
|
||||
<a class="menu-item" href="/admin">
|
||||
<span class="icon"></span>Admin
|
||||
<span class="notification-icon text-light <?=!$pendingAssets?' d-none':''?>"><?=$pendingAssets?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<!--li class="nav2014-blog">
|
||||
<a class="menu-item" href="https://web.archive.org/web/20140821165031/http://blog.roblox.com/">
|
||||
<span class="icon"></span>Blog
|
||||
</a>
|
||||
</li>
|
||||
<li class="upgrade-now">
|
||||
<a href="/web/20140821165031/http://www.roblox.com/Upgrades/BuildersClubMemberships.aspx" class="nav-button" id="builders-club-button">Upgrade Now</a>
|
||||
</li>
|
||||
<li class="nav2014-events">
|
||||
<span class="events-text">Events</span>
|
||||
</li>
|
||||
<li class="nav2014-sponsor">
|
||||
<a class="menu-item" href="/web/20140821165031/http://www.roblox.com/event/clanbattle" title="Clan Battle">
|
||||
<img src="https://web.archive.org/web/20140821165031im_/http://images.rbxcdn.com/6d4be1b1b1e67971c837da0dae72e82d">
|
||||
</a>
|
||||
</li-->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark navbar-orange header-2014 fixed-top py-0">
|
||||
<div class="nav-icon" onselectstart="return false;"></div>
|
||||
<a class="navbar-brand pt-0" href="/"><img src="/img/2013/roblox_logo.png"></a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#primaryNavbar" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="primaryNavbar">
|
||||
<ul class="navbar-nav header-links mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/games">Games</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/catalog">Catalog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/develop">Develop</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/forum">Forum</a>
|
||||
</li>
|
||||
<div class="search">
|
||||
<div class="search-input-container">
|
||||
<input type="text" placeholder="Search">
|
||||
</div>
|
||||
<div class="search-icon"></div>
|
||||
<div class="universal-search-dropdown">
|
||||
<div class="universal-search-option" data-searchurl="/games?Keyword=">
|
||||
<div class="universal-search-text">Search <span class="universal-search-string"></span> in Games</div>
|
||||
</div>
|
||||
<div class="universal-search-option" data-searchurl="/browse?Category=People&SearchTextBox=">
|
||||
<div class="universal-search-text">Search <span class="universal-search-string"></span> in People</div>
|
||||
</div>
|
||||
<div class="universal-search-option selected" data-searchurl="/browse?Category=Groups&SearchTextBox=">
|
||||
<div class="universal-search-text">Search <span class="universal-search-string"></span> in Groups</div>
|
||||
</div>
|
||||
<div class="universal-search-option" data-searchurl="/catalog?Keyword=">
|
||||
<div class="universal-search-text">Search <span class="universal-search-string"></span> in Catalog</div>
|
||||
</div>
|
||||
<div class="universal-search-option" data-searchurl="/catalog?CurrencyType=0&SortType=1&Category=6&Keyword=">
|
||||
<div class="universal-search-text">Search <span class="universal-search-string"></span> in Library</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
<div class="navbar-nav">
|
||||
<div class="navbar-button-container">
|
||||
<a class="btn btn-sm mr-4" data-toggle="tooltip" data-html="true" data-placement="bottom" title="<?=SESSION["currency"]?> <?=SITE_CONFIG["site"]["currency"]?> <br> Next stipend in <?=GetReadableTime(SESSION["nextCurrencyStipend"], ["Full" => true, "Ending" => false, "Abbreviate" => true])?>" href="/my/money"><i class="fas fa-pizza-slice mr-1"></i> <?=SESSION["currency"]?></a>
|
||||
</div>
|
||||
<div class="navbar-button-container dropdown-hover">
|
||||
<a class="btn btn-sm" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fas fa-cog"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right mx-2" aria-labelledby="settings-dropdown">
|
||||
<a class="dropdown-item" href="/my/account">Settings</a>
|
||||
<a class="dropdown-item" href="/logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<noscript>
|
||||
<div id="ctl00_Announcement">
|
||||
<div id="ctl00_SystemAlertDiv" class="SystemAlert" style="background-color:red">
|
||||
<div id="ctl00_SystemAlertTextColor" class="SystemAlertText">
|
||||
<div id="ctl00_LabelAnnouncement">Please enable Javascript to use all the features on this site.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
<?php foreach($announcements as $announcement) { ?>
|
||||
<div id="ctl00_Announcement">
|
||||
<div id="ctl00_SystemAlertDiv" class="SystemAlert" style="background-color:<?=$announcement["bgcolor"]?>">
|
||||
<div id="ctl00_SystemAlertTextColor" class="SystemAlertText">
|
||||
<div id="ctl00_LabelAnnouncement"><?=$markdown->line($announcement["text"])?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } /* foreach($announcements as $announcement) */ ?>
|
||||
<div class="app container py-4 nav-content"<?=self::$pageConfig['app-attributes']?>>
|
||||
<?php } else { ?>
|
||||
<body>
|
||||
<?php if(self::$pageConfig["includeNav"]) { ?>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark navbar-orange navbar-top py-0">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/"><?=SITE_CONFIG["site"]["name"]?></a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#primaryNavbar" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="primaryNavbar">
|
||||
<ul class="navbar-nav header-links mr-auto">
|
||||
<?php if(SESSION) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/games">Games</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/catalog">Catalog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/develop">Develop</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/forum">Forum</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/browse">People</a>
|
||||
</li>
|
||||
<?php if(SESSION) { ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="moreDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">More</a>
|
||||
<div class="dropdown-menu mt-0" aria-labelledby="moreDropdown">
|
||||
<!--a class="dropdown-item" href="/browse">People</a>
|
||||
<div class="dropdown-divider"></div-->
|
||||
<a class="dropdown-item" href="https://discord.com/invite/ZXbhxcQfUW">Discord</a>
|
||||
<!--a class="dropdown-item" href="https://twitter.com/boxerpizza">Twitter</a-->
|
||||
</div>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
<div class="navbar-nav">
|
||||
<?php if(SESSION) { ?>
|
||||
<a class="nav-link mr-2" href="/user?ID=<?=SESSION["userId"]?>"><?=SESSION["userName"]?></a>
|
||||
<?php if (Users::IsAdmin()) { ?>
|
||||
<div class="navbar-button-container">
|
||||
<a class="btn btn-sm btn-light py-0 px-1 unread-messages-indicator<?=!SESSION["unreadMessages"]?' d-none':''?>"><?=SESSION["unreadMessages"]?></a>
|
||||
<a class="btn btn-sm btn-outline-light my-1 mr-2" title="Messages" href="/my/messages" data-toggle="tooltip" data-html="true" data-placement="bottom">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</a>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<div class="navbar-button-container">
|
||||
<a class="btn btn-sm btn-light py-0 px-1 friend-requests-indicator<?=!SESSION["friendRequests"]?' d-none':''?>"><?=SESSION["friendRequests"]?></a>
|
||||
<a class="btn btn-sm btn-outline-light my-1 mr-2" title="My Friends" href="/friends" data-toggle="tooltip" data-html="true" data-placement="bottom">
|
||||
<i class="fas fa-user-friends"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-button-container">
|
||||
<a class="btn btn-sm btn-outline-light my-1 mr-2" data-toggle="tooltip" data-html="true" data-placement="bottom" title="<?=SESSION["currency"]?> <?=SITE_CONFIG["site"]["currency"]?> <br> Next stipend in <?=GetReadableTime(SESSION["nextCurrencyStipend"], ["Full" => true, "Ending" => false, "Abbreviate" => true])?>" href="/my/money"><i class="fal fa-pizza-slice"></i> <?=SESSION["currency"]?></a>
|
||||
</div>
|
||||
<div class="navbar-button-container">
|
||||
<a class="btn btn-sm btn-light my-1 mr-2 px-3" href="/logout">Logout</a>
|
||||
</div>
|
||||
<?php } else { ?>
|
||||
<a class="nav-link" href="/">Sign Up</a>
|
||||
<span class="nav-link darken px-0">or</span>
|
||||
<a class="btn btn-sm btn-light my-1 mx-2 px-4" href="/login">Login</a>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php if(SESSION) { ?>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark navbar-top bg-dark py-0">
|
||||
<div class="container">
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#secondaryNavbar" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="secondaryNavbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/user">Profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/my/character">Character</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/friends">Friends</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/groups">Groups</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/my/stuff">Inventory</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/my/money">Money</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/my/account">Account</a>
|
||||
</li>
|
||||
<?php if(SESSION && SESSION["adminLevel"]) { ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link py-1" href="/admin">Admin <span class="btn btn-sm btn-outline-light py-0<?=!$pendingAssets?' d-none':''?>" style="margin-top:-3px"><?=$pendingAssets?></span></a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php } /* if(SESSION) */ ?>
|
||||
</div>
|
||||
<noscript>
|
||||
<div class="alert py-2 mb-0 rounded-0 text-center text-light bg-danger" role="alert">
|
||||
disabling javascript breaks the ux in half so dont do it pls
|
||||
</div>
|
||||
</noscript>
|
||||
<?php foreach($announcements as $announcement) { ?>
|
||||
<div class="alert py-2 mb-0 rounded-0 text-center text-<?=$announcement["textcolor"]?>" role="alert" style="background-color: <?=$announcement["bgcolor"]?>">
|
||||
<?=$markdown->text($announcement["text"])?>
|
||||
</div>
|
||||
<?php } /* foreach($announcements as $announcement) */ ?>
|
||||
<?php } /* if(self::$pageConfig["includeNav"]) */ ?>
|
||||
<div class="app container py-4"<?=self::$pageConfig['app-attributes']?>>
|
||||
<?php } /* if($theme == "2014") */ ?>
|
||||
<?php } static function buildFooter() { ?>
|
||||
</div>
|
||||
<?php if(self::$pageConfig["includeFooter"]) { ?>
|
||||
<nav class="footer navbar navbar-light navbar-orange">
|
||||
<div class="container py-2 text-light text-center">
|
||||
<!--div class="row" style="width:100%">
|
||||
<div class="col-sm-3 text-center pt-1" style="border-right: 1px solid #ccc;">
|
||||
<h3 class="font-weight-normal"><?=SITE_CONFIG["site"]["name"]?></h3>
|
||||
</div>
|
||||
<div class="col-sm-9 pl-4">
|
||||
<p class="font-weight-normal mb-0 pl-2">made by pizzaboxer</p>
|
||||
<p class="font-weight-normal mb-0 pl-2">copyright © <?=SITE_CONFIG["site"]["name"]?> 2020</p>
|
||||
</div>
|
||||
</div-->
|
||||
<div class="mx-auto">
|
||||
<span><small class="px-2">Copyright © <?=SITE_CONFIG["site"]["name"]?> 2020-<?=date('Y')?></small> | <small class="px-2"><?=db::run("SELECT COUNT(*)+1 FROM users")->fetchColumn()?> users registered</small> | <a href="/info/privacy" class="text-light px-2">Privacy Policy</a> | <a href="/info/terms-of-service" class="text-light px-2">Terms of Service</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<?php } ?>
|
||||
<div class="global modal fade" tabindex="-1" role="dialog" aria-labelledby="primaryModalCenter" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header card-header bg-cardpanel py-2">
|
||||
<h3 class="col-12 modal-title text-center font-weight-normal"></h3>
|
||||
</div>
|
||||
<div class="modal-body text-center text-break">
|
||||
your smell
|
||||
</div>
|
||||
<div class="modal-footer text-center">
|
||||
<div class="mx-auto">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if(SESSION) { ?>
|
||||
<div class="placelauncher modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content"></div>
|
||||
</div>
|
||||
<div class="launch template d-none">
|
||||
<div class="modal-body text-center">
|
||||
<span class="jumbo spinner-border text-danger mb-3" role="status"></span>
|
||||
<h5 class="font-weight-normal mb-3">Starting <?=SITE_CONFIG["site"]["name"]?>...</h5>
|
||||
<a class="btn btn-sm btn-outline-danger btn-block px-4" data-dismiss="modal">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="install template d-none">
|
||||
<div class="modal-body text-center pb-0">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<img src="/img/ProjectPolygon.png" class="img-fluid pl-3 py-3 pr-1" style="max-width: 150px">
|
||||
<h2 class="font-weight-normal">Welcome to <?=SITE_CONFIG["site"]["name"]?>!</h2>
|
||||
<h5 class="font-weight-normal">Seems like you don't have <span class="year">2010</span> installed</h5>
|
||||
<a class="btn btn-success btn-block mx-auto mt-3 install" style="max-width:18rem">Download</a>
|
||||
</div>
|
||||
<div class="modal-footer text-center py-2">
|
||||
<small class="mx-auto">If you do have the client installed, just ignore this</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
<?php if(SESSION && SESSION["adminLevel"]){ ?>
|
||||
<script>
|
||||
//admin.js
|
||||
if (polygon.admin == undefined) polygon.admin = {};
|
||||
|
||||
polygon.admin.forum =
|
||||
{
|
||||
moderate_post_prompt: function(type, id)
|
||||
{
|
||||
polygon.buildModal({
|
||||
header: "Delete Post",
|
||||
body: 'Are you sure you want to delete this post?',
|
||||
buttons: [{class:'btn btn-danger px-4 post-delete-confirm', attributes:[{attr:'data-type', val:type}, {attr: 'data-id', val:id}], dismiss:true, text:'Yes'}, {class:'btn btn-secondary px-4', dismiss:true, text:'No'}]
|
||||
});
|
||||
},
|
||||
|
||||
moderate_post: function(type, id)
|
||||
{
|
||||
$.post('/api/admin/delete-post', {"postType": type, "postId": id}, function(data)
|
||||
{
|
||||
if(data.success)
|
||||
{
|
||||
toastr["success"]("Post has been deleted");
|
||||
setTimeout(function(){ window.location.reload(); }, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
toastr["error"](data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
polygon.admin.gitpull = function()
|
||||
{
|
||||
polygon.buildModal({
|
||||
header: "<i class=\"fab fa-git-alt text-danger\"></i> Git Pull",
|
||||
body: "<span class=\"spinner-border spinner-border-sm text-danger\" role=\"status\" aria-hidden=\"true\"></span> Executing Git Pull...",
|
||||
buttons:
|
||||
[
|
||||
{class: 'btn btn-outline-primary', dismiss: true, text: 'Close'},
|
||||
{class: 'btn btn-outline-danger disabled', attributes: [{attr: "disabled", val: "disabled"}], text: 'Run Again'}
|
||||
]
|
||||
});
|
||||
|
||||
$.get("/api/admin/git-pull", function(data)
|
||||
{
|
||||
polygon.buildModal({
|
||||
header: "<i class=\"fab fa-git-alt text-danger\"></i> Git Pull",
|
||||
body: "<pre class=\"mb-0\">"+data+"</pre>",
|
||||
buttons:
|
||||
[
|
||||
{class: 'btn btn-outline-primary', dismiss: true, text: 'Close'},
|
||||
{class: 'btn btn-outline-success gitpull', text: 'Run Again'}
|
||||
]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$("body").on("click", ".gitpull", polygon.admin.gitpull);
|
||||
|
||||
$("body").keydown(function(event)
|
||||
{
|
||||
if (event.originalEvent.ctrlKey && event.originalEvent.key == "/") polygon.admin.gitpull();
|
||||
});
|
||||
|
||||
polygon.admin.request_render = function(type, id)
|
||||
{
|
||||
$.post('/api/admin/request-render', {"renderType": type, "assetID": id}, function(data)
|
||||
{
|
||||
if(data.success) toastr["success"](data.message);
|
||||
else toastr["error"](data.message);
|
||||
});
|
||||
}
|
||||
|
||||
$("body").on("click", ".post-delete", function(){ polygon.admin.forum.moderate_post_prompt($(this).attr("data-type"), $(this).attr("data-id")); });
|
||||
$("body").on("click", ".post-delete-confirm", function(){ polygon.admin.forum.moderate_post($(this).attr("data-type"), $(this).attr("data-id")); });
|
||||
$("body").on("click", ".request-render", function(){ polygon.admin.request_render($(this).attr("data-type"), $(this).attr("data-id")); });
|
||||
</script>
|
||||
<?php } ?>
|
||||
<?php foreach(self::$polygonScripts as $url){ ?>
|
||||
<script type="text/javascript" src="<?=$url?>"></script>
|
||||
<?php } ?>
|
||||
<?=self::$additionalFooterStuff?>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
ob_end_flush();
|
||||
}
|
||||
|
||||
static function errorCode($code, $customText = false)
|
||||
{
|
||||
http_response_code($code);
|
||||
|
||||
$text =
|
||||
[
|
||||
400 => ["title" => "Bad request", "text" => "There was a problem with your request"],
|
||||
404 => ["title" => "Requested page not found", "text" => "You may have clicked an expired link or mistyped the address"],
|
||||
420 => ["title" => "Website is currently under maintenance", "text" => "check back later"],
|
||||
500 => ["title" => "Unexpected error with your request", "text" => "Please try again after a few moments"]
|
||||
];
|
||||
|
||||
if(!isset($text[$code])) $code = 500;
|
||||
|
||||
if (is_array($customText) && count($customText)) $text[$code] = $customText;
|
||||
|
||||
self::buildHeader();
|
||||
?>
|
||||
<div class="card mx-auto" style="max-width:640px;">
|
||||
<div class="card-body text-center">
|
||||
<img src="/img/error.png">
|
||||
<h2 class="font-weight-normal"><?=$text[$code]["title"]?></h2>
|
||||
<?=$text[$code]["text"]?>
|
||||
<?php if (!is_array($customText) && !empty($customText) && $code == 500) { ?>
|
||||
<pre class="mt-4"><?=$customText?></pre>
|
||||
<?php } ?>
|
||||
<hr>
|
||||
<a class="btn btn-outline-primary mx-1 mt-1 py-1" onclick="window.history.back()">Go to Previous Page</a>
|
||||
<a class="btn btn-outline-primary mx-1 mt-1 py-1" href="/">Return Home</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
self::buildFooter();
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
define("SITE_CONFIG",
|
||||
[
|
||||
"database" =>
|
||||
[
|
||||
"host" => "127.0.0.1",
|
||||
"schema" => "polygon",
|
||||
"username" => "polygon",
|
||||
"password" => ""
|
||||
],
|
||||
|
||||
"site" =>
|
||||
[
|
||||
"name" => "Project Polygon",
|
||||
"name_secondary" => "Polygon",
|
||||
"currency" => "Pizzas",
|
||||
"private" => true,
|
||||
"games" => true,
|
||||
"thumbserver" => true
|
||||
],
|
||||
|
||||
"keys" => // DO NOT ALTER ANY OF THESE UNLESS NECESSARY
|
||||
[
|
||||
// use \Defuse\Crypto\Key::createNewRandomKey()->saveToAsciiSafeString(); for this
|
||||
"passwordEncryption" => "",
|
||||
"renderserverApi" => "",
|
||||
],
|
||||
|
||||
"api" => // deprecated - use above
|
||||
[
|
||||
"renderserverKey" => ""
|
||||
],
|
||||
|
||||
"paths" =>
|
||||
[
|
||||
"assets" => $_SERVER['DOCUMENT_ROOT']."/asset/files/",
|
||||
"thumbs_assets" => $_SERVER['DOCUMENT_ROOT']."/thumbs/assets/",
|
||||
"thumbs_avatars" => $_SERVER['DOCUMENT_ROOT']."/thumbs/avatars/"
|
||||
]
|
||||
]);
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Sonata Project package.
|
||||
*
|
||||
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Sonata\GoogleAuthenticator;
|
||||
|
||||
/**
|
||||
* FixedBitNotation.
|
||||
*
|
||||
* The FixedBitNotation class is for binary to text conversion. It
|
||||
* can handle many encoding schemes, formally defined or not, that
|
||||
* use a fixed number of bits to encode each character.
|
||||
*
|
||||
* @author Andre DeMarre
|
||||
*/
|
||||
final class FixedBitNotation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $chars;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $bitsPerCharacter;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $radix;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $rightPadFinalBits;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $padFinalGroup;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $padCharacter;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $charmap;
|
||||
|
||||
/**
|
||||
* @param int $bitsPerCharacter Bits to use for each encoded character
|
||||
* @param string $chars Base character alphabet
|
||||
* @param bool $rightPadFinalBits How to encode last character
|
||||
* @param bool $padFinalGroup Add padding to end of encoded output
|
||||
* @param string $padCharacter Character to use for padding
|
||||
*/
|
||||
public function __construct(int $bitsPerCharacter, ?string $chars = null, bool $rightPadFinalBits = false, bool $padFinalGroup = false, string $padCharacter = '=')
|
||||
{
|
||||
// Ensure validity of $chars
|
||||
if (!\is_string($chars) || ($charLength = \strlen($chars)) < 2) {
|
||||
$chars =
|
||||
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
|
||||
$charLength = 64;
|
||||
}
|
||||
|
||||
// Ensure validity of $bitsPerCharacter
|
||||
if ($bitsPerCharacter < 1) {
|
||||
// $bitsPerCharacter must be at least 1
|
||||
$bitsPerCharacter = 1;
|
||||
$radix = 2;
|
||||
} elseif ($charLength < 1 << $bitsPerCharacter) {
|
||||
// Character length of $chars is too small for $bitsPerCharacter
|
||||
// Set $bitsPerCharacter to greatest acceptable value
|
||||
$bitsPerCharacter = 1;
|
||||
$radix = 2;
|
||||
|
||||
while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) {
|
||||
++$bitsPerCharacter;
|
||||
}
|
||||
|
||||
$radix >>= 1;
|
||||
} elseif ($bitsPerCharacter > 8) {
|
||||
// $bitsPerCharacter must not be greater than 8
|
||||
$bitsPerCharacter = 8;
|
||||
$radix = 256;
|
||||
} else {
|
||||
$radix = 1 << $bitsPerCharacter;
|
||||
}
|
||||
|
||||
$this->chars = $chars;
|
||||
$this->bitsPerCharacter = $bitsPerCharacter;
|
||||
$this->radix = $radix;
|
||||
$this->rightPadFinalBits = $rightPadFinalBits;
|
||||
$this->padFinalGroup = $padFinalGroup;
|
||||
$this->padCharacter = $padCharacter[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a string.
|
||||
*
|
||||
* @param string $rawString Binary data to encode
|
||||
*/
|
||||
public function encode($rawString): string
|
||||
{
|
||||
// Unpack string into an array of bytes
|
||||
$bytes = unpack('C*', $rawString);
|
||||
$byteCount = \count($bytes);
|
||||
|
||||
$encodedString = '';
|
||||
$byte = array_shift($bytes);
|
||||
$bitsRead = 0;
|
||||
|
||||
$chars = $this->chars;
|
||||
$bitsPerCharacter = $this->bitsPerCharacter;
|
||||
$rightPadFinalBits = $this->rightPadFinalBits;
|
||||
$padFinalGroup = $this->padFinalGroup;
|
||||
$padCharacter = $this->padCharacter;
|
||||
|
||||
// Generate encoded output;
|
||||
// each loop produces one encoded character
|
||||
for ($c = 0; $c < $byteCount * 8 / $bitsPerCharacter; ++$c) {
|
||||
// Get the bits needed for this encoded character
|
||||
if ($bitsRead + $bitsPerCharacter > 8) {
|
||||
// Not enough bits remain in this byte for the current
|
||||
// character
|
||||
// Save the remaining bits before getting the next byte
|
||||
$oldBitCount = 8 - $bitsRead;
|
||||
$oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount);
|
||||
$newBitCount = $bitsPerCharacter - $oldBitCount;
|
||||
|
||||
if (!$bytes) {
|
||||
// Last bits; match final character and exit loop
|
||||
if ($rightPadFinalBits) {
|
||||
$oldBits <<= $newBitCount;
|
||||
}
|
||||
$encodedString .= $chars[$oldBits];
|
||||
|
||||
if ($padFinalGroup) {
|
||||
// Array of the lowest common multiples of
|
||||
// $bitsPerCharacter and 8, divided by 8
|
||||
$lcmMap = [1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1];
|
||||
$bytesPerGroup = $lcmMap[$bitsPerCharacter];
|
||||
$pads = (int) ($bytesPerGroup * 8 / $bitsPerCharacter
|
||||
- ceil((\strlen($rawString) % $bytesPerGroup)
|
||||
* 8 / $bitsPerCharacter));
|
||||
$encodedString .= str_repeat($padCharacter[0], $pads);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Get next byte
|
||||
$byte = array_shift($bytes);
|
||||
$bitsRead = 0;
|
||||
} else {
|
||||
$oldBitCount = 0;
|
||||
$newBitCount = $bitsPerCharacter;
|
||||
}
|
||||
|
||||
// Read only the needed bits from this byte
|
||||
$bits = $byte >> 8 - ($bitsRead + $newBitCount);
|
||||
$bits ^= $bits >> $newBitCount << $newBitCount;
|
||||
$bitsRead += $newBitCount;
|
||||
|
||||
if ($oldBitCount) {
|
||||
// Bits come from seperate bytes, add $oldBits to $bits
|
||||
$bits = ($oldBits << $newBitCount) | $bits;
|
||||
}
|
||||
|
||||
$encodedString .= $chars[$bits];
|
||||
}
|
||||
|
||||
return $encodedString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string.
|
||||
*
|
||||
* @param string $encodedString Data to decode
|
||||
* @param bool $caseSensitive
|
||||
* @param bool $strict Returns null if $encodedString contains
|
||||
* an undecodable character
|
||||
*/
|
||||
public function decode($encodedString, $caseSensitive = true, $strict = false): string
|
||||
{
|
||||
if (!$encodedString || !\is_string($encodedString)) {
|
||||
// Empty string, nothing to decode
|
||||
return '';
|
||||
}
|
||||
|
||||
$chars = $this->chars;
|
||||
$bitsPerCharacter = $this->bitsPerCharacter;
|
||||
$radix = $this->radix;
|
||||
$rightPadFinalBits = $this->rightPadFinalBits;
|
||||
$padCharacter = $this->padCharacter;
|
||||
|
||||
// Get index of encoded characters
|
||||
if ($this->charmap) {
|
||||
$charmap = $this->charmap;
|
||||
} else {
|
||||
$charmap = [];
|
||||
|
||||
for ($i = 0; $i < $radix; ++$i) {
|
||||
$charmap[$chars[$i]] = $i;
|
||||
}
|
||||
|
||||
$this->charmap = $charmap;
|
||||
}
|
||||
|
||||
// The last encoded character is $encodedString[$lastNotatedIndex]
|
||||
$lastNotatedIndex = \strlen($encodedString) - 1;
|
||||
|
||||
// Remove trailing padding characters
|
||||
while ($encodedString[$lastNotatedIndex] === $padCharacter[0]) {
|
||||
$encodedString = substr($encodedString, 0, $lastNotatedIndex);
|
||||
--$lastNotatedIndex;
|
||||
}
|
||||
|
||||
$rawString = '';
|
||||
$byte = 0;
|
||||
$bitsWritten = 0;
|
||||
|
||||
// Convert each encoded character to a series of unencoded bits
|
||||
for ($c = 0; $c <= $lastNotatedIndex; ++$c) {
|
||||
if (!isset($charmap[$encodedString[$c]]) && !$caseSensitive) {
|
||||
// Encoded character was not found; try other case
|
||||
if (isset($charmap[$cUpper = strtoupper($encodedString[$c])])) {
|
||||
$charmap[$encodedString[$c]] = $charmap[$cUpper];
|
||||
} elseif (isset($charmap[$cLower = strtolower($encodedString[$c])])) {
|
||||
$charmap[$encodedString[$c]] = $charmap[$cLower];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($charmap[$encodedString[$c]])) {
|
||||
$bitsNeeded = 8 - $bitsWritten;
|
||||
$unusedBitCount = $bitsPerCharacter - $bitsNeeded;
|
||||
|
||||
// Get the new bits ready
|
||||
if ($bitsNeeded > $bitsPerCharacter) {
|
||||
// New bits aren't enough to complete a byte; shift them
|
||||
// left into position
|
||||
$newBits = $charmap[$encodedString[$c]] << $bitsNeeded
|
||||
- $bitsPerCharacter;
|
||||
$bitsWritten += $bitsPerCharacter;
|
||||
} elseif ($c !== $lastNotatedIndex || $rightPadFinalBits) {
|
||||
// Zero or more too many bits to complete a byte;
|
||||
// shift right
|
||||
$newBits = $charmap[$encodedString[$c]] >> $unusedBitCount;
|
||||
$bitsWritten = 8; //$bitsWritten += $bitsNeeded;
|
||||
} else {
|
||||
// Final bits don't need to be shifted
|
||||
$newBits = $charmap[$encodedString[$c]];
|
||||
$bitsWritten = 8;
|
||||
}
|
||||
|
||||
$byte |= $newBits;
|
||||
|
||||
if (8 === $bitsWritten || $c === $lastNotatedIndex) {
|
||||
// Byte is ready to be written
|
||||
$rawString .= pack('C', $byte);
|
||||
|
||||
if ($c !== $lastNotatedIndex) {
|
||||
// Start the next byte
|
||||
$bitsWritten = $unusedBitCount;
|
||||
$byte = ($charmap[$encodedString[$c]]
|
||||
^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten;
|
||||
}
|
||||
}
|
||||
} elseif ($strict) {
|
||||
// Unable to decode character; abort
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $rawString;
|
||||
}
|
||||
}
|
||||
|
||||
// NEXT_MAJOR: Remove class alias
|
||||
class_alias('Sonata\GoogleAuthenticator\FixedBitNotation', 'Google\Authenticator\FixedBitNotation', false);
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Sonata Project package.
|
||||
*
|
||||
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Sonata\GoogleAuthenticator;
|
||||
|
||||
/**
|
||||
* @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
*/
|
||||
final class GoogleAuthenticator implements GoogleAuthenticatorInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $passCodeLength;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $secretLength;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $pinModulo;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $instanceTime;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $codePeriod;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $periodSize = 30;
|
||||
|
||||
public function __construct(int $passCodeLength = 6, int $secretLength = 10, ?\DateTimeInterface $instanceTime = null, int $codePeriod = 30)
|
||||
{
|
||||
/*
|
||||
* codePeriod is the duration in seconds that the code is valid.
|
||||
* periodSize is the length of a period to calculate periods since Unix epoch.
|
||||
* periodSize cannot be larger than the codePeriod.
|
||||
*/
|
||||
|
||||
$this->passCodeLength = $passCodeLength;
|
||||
$this->secretLength = $secretLength;
|
||||
$this->codePeriod = $codePeriod;
|
||||
$this->periodSize = $codePeriod < $this->periodSize ? $codePeriod : $this->periodSize;
|
||||
$this->pinModulo = 10 ** $passCodeLength;
|
||||
$this->instanceTime = $instanceTime ?? new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $secret
|
||||
* @param string $code
|
||||
* @param int $discrepancy
|
||||
*/
|
||||
public function checkCode($secret, $code, $discrepancy = 1): bool
|
||||
{
|
||||
/**
|
||||
* Discrepancy is the factor of periodSize ($discrepancy * $periodSize) allowed on either side of the
|
||||
* given codePeriod. For example, if a code with codePeriod = 60 is generated at 10:00:00, a discrepancy
|
||||
* of 1 will allow a periodSize of 30 seconds on either side of the codePeriod resulting in a valid code
|
||||
* from 09:59:30 to 10:00:29.
|
||||
*
|
||||
* The result of each comparison is stored as a timestamp here instead of using a guard clause
|
||||
* (https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html). This is to implement
|
||||
* constant time comparison to make side-channel attacks harder. See
|
||||
* https://cryptocoding.net/index.php/Coding_rules#Compare_secret_strings_in_constant_time for details.
|
||||
* Each comparison uses hash_equals() instead of an operator to implement constant time equality comparison
|
||||
* for each code.
|
||||
*/
|
||||
$periods = floor($this->codePeriod / $this->periodSize);
|
||||
|
||||
$result = 0;
|
||||
for ($i = -$discrepancy; $i < $periods + $discrepancy; ++$i) {
|
||||
$dateTime = new \DateTimeImmutable('@'.($this->instanceTime->getTimestamp() - ($i * $this->periodSize)));
|
||||
$result = hash_equals($this->getCode($secret, $dateTime), $code) ? $dateTime->getTimestamp() : $result;
|
||||
}
|
||||
|
||||
return $result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* NEXT_MAJOR: add the interface typehint to $time and remove deprecation.
|
||||
*
|
||||
* @param string $secret
|
||||
* @param float|string|int|\DateTimeInterface|null $time
|
||||
*/
|
||||
public function getCode($secret, /* \DateTimeInterface */$time = null): string
|
||||
{
|
||||
if (null === $time) {
|
||||
$time = $this->instanceTime;
|
||||
}
|
||||
|
||||
if ($time instanceof \DateTimeInterface) {
|
||||
$timeForCode = floor($time->getTimestamp() / $this->periodSize);
|
||||
} else {
|
||||
@trigger_error(
|
||||
'Passing anything other than null or a DateTimeInterface to $time is deprecated as of 2.0 '.
|
||||
'and will not be possible as of 3.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$timeForCode = $time;
|
||||
}
|
||||
|
||||
$base32 = new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true);
|
||||
$secret = $base32->decode($secret);
|
||||
|
||||
$timeForCode = str_pad(pack('N', $timeForCode), 8, \chr(0), STR_PAD_LEFT);
|
||||
|
||||
$hash = hash_hmac('sha1', $timeForCode, $secret, true);
|
||||
$offset = \ord(substr($hash, -1));
|
||||
$offset &= 0xF;
|
||||
|
||||
$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
|
||||
|
||||
return str_pad((string) ($truncatedHash % $this->pinModulo), $this->passCodeLength, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* NEXT_MAJOR: Remove this method.
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $hostname
|
||||
* @param string $secret
|
||||
*
|
||||
* @deprecated deprecated as of 2.1 and will be removed in 3.0. Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.
|
||||
*/
|
||||
public function getUrl($user, $hostname, $secret): string
|
||||
{
|
||||
@trigger_error(sprintf(
|
||||
'Using %s() is deprecated as of 2.1 and will be removed in 3.0. '.
|
||||
'Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.',
|
||||
__METHOD__
|
||||
), E_USER_DEPRECATED);
|
||||
|
||||
$issuer = \func_get_args()[3] ?? null;
|
||||
$accountName = sprintf('%s@%s', $user, $hostname);
|
||||
|
||||
// manually concat the issuer to avoid a change in URL
|
||||
$url = GoogleQrUrl::generate($accountName, $secret);
|
||||
|
||||
if ($issuer) {
|
||||
$url .= '%26issuer%3D'.$issuer;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function generateSecret(): string
|
||||
{
|
||||
return (new FixedBitNotation(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', true, true))
|
||||
->encode(random_bytes($this->secretLength));
|
||||
}
|
||||
|
||||
private function hashToInt(string $bytes, int $start): int
|
||||
{
|
||||
return unpack('N', substr(substr($bytes, $start), 0, 4))[1];
|
||||
}
|
||||
}
|
||||
|
||||
// NEXT_MAJOR: Remove class alias
|
||||
class_alias('Sonata\GoogleAuthenticator\GoogleAuthenticator', 'Google\Authenticator\GoogleAuthenticator', false);
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Sonata Project package.
|
||||
*
|
||||
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Sonata\GoogleAuthenticator;
|
||||
|
||||
interface GoogleAuthenticatorInterface
|
||||
{
|
||||
/**
|
||||
* @param string $secret
|
||||
* @param string $code
|
||||
*/
|
||||
public function checkCode($secret, $code, $discrepancy = 1): bool;
|
||||
|
||||
/**
|
||||
* NEXT_MAJOR: add the interface typehint to $time and remove deprecation.
|
||||
*
|
||||
* @param string $secret
|
||||
* @param float|string|int|\DateTimeInterface|null $time
|
||||
*/
|
||||
public function getCode($secret, /* \DateTimeInterface */$time = null): string;
|
||||
|
||||
/**
|
||||
* NEXT_MAJOR: Remove this method.
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $hostname
|
||||
* @param string $secret
|
||||
*
|
||||
* @deprecated deprecated as of 2.1 and will be removed in 3.0. Use Sonata\GoogleAuthenticator\GoogleQrUrl::generate() instead.
|
||||
*/
|
||||
public function getUrl($user, $hostname, $secret): string;
|
||||
|
||||
public function generateSecret(): string;
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Sonata Project package.
|
||||
*
|
||||
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Sonata\GoogleAuthenticator;
|
||||
|
||||
/**
|
||||
* Responsible for QR image url generation.
|
||||
*
|
||||
* @see http://goqr.me/api/
|
||||
* @see http://goqr.me/api/doc/
|
||||
* @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
*
|
||||
* @author Iltar van der Berg <kjarli@gmail.com>
|
||||
*/
|
||||
final class GoogleQrUrl
|
||||
{
|
||||
/**
|
||||
* Private by design.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL that is used to show a QR code.
|
||||
*
|
||||
* Account names may not contain a double colon (:). Valid account name
|
||||
* examples:
|
||||
* - "John.Doe@gmail.com"
|
||||
* - "John Doe"
|
||||
* - "John_Doe_976"
|
||||
*
|
||||
* The Issuer may not contain a double colon (:). The issuer is recommended
|
||||
* to pass along. If used, it will also be appended before the accountName.
|
||||
*
|
||||
* The previous examples with the issuer "Acme inc" would result in label:
|
||||
* - "Acme inc:John.Doe@gmail.com"
|
||||
* - "Acme inc:John Doe"
|
||||
* - "Acme inc:John_Doe_976"
|
||||
*
|
||||
* The contents of the label, issuer and secret will be encoded to generate
|
||||
* a valid URL.
|
||||
*
|
||||
* @param string $accountName The account name to show and identify
|
||||
* @param string $secret The secret is the generated secret unique to that user
|
||||
* @param string|null $issuer Where you log in to
|
||||
* @param int $size Image size in pixels, 200 will make it 200x200
|
||||
*/
|
||||
public static function generate(string $accountName, string $secret, ?string $issuer = null, int $size = 200): string
|
||||
{
|
||||
if ('' === $accountName || false !== strpos($accountName, ':')) {
|
||||
throw RuntimeException::InvalidAccountName($accountName);
|
||||
}
|
||||
|
||||
if ('' === $secret) {
|
||||
throw RuntimeException::InvalidSecret();
|
||||
}
|
||||
|
||||
$label = $accountName;
|
||||
$otpauthString = 'otpauth://totp/%s?secret=%s';
|
||||
|
||||
if (null !== $issuer) {
|
||||
if ('' === $issuer || false !== strpos($issuer, ':')) {
|
||||
throw RuntimeException::InvalidIssuer($issuer);
|
||||
}
|
||||
|
||||
// use both the issuer parameter and label prefix as recommended by Google for BC reasons
|
||||
$label = $issuer.':'.$label;
|
||||
$otpauthString .= '&issuer=%s';
|
||||
}
|
||||
|
||||
$otpauthString = rawurlencode(sprintf($otpauthString, $label, $secret, $issuer));
|
||||
|
||||
return sprintf(
|
||||
'https://api.qrserver.com/v1/create-qr-code/?size=%1$dx%1$d&data=%2$s&ecc=M',
|
||||
$size,
|
||||
$otpauthString
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// NEXT_MAJOR: Remove class alias
|
||||
class_alias('Sonata\GoogleAuthenticator\GoogleQrUrl', 'Google\Authenticator\GoogleQrUrl', false);
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Sonata Project package.
|
||||
*
|
||||
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Sonata\GoogleAuthenticator;
|
||||
|
||||
/**
|
||||
* Contains runtime exception templates.
|
||||
*
|
||||
* @author Iltar van der Berg <kjarli@gmail.com>
|
||||
*/
|
||||
final class RuntimeException extends \RuntimeException
|
||||
{
|
||||
public static function InvalidAccountName(string $accountName): self
|
||||
{
|
||||
return new self(sprintf(
|
||||
'The account name may not contain a double colon (:) and may not be an empty string. Given "%s".',
|
||||
$accountName
|
||||
));
|
||||
}
|
||||
|
||||
public static function InvalidIssuer(string $issuer): self
|
||||
{
|
||||
return new self(sprintf(
|
||||
'The issuer name may not contain a double colon (:) and may not be an empty string. Given "%s".',
|
||||
$issuer
|
||||
));
|
||||
}
|
||||
|
||||
public static function InvalidSecret(): self
|
||||
{
|
||||
return new self('The secret name may not be an empty string.');
|
||||
}
|
||||
}
|
||||
|
||||
// NEXT_MAJOR: Remove class alias
|
||||
class_alias('Sonata\GoogleAuthenticator\RuntimeException', 'Google\Authenticator\RuntimeException', false);
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
|
||||
header("content-type: application/json");
|
||||
|
||||
$response = ["success" => true, "message" => ""];
|
||||
|
||||
if(!isset($_GET['username'])){ die(http_response_code(400)); }
|
||||
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM blacklistednames WHERE (exact AND lower(username) = lower(:name)) OR (NOT exact AND lower(CONCAT('%', :name, '%')) LIKE lower(CONCAT('%', username, '%')))");
|
||||
$query->bindParam(":name", $_GET['username'], PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
if($query->fetchColumn()){ $response["success"] = false; $response["message"] = "That username is unavailable. Sorry!"; }
|
||||
|
||||
$query = $pdo->prepare("SELECT COUNT(*) FROM users WHERE lower(username) = lower(:name)");
|
||||
$query->bindParam(":name", $_GET['username'], PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
||||
if($query->fetchColumn()){ $response["success"] = false; $response["message"] = "Someone already has that username! Try choosing a different one."; }
|
||||
|
||||
die(json_encode($response));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<?php $asset = $_GET['asset'] ?? false; ?>
|
||||
http://<?=$_SERVER['HTTP_HOST']?>/api/thumbs/whitecharacter.xml;http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?>;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
include $_SERVER['DOCUMENT_ROOT']."/api/private/config.php";
|
||||
if(SITE_CONFIG["api"]["renderserverKey"] != ($_GET['accessKey'] ?? false)) die(http_response_code(403));
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
openssl_sign($_GET['script'], $signature, openssl_pkey_get_private("file://private_key.pem"));
|
||||
echo "%" . base64_encode($signature) . "%" . $_GET['script'];
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?php $asset = $_GET['asset'] ?? false; ?>
|
||||
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
||||
<External>null</External>
|
||||
<External>nil</External>
|
||||
<Item class="Workspace" referent="RBX2">
|
||||
<Properties>
|
||||
<double name="DistributedGameTime">0</double>
|
||||
<CoordinateFrame name="ModelInPrimary">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<string name="Name">Workspace</string>
|
||||
<Ref name="PrimaryPart">null</Ref>
|
||||
<bool name="archivable">true</bool>
|
||||
</Properties>
|
||||
<Item class="Part" referent="RBX0">
|
||||
<Properties>
|
||||
<bool name="Anchored">false</bool>
|
||||
<float name="BackParamA">-0.5</float>
|
||||
<float name="BackParamB">0.5</float>
|
||||
<token name="BackSurface">0</token>
|
||||
<token name="BackSurfaceInput">0</token>
|
||||
<float name="BottomParamA">-0.5</float>
|
||||
<float name="BottomParamB">0.5</float>
|
||||
<token name="BottomSurface">4</token>
|
||||
<token name="BottomSurfaceInput">0</token>
|
||||
<int name="BrickColor">194</int>
|
||||
<CoordinateFrame name="CFrame">
|
||||
<X>0</X>
|
||||
<Y>0.600000024</Y>
|
||||
<Z>0</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<bool name="CanCollide">true</bool>
|
||||
<bool name="DraggingV1">false</bool>
|
||||
<float name="Elasticity">0.5</float>
|
||||
<token name="FormFactor">1</token>
|
||||
<float name="Friction">0.300000012</float>
|
||||
<float name="FrontParamA">-0.5</float>
|
||||
<float name="FrontParamB">0.5</float>
|
||||
<token name="FrontSurface">0</token>
|
||||
<token name="FrontSurfaceInput">0</token>
|
||||
<float name="LeftParamA">-0.5</float>
|
||||
<float name="LeftParamB">0.5</float>
|
||||
<token name="LeftSurface">0</token>
|
||||
<token name="LeftSurfaceInput">0</token>
|
||||
<bool name="Locked">false</bool>
|
||||
<token name="Material">256</token>
|
||||
<string name="Name">Part</string>
|
||||
<float name="Reflectance">0</float>
|
||||
<float name="RightParamA">-0.5</float>
|
||||
<float name="RightParamB">0.5</float>
|
||||
<token name="RightSurface">0</token>
|
||||
<token name="RightSurfaceInput">0</token>
|
||||
<Vector3 name="RotVelocity">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
<float name="TopParamA">-0.5</float>
|
||||
<float name="TopParamB">0.5</float>
|
||||
<token name="TopSurface">3</token>
|
||||
<token name="TopSurfaceInput">0</token>
|
||||
<float name="Transparency">0</float>
|
||||
<Vector3 name="Velocity">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
<bool name="archivable">true</bool>
|
||||
<token name="shape">1</token>
|
||||
<Vector3 name="size">
|
||||
<X>1</X>
|
||||
<Y>1.20000005</Y>
|
||||
<Z>1</Z>
|
||||
</Vector3>
|
||||
</Properties>
|
||||
<Item class="SpecialMesh" referent="RBX1">
|
||||
<Properties>
|
||||
<token name="LODX">2</token>
|
||||
<token name="LODY">2</token>
|
||||
<Content name="MeshId"><url>http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?></url></Content>
|
||||
<token name="MeshType">5</token>
|
||||
<string name="Name">Mesh</string>
|
||||
<Vector3 name="Offset">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
<Vector3 name="Scale">
|
||||
<X>1</X>
|
||||
<Y>1</Y>
|
||||
<Z>1</Z>
|
||||
</Vector3>
|
||||
<Content name="TextureId"><null></null></Content>
|
||||
<Vector3 name="VertexColor">
|
||||
<X>1</X>
|
||||
<Y>1</Y>
|
||||
<Z>1</Z>
|
||||
</Vector3>
|
||||
<bool name="archivable">true</bool>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
require $_SERVER["DOCUMENT_ROOT"].'/api/private/config.php';
|
||||
require $_SERVER["DOCUMENT_ROOT"].'/api/private/components/db.php';
|
||||
|
||||
if(SITE_CONFIG["api"]["renderserverKey"] != ($_GET['accessKey'] ?? false)) die(http_response_code(403));
|
||||
|
||||
$query = $pdo->query("UPDATE servers SET ping = UNIX_TIMESTAMP() WHERE id = 1");
|
||||
echo "pinged";
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQDM2nAU3iPVFbvirzU0YXw9W/jAVZefaRDeN4RiW3ksdW7Wgx6l14nlK6eKpxOdTM5yiS0SVMvjjImZuIrqLwwexGJ92or6aA3n3qf9etxZdKqROjngo3DVMTrVZIfQpkf9r+LEOAx4K3hH71s6Ni54rPLXTIHgJsQO0cnxWnEdEQIDAQABAoGBAL+JmnSYg45wJN2+DpQsdjr07LABF6TQWxo7dId2meTs5DakIJrV3jQtzhiBQYC5WOqUwlS6fm0DcYEOoKx4Uu4ftuL4CGzNtbY0E8CN2xMk0S70KrvsgoO4rqFzPGd+dJoVLlw2bVXrF+vVtFgnxClRx+VSWMD09TGe2R2YXirlAkEA0dnPhhjPuIqwLSLv3xuTfbTlfqoeoaUIahAo7RL9o34xMw2vxLQMw/uLBByxZl9JhFcC+lqxpmwwAzo2nMD+UwJBAPnnRmEh1Ewp3Cn7LjrcKaI7x1NC2nqwo69Qf51nSQl/Udz7PfL053K+J0pJj2IFWS/hmxFo/yTA+MV7+rigIosCQFSh8ncThJrZnCnoADPLzFUTYscN1yK8C0OzVr4ePZr1ZuQ/Ldc4Ajn8NdmntMgjv+OWsAXGFAWZdlem36WilC8CQEyYVVr6Gm7JucBoS3AhAOXHur1LVVmbgGAApUyiVqGBk57OptsrszDZFYPQbhEWIJLrbDL24pTqTJWC/YLPGicCQQCTCCNew5hZ9p0DRx7I6oa1qLMctatrwfFR14MaQzqP961+pbxLK4ShKe1VnqsK+aLrR+TkKYjB64RD2YNOXkNK
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
require $_SERVER["DOCUMENT_ROOT"].'/api/private/config.php';
|
||||
require $_SERVER["DOCUMENT_ROOT"].'/api/private/components/db.php';
|
||||
|
||||
if(SITE_CONFIG["api"]["renderserverKey"] != ($_GET['accessKey'] ?? false)) die(http_response_code(403));
|
||||
header('Content-type: text/javascript');
|
||||
|
||||
if(SITE_CONFIG["site"]["thumbserver"])
|
||||
$data = $pdo->query("SELECT renderStatus, jobID, renderType, assetID FROM renderqueue WHERE renderStatus IN (0, 1) ORDER BY timestampRequested LIMIT 1")->fetch(PDO::FETCH_OBJ);
|
||||
else
|
||||
$data = $pdo->query("SELECT renderStatus, jobID, renderType, assetID FROM renderqueue WHERE renderStatus IN (0, 1) AND assetID = 1 ORDER BY timestampRequested LIMIT 1")->fetch(PDO::FETCH_OBJ);
|
||||
|
||||
if (!$data) die("fart");
|
||||
echo json_encode($data, JSON_PRETTY_PRINT);
|
||||
|
||||
$query = $pdo->prepare('UPDATE renderqueue SET renderStatus = 1, timestampAcknowledged = UNIX_TIMESTAMP() WHERE jobID = :jobid');
|
||||
$query->bindValue(':jobid', $data->jobID, PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
include $_SERVER['DOCUMENT_ROOT']."/api/private/core.php";
|
||||
if(SITE_CONFIG["api"]["renderserverKey"] != ($_GET['accessKey'] ?? false)) die(http_response_code(403));
|
||||
|
||||
$asset = $_GET['asset'] ?? false;
|
||||
$renderType = $_GET['type'] ?? false;
|
||||
|
||||
header("Pragma: no-cache");
|
||||
header("Cache-Control: no-cache");
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
ob_start();
|
||||
?>
|
||||
game:GetService('Visit'):SetUploadUrl('')
|
||||
for _,v in pairs(game.GuiRoot:GetChildren()) do v:Remove() end
|
||||
<?php if($renderType == "Avatar") { ?>
|
||||
if not game.Players.LocalPlayer then game.Players:CreateLocalPlayer(0) end
|
||||
plr = game.Players.LocalPlayer
|
||||
|
||||
--plr.CharacterAppearance = "http://<?=$_SERVER['HTTP_HOST']?>/Asset/CharacterFetch.ashx?userId=<?=$asset?>"
|
||||
plr.CharacterAppearance = "<?=Users::GetCharacterAppearance($asset, -1)?>"
|
||||
plr:LoadCharacter()
|
||||
|
||||
wait(2)
|
||||
|
||||
for _,v in ipairs(plr.Character:GetChildren()) do
|
||||
if v.className == "Tool" then
|
||||
plr.Character.Torso["Right Shoulder"].CurrentAngle = math.pi/2
|
||||
plr.Character["Right Arm"].Transparency = 1
|
||||
plr.Character["Right Arm"].Transparency = 0
|
||||
v.Handle.Transparency = 1
|
||||
v.Handle.Transparency = 0
|
||||
--v.Parent = plr.Character
|
||||
--break
|
||||
end
|
||||
end
|
||||
|
||||
wait(1)
|
||||
|
||||
for _,v in pairs(game.RelativePanel:GetChildren()) do v:Remove() end
|
||||
|
||||
<?php } elseif($renderType == "Head") { ?>
|
||||
if not game.Players:GetChildren()[1] then game.Players:CreateLocalPlayer(0) end
|
||||
plr = game.Players.LocalPlayer
|
||||
|
||||
plr.CharacterAppearance = "http://<?=$_SERVER['HTTP_HOST']?>/api/thumbs/whitecharacter.xml;http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?>&force=true;"
|
||||
plr:LoadCharacter()
|
||||
|
||||
for _,v in ipairs(plr.Character:GetChildren()) do
|
||||
if v.className == "Part" and v.Name ~= "Head" then v:Remove() end
|
||||
end
|
||||
|
||||
wait(2)
|
||||
<?php } elseif($renderType == "Clothing") { ?>
|
||||
if not game.Players:GetChildren()[1] then game.Players:CreateLocalPlayer(0) end
|
||||
plr = game.Players.LocalPlayer
|
||||
|
||||
plr.CharacterAppearance = "http://<?=$_SERVER['HTTP_HOST']?>/api/thumbs/whitecharacter.xml;http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?>&force=true;"
|
||||
plr:LoadCharacter()
|
||||
|
||||
wait(2)
|
||||
<?php } elseif($renderType == "Mesh") { ?>
|
||||
game:Load("http://<?=$_SERVER['HTTP_HOST']?>/api/thumbs/mesh?asset=<?=$asset?>&force=true")
|
||||
error("hi")
|
||||
<?php } elseif($renderType == "Model" || $renderType == "UserModel") { ?>
|
||||
--game.Lighting.GeographicLatitude = 40
|
||||
--game.Lighting.TimeOfDay = '12:00:00'
|
||||
<?php if($renderType == "UserModel") { ?>game:Load("rbxasset://whitesky.rbxm")<?php } ?>
|
||||
game:Load("http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?>&force=true")
|
||||
game:GetChildren()[20].Parent = workspace
|
||||
--[[while wait() do
|
||||
local model = game:GetChildren()[20]
|
||||
if model ~= nil then
|
||||
--if model.Name ~= "FilteredSelection" and model.Name ~= "SpawnerService" then
|
||||
model.Parent = workspace
|
||||
--end
|
||||
else
|
||||
break
|
||||
end
|
||||
end--]]
|
||||
error("hi")
|
||||
<?php } elseif($renderType == "Place") { ?>
|
||||
game:Load("http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?>")
|
||||
print("loaded <?=$asset?>")
|
||||
|
||||
for _,v in ipairs(game.Lighting:GetChildren()) do
|
||||
if v.className == "Sky" then skybox = true end
|
||||
end
|
||||
if not skybox then game:Load('rbxasset://sky.rbxm') end
|
||||
|
||||
for _,v in pairs(game.StarterPack:GetChildren()) do v:Remove() end
|
||||
|
||||
error("hi")
|
||||
<?php }
|
||||
|
||||
$script = ob_get_clean();
|
||||
openssl_sign($script, $signature, openssl_pkey_get_private("file://private_key.pem"));
|
||||
echo "%" . base64_encode($signature) . "%" . $script;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
require $_SERVER["DOCUMENT_ROOT"].'/api/private/config.php';
|
||||
require $_SERVER["DOCUMENT_ROOT"].'/api/private/components/db.php';
|
||||
|
||||
if(SITE_CONFIG["api"]["renderserverKey"] != ($_GET['accessKey'] ?? false)) die(http_response_code(403));
|
||||
|
||||
$completetype = $_GET['type'] ?? false; //1 = success; 2 = error;
|
||||
$response = $_GET['response'] ?? false;
|
||||
|
||||
switch ($completetype)
|
||||
{
|
||||
case 1: //success
|
||||
$query = $pdo->query("UPDATE renderqueue SET renderStatus = 4 WHERE renderStatus = 1 LIMIT 1");
|
||||
// moved this to upload
|
||||
echo "success";
|
||||
break;
|
||||
|
||||
case 2: //error
|
||||
$query = $pdo->prepare("UPDATE renderqueue SET renderStatus = 3, additionalInfo = :response, timestampCompleted = UNIX_TIMESTAMP() WHERE renderStatus = 1 LIMIT 1");
|
||||
$query->bindParam(':response', $response, PDO::PARAM_STR);
|
||||
$query->execute();
|
||||
echo "success";
|
||||
break;
|
||||
|
||||
default:
|
||||
die("invalid type");
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue