This commit is contained in:
pizzaboxer 2022-12-31 16:12:00 +00:00
parent 9772b05bcb
commit dcc8e61201
361 changed files with 100309 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/thumbs/assets/*
/thumbs/avatars/*
/asset/files/*
/api/private/config.php

11
RobloxOld.css Normal file
View File

@ -0,0 +1,11 @@
*
{
font-size: 12px;
font-family: 'Comic Sans MS', Verdana, Arial, Helvetica, sans-serif;
}
H1
{
font-weight: bold;
font-size: larger;
}

138
admin.php Normal file
View File

@ -0,0 +1,138 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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;
}
$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/"),
"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 p-0 divider-right">
<div class="pl-3">
<h3 class="pb-2 font-weight-normal">You are <?=vowel([1 => "Moderator", 2 => "Administrator"][SESSION["adminLevel"]])?> - Choose an action</h3>
<div class="row mx-1 mb-4">
<div class="col-md-4 py-2 pl-0">
<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>
<div class="col-md-4 py-2 pl-0">
<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 pl-0">
<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(SESSION["adminLevel"] >= 2) { ?>
<div class="col-md-4 py-2 pl-0">
<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 pl-0">
<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 pl-0">
<a class="btn btn-outline-primary btn-lg btn-block px-0" href="/admin/staff-logs"><i class="fal fa-book"></i> Staff Logs</a>
</div>
<div class="col-md-4 py-2 pl-0">
<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>
<?php } if(SESSION["userId"] == 1){ ?>
<div class="col-md-4 py-2 pl-0">
<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>
<?php } ?>
<div class="col-md-4 py-2 pl-0">
<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>
</div>
</div>
</div>
<div class="col-md-5 p-0">
<div class="px-4">
<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>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>
</div>
<?php pageBuilder::buildFooter(); ?>

View File

@ -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");

View File

@ -0,0 +1,61 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged" => true, "secure" => true]);
$wearing = isset($_POST["wearing"]) && $_POST["wearing"] == "true";
$userId = SESSION["userId"];
$type = $_POST["type"] ?? false;
$page = $_POST["page"] ?? 1;
$assets = [];
if($wearing)
{
$query = $pdo->prepare("SELECT COUNT(*) FROM ownedAssets WHERE userId = :uid AND wearing = 1");
}
else
{
$type_str = catalog::getTypeByNum($type);
if(!catalog::getTypeByNum($type)) api::respond(400, false, "Invalid asset type");
$query = $pdo->prepare("SELECT COUNT(*) FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :uid AND assets.type = :type AND wearing = 0");
$query->bindParam(":type", $type, PDO::PARAM_INT);
}
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
$query->execute();
$pages = ceil($query->fetchColumn()/8);
$offset = ($page - 1)*8;
if(!$pages) api::respond(200, true, $wearing ? 'You are not currently wearing anything' : 'You don\'t have any unequipped '.($type_str.(!str_ends_with($type_str, 's') ? 's' : '').' to wear'));
if($wearing)
{
$query = $pdo->prepare("
SELECT assets.* FROM ownedAssets
INNER JOIN assets ON assets.id = assetId
WHERE userId = :uid AND wearing = 1
ORDER BY last_toggle DESC LIMIT 8 OFFSET $offset");
}
else
{
$query = $pdo->prepare("
SELECT assets.* FROM ownedAssets
INNER JOIN assets ON assets.id = assetId
WHERE userId = :uid AND assets.type = :type AND wearing = 0
ORDER BY timestamp DESC LIMIT 8 OFFSET $offset");
$query->bindParam(":type", $type, PDO::PARAM_INT);
}
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
$query->execute();
while($asset = $query->fetch(PDO::FETCH_OBJ))
{
$assets[] =
[
"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" => $pages, "items" => $assets]));

View File

@ -0,0 +1,21 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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 = users::hex2bc(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");

View File

@ -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");

View File

@ -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");

View File

@ -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");

60
api/account/getFeed.php Normal file
View File

@ -0,0 +1,60 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged_in" => true]);
$userid = SESSION["userId"];
$query = $pdo->prepare("
SELECT * FROM feed
WHERE userId = :uid
OR userId IN (SELECT receiverId FROM friends WHERE requesterId = :uid AND status = 1)
OR userId IN (SELECT requesterId FROM friends WHERE receiverId = :uid AND status = 1)
ORDER BY id DESC LIMIT 15");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->execute();
$feed = [];
$news = [];
/*$news[] =
[
"header" => '<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"
]; */
while($row = $query->fetch(PDO::FETCH_OBJ))
{
$feed[] =
[
"userName" => users::getUserNameFromUid($row->userId),
"img" => Thumbnails::GetAvatar($row->userId, 100, 100),
"header" => '<p class="m-0"><a href="/user?ID='.$row->userId.'">'.users::getUserNameFromUid($row->userId).'</a> - <small>'.timeSince('@'.$row->timestamp).'</small></p>',
"message" => polygon::filterText($row->text)
];
}
if($query->rowCount() < 15)
{
$feed[] =
[
"userName" => "Your feed is currently empty!",
"img" => "/img/feed-starter.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?"
];
}
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "feed" => $feed, "news" => $news]);

View File

@ -0,0 +1,25 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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]);

5
api/account/ping.php Normal file
View File

@ -0,0 +1,5 @@
<?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"]]);

View File

@ -0,0 +1,44 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
$userid = SESSION["userId"];
$type = $_POST["type"] ?? false;
$page = $_POST["page"] ?? 1;
$transactions = [];
if(!in_array($type, ["Purchases", "Sales"])) api::respond(400, false, "Bad Request");
$query = $pdo->prepare("SELECT COUNT(*) FROM transactions WHERE ".($type=="Sales"?"seller":"purchaser")." = :uid");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->execute();
$pages = ceil($query->fetchColumn()/15);
$offset = ($page - 1)*15;
$query = $pdo->prepare("
SELECT transactions.*, users.username, assets.name FROM transactions
INNER JOIN users ON users.id = ".($type=="Sales"?"purchaser":"seller")." INNER JOIN assets ON assets.id = transactions.assetId
WHERE ".($type=="Sales"?"seller":"purchaser")." = :uid ORDER BY id DESC LIMIT 15 OFFSET $offset");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->execute();
if(!$query->rowCount()) api::respond(200, true, "You have not ".($type=="Sales"?"sold":"purchased")." any items!");
while($transaction = $query->fetch(PDO::FETCH_OBJ))
{
$memberID = $type == "Sales" ? $transaction->purchaser : $transaction->seller;
$transactions[] =
[
"type" => $type == "Sales" ? "Sold" : "Purchased",
"date" => date('n/j/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
];
}
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "transactions" => $transactions, "pages" => $pages]);

View File

@ -0,0 +1,20 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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");

View File

@ -0,0 +1,21 @@
<?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"])) 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!");
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");

View File

@ -0,0 +1,28 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
$userId = SESSION["userId"];
$status = $_POST['status'] ?? false;
if(!$status) api::respond(405, false, "Your status cannot be empty");
if(strlen($status) > 140) api::respond(405, false, "Your status cannot be more than 140 characters");
//ratelimit
$query = db::run("SELECT timestamp FROM feed WHERE userId = :uid AND timestamp+60 > UNIX_TIMESTAMP()", [":uid" => $userId]);
if($query->rowCount()) api::respond(400, false, "Please wait ".(($query->fetchColumn()+60)-time())." seconds before updating your status");
db::run("INSERT INTO feed (userId, timestamp, text) VALUES (:uid, UNIX_TIMESTAMP(), :status)", [":uid" => $userId, ":status" => $status]);
db::run("UPDATE users SET status = :status WHERE id = :uid", [":uid" => $userId, ":status" => $status]);
// $status = str_ireplace("http://", "", $status);
// $status = str_ireplace("https://", "", $status);
polygon::sendKushFeed([
"username" => SESSION["userName"],
"content" => $status,
"avatar_url" => Thumbnails::GetAvatar(SESSION["userId"], 420, 420)
]);
api::respond(200, true, "OK");

26
api/admin/addBanner.php Normal file
View File

@ -0,0 +1,26 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "admin_ratelimit" => true, "secure" => true]);
if(!isset($_POST["text"]) || !isset($_POST["bg-color"]) || !isset($_POST["text-color"])){ api::respond(400, false, "Invalid Request"); }
if($_POST["text-color"] != "dark" && $_POST["text-color"] != "light"){ api::respond(400, false, "Invalid Request"); }
if(!trim($_POST["text"])){ api::respond(400, false, "You haven't set the banner text"); }
if(strlen($_POST["text"]) > 128){ api::respond(400, false, "The banner text must be less than 128 characters"); }
if(!trim($_POST["bg-color"])){ api::respond(400, false, "You haven't set a background color"); }
if(!ctype_xdigit(ltrim($_POST["bg-color"], "#")) || strlen($_POST["bg-color"]) != 7){ api::respond(400, false, "That doesn't appear to be a valid hex color"); }
if($pdo->query("SELECT COUNT(*) FROM announcements WHERE activated")->fetchColumn() > 5){ api::respond(400, false, "There's too many banners currently active!"); }
$userId = SESSION["userId"];
$text = trim($_POST["text"]);
$color = trim($_POST["bg-color"]);
$textcolor = "text-".trim($_POST["text-color"]);
$query = $pdo->prepare("INSERT INTO announcements (createdBy, text, bgcolor, textcolor) VALUES (:uid, :text, :bgc, :tc)");
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
$query->bindParam(":text", $text, PDO::PARAM_STR);
$query->bindParam(":bgc", $color, PDO::PARAM_STR);
$query->bindParam(":tc", $textcolor, PDO::PARAM_STR);
$query->execute();
users::logStaffAction("[ Banners ] Created site banner with text: ".$text);
api::respond(200, true, "Banner has been created");

20
api/admin/delete-post.php Normal file
View File

@ -0,0 +1,20 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "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"); }

39
api/admin/get-assets.php Normal file
View File

@ -0,0 +1,39 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "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::getItemInfo($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]));

View File

@ -0,0 +1,35 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "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->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]));

49
api/admin/git-pull.php Normal file
View File

@ -0,0 +1,49 @@
<?php
header("content-type: text/plain");
function sendSystemWebhook($message)
{
// example payload:
// $payload = ["username" => "test", "content" => "test", "avatar_url" => "https://polygon.pizzaboxer.xyz/thumbs/avatar?id=1&x=100&y=100"];
$payload = ["content" => $message];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://discord.com/api/webhooks/");
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;
}
// this should only be used if core.php does not work
$emergency = ($_GET["key"] ?? false) == "D5F6E2EAA6C07C991CA2895920A8BBA8BB66CA16";
$output = "";
$webhook = "";
$output_array = [];
if($emergency)
{
$webhook .= sprintf("[%s] Git Pull intiated by %s\n", date('d/m/Y h:i:s A'), "[[[EMERGENCY]]]");
}
else
{
require $_SERVER["DOCUMENT_ROOT"]."/api/private/core.php";
if(!SESSION || !SESSION["adminLevel"]) die(http_response_code(404));
$webhook .= sprintf("[%s] Git Pull executed by %s\n", date('d/m/Y h:i:s A'), SESSION["userName"]);
}
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 .= "```";
// sendSystemWebhook($webhook);

View File

@ -0,0 +1,26 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "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::getUserInfoFromUserName($_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);

View File

@ -0,0 +1,21 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "secure" => true]);
$assetId = $_POST['assetID'] ?? false;
$action = $_POST['action'] ?? false;
$action_sql = $action == "approve" ?: 2;
$reason = $_POST['reason'] ?? false;
$asset = catalog::getItemInfo($assetId);
if(!in_array($action, ["approve", "decline"])) api::respond(400, false, "Invalid request");
if(!$asset) api::respond(400, false, "Asset does not exist");
$query = $pdo->prepare("UPDATE assets SET approved = :action WHERE id IN (:id, :image)");
$query->bindParam(":action", $action_sql, PDO::PARAM_INT);
$query->bindParam(":id", $asset->id, PDO::PARAM_INT);
$query->bindParam(":image", $asset->imageID, PDO::PARAM_INT);
$query->execute();
users::logStaffAction('[ Asset Moderation ] '.ucfirst($action).'d "'.$asset->name.'" [ID '.$asset->id.']'.($reason ? ' with reason: '.$reason : ''));
api::respond(200, true, '"'.htmlspecialchars($asset->name).'" has been '.$action.'d');

View File

@ -0,0 +1,59 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "admin_ratelimit" => true, "secure" => true]);
if(!isset($_POST["username"]) || !isset($_POST["banType"]) || !isset($_POST["moderationNote"]) || !isset($_POST["until"])){ api::respond(400, false, "Bad Request"); }
if($_POST["banType"] < 1 || $_POST["banType"] > 4){ api::respond(400, false, "Bad Request"); }
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"];
$bannerInfo = users::getUserInfoFromUserName($_POST["username"]);
$reason = $_POST["moderationNote"];
$bannedUntil = $_POST["banType"] == 2 ? strtotime($_POST["until"]." ".date('G:i:s')) : 0;
if(!$bannerInfo){ api::respond(200, false, "User does not exist"); }
if($banType == 4)
{
if(!users::getUserModeration($bannerInfo->id)){ api::respond(200, false, "That user isn't banned!"); }
users::undoUserModeration($bannerInfo->id, true);
}
else
{
// if($bannerInfo->id == $userId){ api::respond(200, false, "You cannot moderate yourself!"); }
// if($bannerInfo->adminlevel){ api::respond(200, false, "You cannot moderate a staff member"); }
if(users::getUserModeration($bannerInfo->id)){ api::respond(200, false, "That user is already banned!"); }
if($banType == 2 && $bannedUntil < strtotime('tomorrow')){ api::respond(200, false, "Ban time must be at least 1 day long"); }
$query = $pdo->prepare("INSERT INTO bans (userId, bannerId, timeStarted, timeEnds, reason, banType, note) VALUES (:bid, :uid, UNIX_TIMESTAMP(), :ends, :reason, :type, :note)");
$query->bindParam(":bid", $bannerInfo->id, PDO::PARAM_INT);
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
$query->bindParam(":ends", $bannedUntil, PDO::PARAM_INT);
$query->bindParam(":reason", $reason, PDO::PARAM_STR);
$query->bindParam(":type", $banType, PDO::PARAM_INT);
$query->bindParam(":note", $staffNote, PDO::PARAM_STR);
$query->execute();
}
$text =
[
1 => "warned",
2 => "banned for ".timeSince("@".($bannedUntil+1), false, false),
3 => "permanently banned",
4 => "unbanned"
];
$staff =
[
1 => "Warned ".$bannerInfo->username,
2 => "Banned ".$bannerInfo->username." for ".timeSince("@".($bannedUntil+1), false, false),
3 => "Permanently banned ".$bannerInfo->username,
4 => "Unbanned ".$bannerInfo->username
];
users::logStaffAction("[ User Moderation ] ".$staff[$banType]." ( user ID ".$bannerInfo->id." )");
api::respond(200, true, $bannerInfo->username." has been ".$text[$banType]);

View File

@ -0,0 +1,60 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "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(trim($_POST["moderationNote"])))?>
</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());

View File

@ -0,0 +1,34 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "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::getItemInfo($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
default: api::respond(200, false, "This asset cannot be re-rendered");
}
}
else if($renderType == "Avatar")
{
$user = users::getUserInfoFromUid($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>");

110
api/admin/upload.php Normal file
View File

@ -0,0 +1,110 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "admin" => true, "secure" => true]);
$file = $_FILES["file"] ?? false;
$name = $_POST["name"] ?? false;
$type = $_POST["type"] ?? false;
$uploadas = $_POST["creator"] ?? "Polygon";
$creator = users::getUidFromUserName($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", "video/ogg"])) api::respond(400, false, "Must be an mpeg, wav, ogg or midi audio. - ".$file["type"]);
$assetId = catalog::createAsset(["type" => $type, "creator" => $creator, "name" => $name, "description" => "", "audioType" => $file["type"], "approved" => 1]);
copy($file["tmp_name"], $_SERVER['DOCUMENT_ROOT']."/asset/files/".$assetId);
image::renderfromimg("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::renderfromimg("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!"]);

View File

@ -0,0 +1,36 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize();
if(!isset($_GET['assetID'])) api::respond(400, false, "Bad Request");
$assetID = $_GET['assetID'];
$page = $_GET['page'] ?? 1;
$query = $pdo->prepare("SELECT COUNT(*) FROM asset_comments WHERE assetID = :id");
$query->bindParam(":id", $assetID, PDO::PARAM_INT);
$query->execute();
$pages = ceil($query->fetchColumn()/15);
$offset = ($page - 1)*15;
$query = $pdo->prepare("SELECT asset_comments.*, users.username FROM asset_comments INNER JOIN users ON users.id = asset_comments.author WHERE assetID = :id ORDER BY id DESC");
$query->bindParam(":id", $assetID, PDO::PARAM_INT);
$query->execute();
if(!$query->rowCount()) api::respond(200, true, "This asset has no comments");
$comments = [];
while($row = $query->fetch(PDO::FETCH_OBJ))
{
$comments[] =
[
"time" => strtolower(timeSince($row->time)),
"commenter_name" => $row->username,
"commenter_id" => $row->author,
"commenter_avatar" => Thumbnails::GetAvatar($row->author, 110, 110),
"content" => nl2br(polygon::filterText($row->content))
];
}
api::respond_custom(["status" => 200, "success" => true, "message" => "OK", "comments" => $comments, "pages" => $pages]);

View File

@ -0,0 +1,29 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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::getItemInfo($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();
$lastComment = $query->fetchColumn();
if($lastComment) api::respond(400, false, "Please wait ".(60-(time()-$lastComment))." seconds 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");

59
api/catalog/purchase.php Normal file
View File

@ -0,0 +1,59 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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::getItemInfo($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
]));
$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, timestamp) VALUES (:uid, :sid, :aid, :price, 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->execute();
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']],
]));

View File

@ -0,0 +1,34 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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::getItemInfo($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]));

123
api/develop/upload.php Normal file
View File

@ -0,0 +1,123 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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(400, false, "You must select a file");
if(!in_array($file["type"], ["image/png", "image/jpg", "image/jpeg"])) api::respond(400, false, "Must be a .png or .jpg file");
if(!$name) api::respond(400, false, "You must specify a name");
if(polygon::filterText($name, false, false, true) != $name) api::respond(400, false, "The name contains inappropriate text");
if(!in_array($type, [2, 11, 12, 13])) api::respond(400, false, "You can't upload that type of content!");
$query = $pdo->prepare("SELECT created FROM assets WHERE creator = :uid ORDER BY id DESC");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->execute();
$lastCreation = $query->fetchColumn();
if($lastCreation+30 > time()) api::respond(400, false, "Please wait ".(30-(time()-$lastCreation))." seconds before creating a new asset");
// 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
{
image::process($image, ["name" => "$imageId", "keepRatio" => true, "align" => "T", "x" => 128, "y" => 128, "dir" => "/asset/files/"]);
Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "T"]);
Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "T"]);
$itemId = catalog::createAsset(["type" => 2, "creator" => SESSION["userId"], "name" => $name, "description" => "T-Shirt", "imageID" => $imageId]);
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::merge($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
{
image::process($image, ["name" => "$imageId", "x" => 585, "y" => 559, "dir" => "/asset/files/"]);
Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "C"]);
Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "C"]);
$itemId = catalog::createAsset(["type" => $type, "creator" => SESSION["userId"], "name" => $name, "description" => catalog::getTypeByNum($type), "imageID" => $imageId]);
file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, catalog::generateGraphicXML(catalog::getTypeByNum($type), $imageId));
polygon::requestRender("Clothing", $itemId);
}
elseif($type == 13) //decal
{
image::process($image, ["name" => "$imageId", "x" => 256, "scaleY" => true, "dir" => "/asset/files/"]);
Thumbnails::UploadAsset($image, $imageId, 60, 62, ["keepRatio" => true, "align" => "C"]);
Thumbnails::UploadAsset($image, $imageId, 420, 420, ["keepRatio" => true, "align" => "C"]);
$itemId = catalog::createAsset(["type" => 13, "creator" => SESSION["userId"], "name" => $name, "description" => "Decal", "imageID" => $imageId]);
file_put_contents(SITE_CONFIG['paths']['assets'].$itemId, catalog::generateGraphicXML("Decal", $imageId));
image::process($image, ["name" => "$itemId-48x48.png", "x" => 48, "y" => 48, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-75x75.png", "x" => 75, "y" => 75, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-100x100.png", "x" => 100, "y" => 100, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-110x110.png", "x" => 110, "y" => 110, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-250x250.png", "x" => 250, "y" => 250, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-352x352.png", "x" => 352, "y" => 352, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-420x230.png", "x" => 420, "y" => 230, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$itemId-420x420.png", "x" => 420, "y" => 420, "dir" => "/thumbs/assets/"]);
}
api::respond_custom(["status" => 200, "success" => true, "message" => catalog::getTypeByNum($type)." successfully created!"]);

20
api/friends/accept.php Normal file
View File

@ -0,0 +1,20 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
$userid = SESSION["userId"];
$friendid = $_POST['friendID'];
$query = $pdo->prepare("SELECT * FROM friends WHERE id = :id AND status = 0");
$query->bindParam(":id", $friendid, PDO::PARAM_INT);
$query->execute();
$friendInfo = $query->fetch(PDO::FETCH_OBJ);
if(!$friendInfo) api::respond(400, false, "Friend request doesn't exist");
if($friendInfo->receiverId != SESSION["userId"]) api::respond(400, false, "You are not the receiver of this friend request");
$query = $pdo->prepare("UPDATE friends SET status = 1 WHERE id = :id");
$query->bindParam(":id", $friendid, PDO::PARAM_INT);
$query->execute();
api::respond(200, true, "OK");

View File

@ -0,0 +1,35 @@
<?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 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::getUserNameFromUid($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]);

View File

@ -0,0 +1,47 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
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::getUserInfoFromUid($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::getUserNameFromUid($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]);

20
api/friends/revoke.php Normal file
View File

@ -0,0 +1,20 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
$userid = SESSION["userId"];
$friendid = $_POST['friendID'];
$query = $pdo->prepare("SELECT * FROM friends WHERE id = :id AND NOT status = 2");
$query->bindParam(":id", $friendid, PDO::PARAM_INT);
$query->execute();
$friendInfo = $query->fetch(PDO::FETCH_OBJ);
if(!$friendInfo) api::respond(400, false, "Friend connection doesn't exist");
if(!in_array($userid, [$friendInfo->requesterId, $friendInfo->receiverId])) api::respond(400, false, "You are not a part of this friend connection");
$query = $pdo->prepare("UPDATE friends SET status = 2 WHERE id = :id");
$query->bindParam(":id", $friendid, PDO::PARAM_INT);
$query->execute();
api::respond(200, true, "OK");

27
api/friends/send.php Normal file
View File

@ -0,0 +1,27 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST", "logged_in" => true, "secure" => true]);
$userid = SESSION["userId"];
$friendid = $_POST['userID'] ?? false;
if(!$friendid) api::respond(400, false, "Bad Request");
if($friendid == $userid) api::respond(400, false, "You can't perform friend operations on yourself");
$query = $pdo->prepare("SELECT status FROM friends WHERE :uid IN (requesterId, receiverId) AND :rid IN (requesterId, receiverId) AND NOT status = 2");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->bindParam(":rid", $friendid, PDO::PARAM_INT);
$query->execute();
if($query->rowCount()) api::respond(400, false, "Friend connection already exists");
$query = $pdo->prepare("SELECT timeSent FROM friends WHERE requesterId = :uid AND timeSent+30 > UNIX_TIMESTAMP()");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->execute();
if($query->rowCount()) api::respond(400, false, "Please wait ".(($query->fetchColumn()+30)-time())." seconds before sending another request");
$query = $pdo->prepare("INSERT INTO friends (requesterId, receiverId, timeSent) VALUES (:uid, :rid, UNIX_TIMESTAMP())");
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->bindParam(":rid", $friendid, PDO::PARAM_INT);
$query->execute();
api::respond(200, true, "OK");

61
api/games/getServers.php Normal file
View File

@ -0,0 +1,61 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST"]);
$client = $_POST["client"] ?? "false";
$creator = $_POST["creator"] ?? false;
$page = $_POST["page"] ?? 1;
$pages = 1;
$items = [];
$query_params = "1";
$value_params = [];
if($client !== "false")
{
if(!in_array($client, [2009, 2010, 2011, 2012])) api::respond(400, false, "Bad Request");
$query_params .= " AND version = :version";
$value_params[":version"] = $client;
}
if($creator)
{
$query_params .= " AND hoster = :uid";
$value_params[":uid"] = $creator;
}
$servercount = db::run("SELECT COUNT(*) FROM selfhosted_servers WHERE $query_params", $value_params)->fetchColumn();
$pages = ceil($servercount/10);
$offset = ($page - 1)*10;
$servers = db::run("
SELECT *,
(SELECT COUNT(*) FROM client_sessions WHERE ping+35 > UNIX_TIMESTAMP() AND serverID = selfhosted_servers.id AND valid) AS players,
(ping+35 > UNIX_TIMESTAMP()) AS online
FROM selfhosted_servers WHERE $query_params
ORDER BY online DESC, players DESC, ping DESC, created DESC LIMIT 10 OFFSET $offset", $value_params);
if(!$servers->rowCount()) api::respond(200, true, "No servers matched your query");
while($server = $servers->fetch(PDO::FETCH_OBJ))
{
$gears = [];
foreach(json_decode($server->allowed_gears, true) as $gear_attr => $gear_val)
if($gear_val) $gears[] = ["name" => catalog::$gear_attr_display[$gear_attr]["text_sel"], "icon" => catalog::$gear_attr_display[$gear_attr]["icon"]];
$items[] =
[
"server_name" => polygon::filterText($server->name),
"server_id" => $server->id,
"server_thumbnail" => Thumbnails::GetAvatar($server->hoster, 420, 420),
"hoster_name" => users::getUserNameFromUid($server->hoster),
"hoster_id" => $server->hoster,
"date" => date('n/d/Y g:i:s A', $server->created),
"version" => $server->version,
"server_online" => $server->ping+35 > time() ? true : false,
"players_online" =>$server->ping+35 > time() ? $server->players : 0,
"players_max" => $server->maxplayers,
"gears" => $gears
];
}
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $pages, "items" => $items]));

View File

@ -0,0 +1,62 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
header("Pragma: no-cache");
header("Cache-Control: no-cache");
api::initialize(["method" => "GET"]);//, "logged_in" => true, "secure" => true]);
if(!SITE_CONFIG["site"]["games"]) api::respond(200, false, "Games are temporarily disabled for maintenance");
$serverID = $_GET["serverID"] ?? $_GET['placeId'] ?? false;
$isTeleport = isset($_GET["isTeleport"]) && $_GET['isTeleport'] == "true";
if($isTeleport && $_SERVER["HTTP_USER_AGENT"] != "Roblox/WinInet")
api::respond_custom([
"Error" => "Request is not authorized from specified origin",
"userAgent" => $_SERVER["HTTP_USER_AGENT"] ?? null,
"referrer" => $_SERVER["HTTP_REFERER"] ?? null
]);
$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);
if(!$serverInfo) api::respond(400, false, "Server does not exist");
if($serverInfo->players >= $serverInfo->maxplayers) api::respond(200, false, "This server is currently full. Please try again later");
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(400, false, "You are not logged in");
$userid = SESSION["userId"];
}
$ticket = generateUUID();
$securityTicket = generateUUID();
$query = $pdo->prepare("INSERT INTO client_sessions (ticket, securityTicket, uid, sessionType, serverID, created, isTeleport) VALUES (:uuid, :security, :uid, 1, :sid, UNIX_TIMESTAMP(), :teleport)");
$query->bindParam(":uuid", $ticket, PDO::PARAM_STR);
$query->bindParam(":security", $securityTicket, PDO::PARAM_STR);
$query->bindParam(":uid", $userid, PDO::PARAM_INT);
$query->bindParam(":sid", $serverID, PDO::PARAM_INT);
$query->bindParam(":teleport", $isTeleport, PDO::PARAM_INT);
$query->execute();
api::respond_custom([
"status" => 200,
"success" => true,
"message" => "OK",
"version" => $serverInfo->version,
"joinScriptUrl" => "http://chef.pizzaboxer.xyz/game/join?ticket=".$ticket,
// these last few params are for teleportservice and lack any function - just ignore
"authenticationUrl" => "http://chef.pizzaboxer.xyz/Login/Negotiate.ashx",
"authenticationTicket" => "unusedplzignore",
"status" => 2
]);

118
api/ide/toolbox.php Normal file
View File

@ -0,0 +1,118 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
$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">&lt;&lt;</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">&gt;&gt;</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">&lt;&lt;</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">&gt;&gt;</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>

1258
api/private/core.php Normal file

File diff suppressed because it is too large Load Diff

20
api/private/db.php Normal file
View File

@ -0,0 +1,20 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/config.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();
}

553
api/private/pagebuilder.php Normal file
View File

@ -0,0 +1,553 @@
<?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
// when core.js is moved to its own file instead of being plopped directly into
// the html, this wont be necessary
public static array $polygonScripts = [];
public static array $CSSdependencies =
[
"/css/fontawesome-pro-v5.15.2/css/all.css",
"/css/toastr.css",
"/css/polygon.css?t=4"
];
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://chef.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()
{
// ideally i should probably have this loaded in from
// core.php instead of doing the php query on the fly here
global $pdo, $announcements, $markdown;
if(SESSION && SESSION["adminLevel"]) $pendingAssets = db::run("SELECT COUNT(*) FROM assets WHERE NOT approved AND type != 1")->fetchColumn();
?>
<!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>
<?php if(SESSION && SESSION["userInfo"]["theme"] == "dark") { ?>
<link rel="stylesheet" href="/css/polygon-dark.css?t=<?=time()?>">
<?php } ?>
</head>
<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 mr-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<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>
<li class="nav-item">
<a class="nav-link" href="/browse">People</a>
</li>
<!--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>
<?php if(SESSION) { ?><a class="dropdown-item" href="/discord">Discord</a><?php } ?>
<a class="dropdown-item" href="https://twitter.com/boxerpizza">Twitter</a>
</div>
</li-->
</ul>
<div class="navbar-nav">
<?php if(SESSION) { ?>
<a class="nav-link mr-2" href="/user?ID=<?=SESSION["userId"]?>"><?=SESSION["userName"]?></a>
<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 <?=timeSince("@".SESSION["nextCurrencyStipend"], true, false, false, 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="/register">Sign Up</a>
<a class="nav-link darken px-0">or</a>
<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="/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 } 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 } } ?>
<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>
<div class="app container py-4"<?=self::$pageConfig['app-attributes']?>>
<?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(*) 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 py-2">
<h3 class="col-12 modal-title text-center font-weight-normal" id="primaryModalTitle"></h3>
</div>
<div class="modal-body text-center text-break" style="white-space: pre-line">
your smell
</div>
<div class="modal-footer text-center">
<div class="mx-auto">
</div>
</div>
</div>
</div>
</div>
<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">&times;</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 the <span class="year">2010</span> client installed</h5>
<a class="btn btn-success btn-block mx-auto mt-3 install" style="max-width:18rem">Download Client</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>
<script src="/js/bootstrap.bundle.min.js"></script>
<script>
//core.js
$.ajaxSetup({ headers: { 'x-polygon-csrf': $('meta[name="polygon-csrf"]').attr('content') } });
/* todo - dont be lazy and work on this
polygon.ajax = function(url, method, data, trusted, successCallback, errorCallback)
{
var ajaxOptions = {type: method, data: data};
if(trusted)
{
ajaxOptions.url = window.location.origin + url;
ajaxOptions.headers: {'x-polygon-csrf': $('meta[name="polygon-csrf"]').attr('content')};
}
else { ajaxOptions.url = url; }
}*/
polygon.button =
{
busy: function(button)
{
$(button).attr("disabled", "disabled").find(".spinner-border").removeClass("d-none");
},
active: function(button)
{
$(button).removeAttr("disabled").find(".spinner-border").addClass("d-none");
}
};
polygon.insertAlert = function(options)
{
var alertCode = '';
if(options.alertClasses == undefined) options.alertClasses = '';
if(options.parentClasses) alertCode += '<div class="'+options.parentClasses+'">';
alertCode += '<div class="alert alert-danger '+options.alertClasses+' px-2 py-1" style="width: fit-content;" role="alert">'+options.text+'</div>';
if(options.parentClasses) alertCode += '</div>';
$(options.parent).append(alertCode);
}
polygon.buildModal = function(options)
{
if(options.options == undefined) options.options = "show";
if(options.fade == undefined) $(".global.modal").addClass("fade");
else if(!options.fade) $(".global.modal").removeClass("fade");
var footer = $(".global.modal .modal-footer .mx-auto");
$(".global.modal .modal-title").html(options.header);
$(".global.modal .modal-body").html(options.body);
footer.empty();
$.each(options.buttons, function(_, button)
{
var buttonCode = '<button type="button" class="'+button.class+' text-center mx-1"';
// todo - improve how attributes are handled
// right now its like {"attr": "data-whatever", "val": 1} instead of just being like {"data-whatever": 1}
if(button.attributes != undefined) $.each(button.attributes, function(_, attr){ buttonCode += ' '+attr.attr+'="'+attr.val+'"'; });
if(button.dismiss) buttonCode += ' data-dismiss="modal"';
buttonCode += '><h4 class="font-weight-normal pb-0">'+button.text+'</h4></button>';
footer.append(buttonCode);
});
if(options.footer) footer.append('<p class="text-muted mt-3 mb-0">'+options.footer+'</p>');
$(".global.modal").modal(options.options);
};
polygon.populate = function(data, template, container)
{
$.each(data, function(_, item)
{
var templateCode = $(template).clone();
templateCode.html(function(_, html)
{
console.log(html);
// todo - this isnt very flexible
for (let key in item) html = html.replace(new RegExp("\\$"+key, "g"), item[key]);
return html;
});
if(templateCode.find("img").attr("preload-src"))
templateCode.find("img").attr("src", templateCode.find("img").attr("preload-src"));
templateCode.appendTo(container);
});
}
polygon.populateControl = function(control, data)
{
return polygon.populate(data, "."+control+"-container .template .item", "."+control+"-container .items");
}
polygon.pagination =
{
register: function(control, callback)
{
var pagination = "."+control+"-container .pagination";
if(!$(pagination).length) return;
$(pagination+" .back").click(function(){ callback(+$(pagination+" .page").val()-1); });
$(pagination+" .next").click(function(){ callback(+$(pagination+" .page").val()+1); });
$(pagination+" .page").on("focusout keypress", this, function(event)
{
if(event.type == "keypress") if(event.which == 13) $(this).blur(); else return;
if($(this).val() == $(this).attr("data-last-page")) return;
$(this).attr("data-last-page", $(this).val());
callback($(this).val());
});
},
handle: function(control, page, pages)
{
var pagination = "."+control+"-container .pagination";
if(!$(pagination).length) return;
if(pages <= 1 || pages == undefined) return $(pagination).addClass("d-none");
$(pagination).removeClass("d-none");
$(pagination+" .pages").text(pages);
if($(pagination+" .page").prop("tagName") == "INPUT") $(pagination+" .page").val(page);
else $(pagination+" .page").text(page);
if(page <= 1) $(pagination+" .back").attr("disabled", "disabled");
else $(pagination+" .back").removeAttr("disabled");
if(page >= pages) $(pagination+" .next").attr("disabled", "disabled");
else $(pagination+" .next").removeAttr("disabled");
}
}
toastr.options =
{
"closeButton": false,
"debug": false,
"newestOnTop": false,
"progressBar": true,
"positionClass": "toast-top-right",
"preventDuplicates": false,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "10000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
}
$(function()
{
if(polygon.user.logged_in)
{
setInterval(function()
{
if(document.hidden) return;
$.post("/api/account/ping", function(data)
{
if(data.friendRequests) $(".friend-requests-indicator").text(data.friendRequests).removeClass("d-none");
else $(".friend-requests-indicator").addClass("d-none");
if(data.status == 401) window.location.reload();
});
}, 30000);
}
$("[data-toggle='tooltip']").tooltip();
});
</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 }
static function errorCode($code)
{
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"]
];
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"]?>
<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();
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

93
api/private/vendors/2fa/GoogleQrUrl.php vendored Normal file
View File

@ -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);

View File

@ -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);

1729
api/private/vendors/Parsedown.php vendored Normal file

File diff suppressed because it is too large Load Diff

1332
api/private/vendors/PasswordLock.php vendored Normal file

File diff suppressed because it is too large Load Diff

5182
api/private/vendors/class.upload.php vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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));

2
api/thumbs/charapp.php Normal file
View File

@ -0,0 +1,2 @@
<?php $asset = $_GET['asset'] ?? false; ?>
http://<?=$_SERVER['HTTP_HOST']?>/api/thumbs/whitecharacter.xml;http://<?=$_SERVER['HTTP_HOST']?>/asset/?id=<?=$asset?>;

7
api/thumbs/execute.php Normal file
View File

@ -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'];

124
api/thumbs/mesh.php Normal file
View File

@ -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>

6
api/thumbs/ping.php Normal file
View File

@ -0,0 +1,6 @@
<?php
include $_SERVER['DOCUMENT_ROOT']."/api/private/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";

View File

@ -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-----

13
api/thumbs/queue.php Normal file
View File

@ -0,0 +1,13 @@
<?php
include $_SERVER['DOCUMENT_ROOT']."/api/private/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"]) die("fart");
$data = $pdo->query("SELECT renderStatus, jobID, renderType, assetID FROM renderqueue WHERE renderStatus IN (0, 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();

97
api/thumbs/render.php Normal file
View File

@ -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?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;

28
api/thumbs/update.php Normal file
View File

@ -0,0 +1,28 @@
<?php
include $_SERVER['DOCUMENT_ROOT']."/api/private/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");
}
?>

69
api/thumbs/upload.php Normal file
View File

@ -0,0 +1,69 @@
<?php
include $_SERVER['DOCUMENT_ROOT']."/api/private/core.php";
if(SITE_CONFIG["api"]["renderserverKey"] != ($_GET['accessKey'] ?? false)) die(http_response_code(403));
$jobid = $_GET['jobID'] ?? false;
$query = $pdo->prepare("SELECT * FROM renderqueue WHERE jobID = :jobID");
$query->bindParam(":jobID", $jobid, PDO::PARAM_STR);
$query->execute();
$data = $query->fetch(PDO::FETCH_OBJ);
if(!$data) die("doesnt exist");
//$query = $pdo->prepare("UPDATE renderqueue SET renderStatus = 4 WHERE jobID = :jobID");
//$query->bindParam(":jobID", $jobid, PDO::PARAM_STR);
//$query->execute();
$assetID = $data->assetID;
polygon::importLibrary("class.upload");
$image = new Upload($_FILES["file"]);
$image->allowed = ['image/png', 'image/jpg', 'image/jpeg'];
$image->image_convert = 'png';
if($data->renderType == "Avatar")
{
/* image::process($image, ["name" => "$assetID-420x420.png", "x" => 420, "y" => 420, "dir" => "/thumbs/avatars/"]);
image::process($image, ["name" => "$assetID-352x352.png", "x" => 352, "y" => 352, "dir" => "/thumbs/avatars/"]);
image::process($image, ["name" => "$assetID-250x250.png", "x" => 250, "y" => 250, "dir" => "/thumbs/avatars/"]);
image::process($image, ["name" => "$assetID-110x110.png", "x" => 110, "y" => 110, "dir" => "/thumbs/avatars/"]);
image::process($image, ["name" => "$assetID-100x100.png", "x" => 100, "y" => 100, "dir" => "/thumbs/avatars/"]);
image::process($image, ["name" => "$assetID-75x75.png", "x" => 75, "y" => 75, "dir" => "/thumbs/avatars/"]);
image::process($image, ["name" => "$assetID-48x48.png", "x" => 48, "y" => 48, "dir" => "/thumbs/avatars/"]); */
Thumbnails::UploadAvatar($image, $assetID, 420, 420);
Thumbnails::UploadAvatar($image, $assetID, 352, 352);
Thumbnails::UploadAvatar($image, $assetID, 250, 250);
Thumbnails::UploadAvatar($image, $assetID, 110, 110);
Thumbnails::UploadAvatar($image, $assetID, 100, 100);
Thumbnails::UploadAvatar($image, $assetID, 75, 75);
Thumbnails::UploadAvatar($image, $assetID, 48, 48);
}
else
{
$type = catalog::getItemInfo($assetID)->type;
if(in_array($type, [4, 8, 10, 11, 12, 17, 19]))
{
/* image::process($image, ["name" => "$assetID-420x420.png", "x" => 420, "y" => 420, "dir" => "/thumbs/assets/"]);
if(in_array($type, [8, 19])) image::process($image, ["name" => "$assetID-420x230.png", "keepRatio" => true, "align" => "C", "x" => 420, "y" => 230, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$assetID-352x352.png", "x" => 352, "y" => 352, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$assetID-250x250.png", "x" => 250, "y" => 250, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$assetID-110x110.png", "x" => 110, "y" => 110, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$assetID-100x100.png", "x" => 100, "y" => 100, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$assetID-75x75.png", "x" => 75, "y" => 75, "dir" => "/thumbs/assets/"]);
image::process($image, ["name" => "$assetID-48x48.png", "x" => 48, "y" => 48, "dir" => "/thumbs/assets/"]); */
if(in_array($type, [8, 19])) Thumbnails::UploadAsset($image, $assetID, 420, 230, ["align" => "C"]);
Thumbnails::UploadAsset($image, $assetID, 420, 420);
Thumbnails::UploadAsset($image, $assetID, 352, 352);
Thumbnails::UploadAsset($image, $assetID, 250, 250);
Thumbnails::UploadAsset($image, $assetID, 110, 110);
Thumbnails::UploadAsset($image, $assetID, 100, 100);
Thumbnails::UploadAsset($image, $assetID, 75, 75);
Thumbnails::UploadAsset($image, $assetID, 48, 48);
}
}
$query = $pdo->prepare("UPDATE renderqueue SET renderStatus = 2, timestampCompleted = UNIX_TIMESTAMP() WHERE jobID = :jobID");
$query->bindParam(":jobID", $jobid, PDO::PARAM_STR);
$query->execute();

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<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="BodyColors">
<Properties>
<int name="HeadColor">1</int>
<int name="LeftArmColor">1</int>
<int name="LeftLegColor">1</int>
<string name="Name">Body Colors</string>
<int name="RightArmColor">1</int>
<int name="RightLegColor">1</int>
<int name="TorsoColor">1</int>
<bool name="archivable">true</bool>
</Properties>
</Item>
</roblox>

44
api/users/getBadges.php Normal file
View File

@ -0,0 +1,44 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize();
if(!isset($_GET['userID'])){ api::respond(400, false, "Invalid Request - userID not set"); }
if(!is_numeric($_GET['userID'])){ api::respond(400, false, "Invalid Request - userID is not numeric"); }
if(!isset($_GET['type'])){ api::respond(400, false, "Invalid Request - badge type not set"); }
if(!in_array($_GET['type'], ["user-badges", "polygon-badges"])){ api::respond(400, false, "Invalid Request - invalid badge type"); }
$selfProfile = isset($_SERVER['HTTP_REFERER']) && str_ends_with($_SERVER['HTTP_REFERER'], "/user");
$page = $_GET['page'] ?? 1;
$type = $_GET['type'] ?? false;
$userinfo = users::getUserInfoFromUid($_GET['userID']);
if(!$userinfo) api::respond(400, false, "User does not exist");
$badges = [];
if($_GET['type'] == "polygon-badges")
{
if($userinfo->adminlevel)
{
$badges[] = ["name" => "Administrator", "id" => 1];
}
if($userinfo->adminlevel == 2)
{
//$badges[] = ["badgeName" => "Administrator", "badgeId" => 2];
}
}
else
{
}
if($badges == [])
{
$responsemsg = ($selfProfile?"You have":$userinfo->username." has")."n't earned any ";
$responsemsg .= $type == "user-badges" ? "player-made badges" : "Polygon badges";
api::respond(200, true, $responsemsg);
}
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "badges" => $badges]));

View File

@ -0,0 +1,55 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
api::initialize(["method" => "POST"]);
$self = isset($_SERVER['HTTP_REFERER']) && (str_ends_with($_SERVER['HTTP_REFERER'], "/my/stuff") || str_ends_with($_SERVER['HTTP_REFERER'], "/user"));
$userId = $_POST["userId"] ?? false;
$type = $_POST["type"] ?? false;
$page = $_POST["page"] ?? 1;
$assets = [];
if(!catalog::getTypeByNum($type)) api::respond(400, false, "Invalid asset type");
$type_str = catalog::getTypeByNum($type);
$query = $pdo->prepare("SELECT COUNT(*) FROM ownedAssets INNER JOIN assets ON assets.id = assetId WHERE userId = :uid AND assets.type = :type");
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
$query->bindParam(":type", $type, PDO::PARAM_INT);
$query->execute();
$pages = ceil($query->fetchColumn()/18);
$offset = ($page - 1)*18;
if(!$pages) api::respond(200, true, ($self?'You have':users::getUserNameFromUid($userId).' has').' not purchased any '.plural($type_str));
$query = $pdo->prepare("
SELECT assets.*, users.username,
(SELECT COUNT(*) FROM ownedAssets WHERE assetId = assets.id AND userId != assets.creator) AS sales_total
FROM ownedAssets
INNER JOIN assets ON assets.id = assetId
INNER JOIN users ON creator = users.id
WHERE userId = :uid AND assets.type = :type ORDER BY ownedAssets.id DESC LIMIT 18 OFFSET :offset");
$query->bindParam(":uid", $userId, PDO::PARAM_INT);
$query->bindParam(":type", $type, PDO::PARAM_INT);
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
$query->execute();
while($asset = $query->fetch(PDO::FETCH_OBJ))
{
$price = '<span class="text-success">';
if($asset->sale) $price .= $asset->price ? '<i class="fal fa-pizza-slice"></i> '.$asset->price : 'Free';
$price .= '</span>';
$assets[] =
[
"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),
"creator_id" => $asset->creator,
"creator_name" => $asset->username,
"price" => $price
];
}
die(json_encode(["status" => 200, "success" => true, "message" => "OK", "pages" => $pages, "assets" => $assets]));

View File

@ -0,0 +1 @@
0 0 0 0

29
asset/bodycolors.php Normal file
View File

@ -0,0 +1,29 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
$uid = $_GET['userId'] ?? $_GET['userid'] ?? false;
$info = users::getUserInfoFromUid($uid);
if(!$info) pageBuilder::errorCode(404);
$bodycolors = json_decode($info->bodycolors);
header("content-type: application/xml");
header("Pragma: no-cache");
header("Cache-Control: no-cache");
?>
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<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="BodyColors">
<Properties>
<int name="HeadColor"><?=$bodycolors->Head?></int>
<int name="LeftArmColor"><?=$bodycolors->{'Left Arm'}?></int>
<int name="LeftLegColor"><?=$bodycolors->{'Left Leg'}?></int>
<string name="Name">Body Colors</string>
<int name="RightArmColor"><?=$bodycolors->{'Right Arm'}?></int>
<int name="RightLegColor"><?=$bodycolors->{'Right Leg'}?></int>
<int name="TorsoColor"><?=$bodycolors->Torso?></int>
<bool name="archivable">true</bool>
</Properties>
</Item>
</roblox>

14
asset/characterfetch.php Normal file
View File

@ -0,0 +1,14 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
$uid = $_GET['userId'] ?? $_GET['userid'] ?? false;
$sid = $_GET['serverId'] ?? $_GET['serverid'] ?? false;
$host = $_GET['assetHost'] ?? $_SERVER['HTTP_HOST'];
$info = users::getUserInfoFromUid($uid);
if(!$info) pageBuilder::errorCode(500);
header("Pragma: no-cache");
header("Cache-Control: no-cache");
header("content-type: text/plain; charset=utf-8");
echo users::getCharacterAppearance($uid, $sid, $host);

81
asset/index.php Normal file
View File

@ -0,0 +1,81 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
header("Cache-Control: max-age=120");
$id = $_GET['ID'] ?? $_GET['id'] ?? false;
$assetHost = $_GET['host'] ?? $_SERVER['HTTP_HOST'];
$force = isset($_GET['force']);
$rblxasset = false;
$query = $pdo->prepare("SELECT * FROM assets WHERE id = :id");
$query->bindParam(":id", $id, PDO::PARAM_INT);
$query->execute();
$asset = $query->fetch(PDO::FETCH_OBJ);
if(!$asset || isset($_GET['forcerblxasset'])) $rblxasset = true;
// so i dont have a url redirect in the client just yet
// meaning that we're gonna have to replace the roblox.com urls on the fly
// and in order to do that the server has to actually fetch the asset data
// this is absolutely gonna tank performance but for the meantime theres
// not much else i can do
if($rblxasset)
{
// we're only interested in replacing the asset urls of models
$apidata = json_decode(file_get_contents("https://api.roblox.com/marketplace/productinfo?assetId=".$_GET['id']));
if(!in_array($apidata->AssetTypeId, [9, 10])) die(header("Location: https://assetdelivery.roblox.com/v1/asset/?".$_SERVER['QUERY_STRING']));
// /asset/?id= just redirects to a url on roblox's cdn so we need to get that redirect url
$ch = curl_init();
curl_setopt_array($ch, [CURLOPT_URL => "https://assetdelivery.roblox.com/v1/asset/?".$_SERVER['QUERY_STRING'], CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false]);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpcode != 302) die(http_response_code($httpcode));
$assetData = file_get_contents(curl_getinfo($ch, CURLINFO_REDIRECT_URL));
}
else
{
if(!$force && $asset->approved != 1) die(http_response_code(403));
if(!$force && !$asset->publicDomain && (!SESSION || SESSION["userId"] != $asset->creator)) die(http_response_code(403));
if(!file_exists("./files/$id")) die(http_response_code(404));
$assetData = file_get_contents("./files/".$asset->id);
if($asset->type == 10 && !stripos($assetData, 'roblox')) $assetData = gzip::decompress("./files/".$asset->id);
}
// replace asset urls
if($force) $assetData = preg_replace("/%ASSETURL%([0-9]+)/i", "http://$assetHost/asset/?id=$1&force=true", $assetData);
else $assetData = str_replace("%ASSETURL%", "http://$assetHost/asset/?id=", $assetData);
$assetData = str_ireplace(
["www.roblox.com/asset", "roblox.com/asset", "www.roblox.com/thumbs", "roblox.com/thumbs"],
["$assetHost/asset", "$assetHost/asset", "$assetHost/thumbs", "$assetHost/thumbs"],
$assetData);
if(!$rblxasset && $asset->type == 3 && isset($_GET['audiostream']))
{
header('Content-Type: '.$asset->audioType);
header('Content-Disposition: attachment; filename="'.htmlentities($asset->name).str_replace(
["audio/mpeg", "audio/ogg", "audio/mid", "audio/wav"],
[".mp3", ".ogg", ".mid", ".wav"],
$asset->audioType).'"');
}
else
{
header('Content-Type: binary/octet-stream');
if($rblxasset) header('Content-Disposition: attachment');
else header('Content-Disposition: attachment; filename="'.md5_file("./files/".$asset->id).'"');
}
if(!$rblxasset && $asset->type == 5)
{
// all lua assets must be signed
$assetData = "%".$asset->id."%\n".$assetData;
openssl_sign($assetData, $signature, openssl_pkey_get_private("file://".$_SERVER['DOCUMENT_ROOT']."/../polygon_private.pem"));
$assetData = "%".base64_encode($signature)."%".$assetData;
}
header('Content-Length: '.strlen($assetData));
die($assetData);

71
browse.php Normal file
View File

@ -0,0 +1,71 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
users::requireLogin();
$keyword = $_GET['SearchTextBox'] ?? false;
$page = $_GET['PageNumber'] ?? 1;
$keyword_sql = "%$keyword%";
$query = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username LIKE :keywd AND NOT (SELECT COUNT(*) FROM bans WHERE userId = users.id AND NOT isDismissed)");
$query->bindParam(":keywd", $keyword_sql, PDO::PARAM_STR);
$query->execute();
$pages = ceil($query->fetchColumn()/15);
if($page > $pages) $page = $pages;
if(!is_numeric($page) || $page < 1) $page = 1;
$offset = ($page - 1)*15;
$query = $pdo->prepare("SELECT * FROM users WHERE username LIKE :keywd AND NOT (SELECT COUNT(*) FROM bans WHERE userId = users.id AND NOT isDismissed) ORDER BY lastonline DESC LIMIT 15 OFFSET $offset");
$query->bindParam(":keywd", $keyword_sql, PDO::PARAM_STR);
$query->execute();
function buildURL($page)
{
global $keyword;
$url = "?";
if($keyword) $url .= "SearchTextBox=$keyword&";
$url .= "PageNumber=$page";
return $url;
}
pageBuilder::$pageConfig["title"] = "Browse People";
pageBuilder::buildHeader();
?>
<form>
<div class="form-group row">
<label for="SearchTextBox" class="col-sm-1 col-form-label">Search: </label>
<input type="text" class="form-control col-sm-7 mx-2" name="SearchTextBox" id="SearchTextBox" value="<?=htmlspecialchars($keyword)?>">
<button class="btn btn-light">Search Users</button>
</div>
<?php if($query->rowCount()) { ?>
<table class="table table-hover">
<thead class="table-bordered bg-light">
<tr>
<th class="font-weight-normal py-2" scope="col" style="width:5%">Avatar</th>
<th class="font-weight-normal py-2" scope="col" style="width:20%">Name</th>
<th class="font-weight-normal py-2" scope="col" style="width:50%">Blurb</th>
<th class="font-weight-normal py-2" scope="col" style="width:20%">Location / Last Seen</th>
</tr>
</thead>
<tbody>
<?php while($row = $query->fetch(PDO::FETCH_OBJ)) { $status = users::getOnlineStatus($row->id); ?>
<tr>
<td><img src="<?=Thumbnails::GetAvatar($row->id, 100, 100)?>" title="<?=$row->username?>" alt="<?=$row->username?>" width="63" height="63"></td>
<td><a href="/user?ID=<?=$row->id?>"><?=$row->username?></a></td>
<td class="text-break"><?=polygon::filterText($row->blurb)?></td>
<td><span<?=$status["attributes"]?:''?>><?=$status["text"]?></span></td>
</tr>
<?php } ?>
</tbody>
</table>
<?php } else { ?>
<p class="text-center">No results matched your search query</p>
<?php } if($pages > 1) { ?>
<div class="pagination form-inline justify-content-center">
<a class="btn btn-light back<?=$page<=1?' disabled':'" href="'.buildURL($page-1)?>"><h5 class="mb-0"><i class="fal fa-caret-left"></i></h5></a>
<span class="px-3">Page <?=$page?> of <?=$pages?></span>
<a class="btn btn-light next<?=$page>=$pages?' disabled':'" href="'.buildURL($page+1)?>"><h5 class="mb-0"><i class="fal fa-caret-right"></i></h5></a>
</div>
<?php } ?>
</form>
<?php pageBuilder::buildFooter(); ?>

250
catalog.php Normal file
View File

@ -0,0 +1,250 @@
<?php
require $_SERVER['DOCUMENT_ROOT'].'/api/private/core.php';
// users::requireLogin();
// this is a catastrophe
$cats =
[
1 => ["name" => "All Categories", "price" => true],
3 => ["name" => "Clothing", "subcategories" => [8, 11, 2, 12], "price" => true],
4 => ["name" => "Body Parts", "subcategories" => [17, 18], "price" => true],
5 => ["name" => "Gear", "type" => 19, "price" => true],
6 => ["name" => "Models", "type" => 10, "price" => false],
8 => ["name" => "Decals", "type" => 13, "price" => false],
9 => ["name" => "Audio", "type" => 3, "price" => false],
];
$sorts =
[
0 => "sales DESC",
1 => "updated DESC",
2 => "price DESC",
3 => "price"
];
function getUrl($strings)
{
global $cat, $subcat;
$url = "?";
if($subcat) $url .= "Subcategory=".$subcat."&";
$url .= "CurrencyType=%currency%&SortType=%sort%&Category=".$cat;
return str_replace(["%currency%", "%sort%"], $strings, $url);
}
$cat = $_GET['Category'] ?? 3;
$subcat = $_GET['Subcategory'] ?? false;
$keyword = $_GET['Keyword'] ?? "";
$keyword_sql = "%$keyword%";
$currency = $_GET['CurrencyType'] ?? 0;
$sort = $_GET['SortType'] ?? 1;
$page = $_GET['PageNumber'] ?? 1;
if($cat == 3 && !isset($_GET['Category']))
$subcat = 8;
if(!isset($cats[$cat]))
die(header("Location: /catalog"));
if($subcat && (isset($cats[$cat]["type"]) || !in_array($subcat, $cats[$cat]["subcategories"])))
die(header("Location: /catalog?Category=".$cat));
if(!in_array($currency, [0, 1, 2]))
die(header("Location: ".getUrl([0, $sort])));
if(!isset($sorts[$sort]))
die(header("Location: ".getUrl([$currency, 0])));
$queryparam = "";
$type = $subcat ?: $cats[$cat]["type"] ?? 2;
// adding "is not null" fetches the item even if the price is 0
$unavailable = isset($_GET['IncludeNotForSale']) && $_GET['IncludeNotForSale'] == "true";
if($unavailable) $queryparam .= "IS NOT NULL ";
// process query parameters for the item type
$queryparam .= "AND type";
if($cat == 1) $queryparam .= " IN (2, 3, 8, 10, 11, 12, 13, 17, 18, 19)";
elseif(isset($cats[$cat]["type"]) || $subcat) $queryparam .= " = ".($cats[$cat]["type"] ?? $subcat);
else $queryparam .= " IN (".implode(", ", $cats[$cat]["subcategories"]).")";
// process query parameters for the item price
$queryparam .= " AND price";
if(is_numeric($currency) && $currency == 0) $queryparam .= " IS NOT NULL";
elseif($currency == 2) $queryparam .= " = 0";
// get the number of assets matching the query
$results = db::run(
"SELECT COUNT(*) FROM assets WHERE type != 1 AND name LIKE :keywd AND approved != 2 AND sale $queryparam",
[":keywd" => $keyword_sql]
)->fetchColumn();
$pages = ceil($results/18);
if($page > $pages) $page = $pages;
if(!is_numeric($page) || $page < 1) $page = 1;
$offset = ($page - 1)*18;
$query = $pdo->prepare("
SELECT assets.*, users.username,
(SELECT COUNT(*) FROM ownedAssets WHERE assetId = assets.id AND userId != assets.creator) AS sales
FROM assets INNER JOIN users ON creator = users.id
WHERE type != 1 AND name LIKE :keywd AND approved != 2 AND sale $queryparam
ORDER BY ".$sorts[$sort]." LIMIT 18 OFFSET :offset");
$query->bindParam(":keywd", $keyword_sql, PDO::PARAM_STR);
$query->bindParam(":offset", $offset, PDO::PARAM_INT);
$query->execute();
pageBuilder::$polygonScripts[] = "/js/polygon/catalog.js";
pageBuilder::$pageConfig["title"] = "Avatar Items, Virtual Avatars, Virtual Goods";
pageBuilder::buildHeader();
?>
<script>
polygon.catalog =
{
PageNumber: <?=isset($_GET['PageNumber']) ? $page : "null"?>,
Subcategory: <?=$subcat ?: "null"?>,
Category: <?=$cat?>,
Keyword: <?='"'.urlencode($keyword).'"' ?: "null"?>,
CurrencyType: <?=$currency?>,
SortType: <?=$sort?>,
IncludeNotForSale: <?=$unavailable ? "true" : "null"?>,
};
</script>
<div class="row">
<div class="col-xl-2 col-lg-3 col-md-3">
<h2 class="font-weight-normal">Catalog</h2>
</div>
<div class="col-xl-10 col-lg-9 col-md-9">
<div class="input-group CatalogSearchBar">
<input type="text" class="form-control mb-2 keywordTextbox" value="<?=htmlspecialchars($keyword)?>">
<div class="input-group-append">
<select class="form-control mb-2 categoriesForKeyword rounded-0" style="width:auto">
<?php if(isset($cats[$cat]["subcategories"]) && $subcat) { ?>
<option value="Custom" selected="selected"><?=plural(catalog::getTypeByNum($type))?></option>
<?php } foreach($cats as $cat_id => $cat_data) { ?>
<option value="<?=$cat_id?>"<?=!isset($_GET['Subcategory']) && $cat==$cat_id?' selected':''?>><?=$cat_data["name"]?></option>
<?php } ?>
</select>
<button class="btn btn-outline-primary mb-2 submitSearchButton">Search</button>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-xl-2 col-lg-3 col-md-3 pb-4 pl-3 pr-0 divider-right">
<div class="dropdown show mr-3 mb-4">
<a class="btn btn-secondary btn-block text-left"<?=isset($_GET['Category'])?' href="#" role="button" id="browseByCategory" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"':''?>>
<h5 class="font-weight-normal m-0">Category <i class="mt-1 ml-2 mr-1 fas fa-caret-down float-right"></i></h5>
</a>
<div class="bg-light dropdown-menu w-100<?=!isset($_GET['Category'])?' d-block':''?>" style="min-width:5rem;" aria-labelledby="browseByCategory">
<?php foreach($cats as $dropdown_id => $dropdown_cat) { ?>
<a href="#category=<?=strtolower(str_replace(' ', '', $dropdown_cat["name"]))?>" class="dropdown-item assetTypeFilter" data-category="<?=$dropdown_id?>"><?=$dropdown_cat["name"]?></a>
<?php } ?>
</div>
</div>
<?php if(isset($_GET['Category'])) { ?>
<h3 class="font-weight-normal pb-0">Filters</h3>
<div class="filters mt-1">
<?php if(strlen($keyword)) { ?>
<h6 class="ml-2 font-weight-normal mb-1">Category</h6>
<div class="ml-3 filter-category">
<?php if(isset($cats[$cat]["subcategories"]) && $subcat) { ?>
<p class="m-0"><?=plural(catalog::getTypeByNum($type))?></p>
<?php } foreach($cats as $cat_id => $cat_data) { ?>
<p class="m-0"><a href="#category=<?=$cat_data["name"]?>" class="assetTypeFilter<?=$cat==$cat_id?' text-dark text-decoration-none':''?>" data-keepfilters="true" data-category="<?=$cat_id?>"><?=$cat_data["name"]?></a></p>
<?php } ?>
</div>
<div class="divider-bottom my-3"></div>
<?php } elseif(isset($cats[$cat]["subcategories"])) { ?>
<h6 class="ml-2 font-weight-normal mb-1"><?=$cats[$cat]["name"]?> Type</h6>
<div class="ml-3 filter-subcategory">
<p class="m-0"><a href="#category=All <?=$cats[$cat]["name"]?>" class="assetTypeFilter<?=!$subcat?' text-dark text-decoration-none':''?>" data-types="3">All <?=$cats[$cat]["name"]?></a></p>
<?php foreach($cats[$cat]["subcategories"] as $cat_id) { ?>
<p class="m-0"><a href="#category=<?=plural(catalog::getTypeByNum($cat_id))?>" class="assetTypeFilter<?=$subcat==$cat_id?' text-dark text-decoration-none':''?>" data-types="<?=$cat_id?>"><?=plural(catalog::getTypeByNum($cat_id))?></a></p>
<?php } ?>
</div>
<div class="divider-bottom my-3"></div>
<?php } ?>
<h6 class="ml-2 font-weight-normal mb-1">Currency / Price</h6>
<div class="ml-3 filter-clothing-type">
<p class="m-0"><a href="#price=0" class="priceFilter<?=$currency==0?' text-dark text-decoration-none':''?>" data-currencytype="0">All Currency</a></p>
<?php if($cats[$cat]["price"]){ ?>
<p class="m-0"><a href="#price=1"class="priceFilter<?=$currency==1?' text-dark text-decoration-none':''?>" data-currencytype="1">Pizzas</a></p>
<?php } ?>
<p class="m-0"><a href="#price=2" class="priceFilter<?=$currency==2?' text-dark text-decoration-none':''?>" data-currencytype="2">Free</a></p>
<div class="form-check">
<input type="checkbox" class=" form-check-input" id="includeNotForSaleCheckbox"<?=$unavailable?' checked="checked"':''?>>
<label class="form-check-label col-form-label-sm pt-0" for="includeNotForSaleCheckbox">Show off-sale items</label>
</div>
</div>
</div>
<?php } ?>
</div>
<div class="col-xl-10 col-lg-9 col-md-9 p-0 pl-3 pr-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb p-0 m-0" style="background-color:transparent">
<?php if(!$subcat || isset($cats[$cat]["type"])) { ?>
<li class="breadcrumb-item text-dark active"><?=$cats[$cat]["name"]?></li>
<?php } else { ?>
<li class="breadcrumb-item text-dark"><a href="?SortType=1&SortCurrency=0&Category=<?=$cat?>"><?=$cats[$cat]["name"]?></a></li>
<li class="breadcrumb-item text-dark active"><?=plural(catalog::getTypeByNum($type))?></li>
<?php } ?>
</ol>
</nav>
<?php if($query->rowCount()) { ?>
<div class="row">
<div class="col-xl-9 col-lg-8 col-md-6">
<p>Showing <?=number_format($offset+1)?> - <?=number_format($offset+$query->rowCount())?> of <?=number_format($results)?> results</p>
</div>
<div class="col-xl-3 col-lg-4 col-md-6 pr-2 mb-2 d-flex">
<label class="form-label form-label-sm" for="sortBy" style="width:6rem;">Sort by: </label>
<select class="Sort form-control form-control-sm" id="sortBy">
<option value="0"<?=$sort==0?' selected="selected"':''?>>Bestselling</option>
<option value="1"<?=$sort==1?' selected="selected"':''?>>Recently updated</option>
<option value="2"<?=$sort==2?' selected="selected"':''?>>Price (High to Low)</option>
<option value="3"<?=$sort==3?' selected="selected"':''?>>Price (Low to High)</option>
</select>
</div>
</div>
<?php } else { ?>
<p class="text-center">No results matched your criteria</p>
<?php } ?>
<div class="items row pl-2">
<?php while($item = $query->fetch(PDO::FETCH_OBJ)) { ?>
<div class="item col-xl-2 col-lg-3 col-md-3 col-sm-4 col-6 pb-3 px-2" style="line-height:normal">
<div class="card info hover">
<a href="/<?=encode_asset_name($item->name)?>-item?id=<?=$item->id?>"><img src="/thumbs/catalog-loading.png" preload-src="<?=Thumbnails::GetAsset($item, 420, 420)?>" class="card-img-top img-fluid p-2" title="<?=polygon::filterText($item->name)?>" alt="<?=polygon::filterText($item->name)?>"></a>
<div class="card-body pt-0 px-2 pb-2">
<p class="text-truncate text-primary m-0" title="<?=polygon::filterText($item->name)?>"><a href="/<?=encode_asset_name($item->name)?>-item?id=<?=$item->id?>"><?=polygon::filterText($item->name)?></a></p>
<?php if($item->sale) { ?><p class="m-0<?=$item->price?' text-success':''?>"><?=$item->price ? '<i class="fal fa-pizza-slice"></i> '.number_format($item->price):'Free'?></p><?php } ?>
</div>
</div>
<div class="details-wrapper">
<div class="card details d-none">
<div class="card-body pt-0 px-2 pb-2">
<p class="text-truncate m-0"><small class="text-muted">Creator: <a href="/user?ID=<?=$item->creator?>"><?=$item->username?></a></small></p>
<p class="text-truncate m-0"><small class="text-muted">Updated: <span class="text-dark"><?=timeSince($item->updated)?></span></small></p>
<p class="text-truncate m-0"><small class="text-muted">Sales: <span class="text-dark"><?=number_format($item->sales)?></span></small></p>
</div>
</div>
</div>
</div>
<?php } ?>
</div>
<?php if($pages > 1) { ?>
<div class="pagination form-inline justify-content-center">
<button type="button" class="btn btn-light mx-3 back"<?=$page<=1?' disabled':''?>><h5 class="mb-0"><i class="fal fa-caret-left"></i></h5></button>
<span>Page</span>
<input class="form-control form-control-sm text-center mx-1 page" type="text" style="width:30px" value="<?=$page?>">
<span>of <?=$pages?></span>
<button type="button" class="btn btn-light mx-3 next"<?=$page>=$pages?' disabled':''?>><h5 class="mb-0"><i class="fal fa-caret-right"></i></h5></button>
</div>
<?php } ?>
</div>
</div>
<script>
$(".items .item img").each(function(){ $(this).attr("src", $(this).attr("preload-src")); });
$(".items .item").hover(function(){ $(this).find(".details").removeClass("d-none"); }, function(){ $(this).find(".details").addClass("d-none"); });
</script>
<?php pageBuilder::buildFooter(); ?>

10
css/bootstrap-colorpicker.min.css vendored Normal file

File diff suppressed because one or more lines are too long

7
css/bootstrap-datepicker.min.css vendored Normal file

File diff suppressed because one or more lines are too long

7
css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
css/fontawesome-all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Brands';
font-style: normal;
font-weight: normal;
src: url("../webfonts/fa-brands-400.eot");
src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
.fab {
font-family: 'Font Awesome 5 Brands'; }

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands}

View File

@ -0,0 +1,14 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Free';
font-style: normal;
font-weight: 400;
src: url("../webfonts/fa-regular-400.eot");
src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
.far {
font-family: 'Font Awesome 5 Free';
font-weight: 400; }

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:Font Awesome\ 5 Free;font-weight:400}

View File

@ -0,0 +1,15 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face {
font-family: 'Font Awesome 5 Free';
font-style: normal;
font-weight: 900;
src: url("../webfonts/fa-solid-900.eot");
src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); }
.fa,
.fas {
font-family: 'Font Awesome 5 Free';
font-weight: 900; }

View File

@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:Font Awesome\ 5 Free;font-weight:900}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,19 @@
// Animated Icons
// --------------------------
.@{fa-css-prefix}-spin {
animation: fa-spin 2s infinite linear;
}
.@{fa-css-prefix}-pulse {
animation: fa-spin 1s infinite steps(8);
}
@keyframes fa-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,16 @@
// Bordered & Pulled
// -------------------------
.@{fa-css-prefix}-border {
border-radius: .1em;
border: solid .08em @fa-border-color;
padding: .2em .25em .15em;
}
.@{fa-css-prefix}-pull-left { float: left; }
.@{fa-css-prefix}-pull-right { float: right; }
.@{fa-css-prefix}, .fas, .far, .fal, .fab {
&.@{fa-css-prefix}-pull-left { margin-right: .3em; }
&.@{fa-css-prefix}-pull-right { margin-left: .3em; }
}

View File

@ -0,0 +1,12 @@
// Base Class Definition
// -------------------------
.@{fa-css-prefix}, .fas, .far, .fal, .fab {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
}

View File

@ -0,0 +1,6 @@
// Fixed Width Icons
// -------------------------
.@{fa-css-prefix}-fw {
text-align: center;
width: (20em / 16);
}

View File

@ -0,0 +1,992 @@
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
.@{fa-css-prefix}-500px:before { content: @fa-var-500px; }
.@{fa-css-prefix}-accessible-icon:before { content: @fa-var-accessible-icon; }
.@{fa-css-prefix}-accusoft:before { content: @fa-var-accusoft; }
.@{fa-css-prefix}-address-book:before { content: @fa-var-address-book; }
.@{fa-css-prefix}-address-card:before { content: @fa-var-address-card; }
.@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; }
.@{fa-css-prefix}-adn:before { content: @fa-var-adn; }
.@{fa-css-prefix}-adversal:before { content: @fa-var-adversal; }
.@{fa-css-prefix}-affiliatetheme:before { content: @fa-var-affiliatetheme; }
.@{fa-css-prefix}-algolia:before { content: @fa-var-algolia; }
.@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; }
.@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; }
.@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; }
.@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; }
.@{fa-css-prefix}-allergies:before { content: @fa-var-allergies; }
.@{fa-css-prefix}-amazon:before { content: @fa-var-amazon; }
.@{fa-css-prefix}-amazon-pay:before { content: @fa-var-amazon-pay; }
.@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; }
.@{fa-css-prefix}-american-sign-language-interpreting:before { content: @fa-var-american-sign-language-interpreting; }
.@{fa-css-prefix}-amilia:before { content: @fa-var-amilia; }
.@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; }
.@{fa-css-prefix}-android:before { content: @fa-var-android; }
.@{fa-css-prefix}-angellist:before { content: @fa-var-angellist; }
.@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; }
.@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; }
.@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; }
.@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; }
.@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; }
.@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; }
.@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; }
.@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; }
.@{fa-css-prefix}-angrycreative:before { content: @fa-var-angrycreative; }
.@{fa-css-prefix}-angular:before { content: @fa-var-angular; }
.@{fa-css-prefix}-app-store:before { content: @fa-var-app-store; }
.@{fa-css-prefix}-app-store-ios:before { content: @fa-var-app-store-ios; }
.@{fa-css-prefix}-apper:before { content: @fa-var-apper; }
.@{fa-css-prefix}-apple:before { content: @fa-var-apple; }
.@{fa-css-prefix}-apple-pay:before { content: @fa-var-apple-pay; }
.@{fa-css-prefix}-archive:before { content: @fa-var-archive; }
.@{fa-css-prefix}-arrow-alt-circle-down:before { content: @fa-var-arrow-alt-circle-down; }
.@{fa-css-prefix}-arrow-alt-circle-left:before { content: @fa-var-arrow-alt-circle-left; }
.@{fa-css-prefix}-arrow-alt-circle-right:before { content: @fa-var-arrow-alt-circle-right; }
.@{fa-css-prefix}-arrow-alt-circle-up:before { content: @fa-var-arrow-alt-circle-up; }
.@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; }
.@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; }
.@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; }
.@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; }
.@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; }
.@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; }
.@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; }
.@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; }
.@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; }
.@{fa-css-prefix}-arrows-alt-h:before { content: @fa-var-arrows-alt-h; }
.@{fa-css-prefix}-arrows-alt-v:before { content: @fa-var-arrows-alt-v; }
.@{fa-css-prefix}-assistive-listening-systems:before { content: @fa-var-assistive-listening-systems; }
.@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; }
.@{fa-css-prefix}-asymmetrik:before { content: @fa-var-asymmetrik; }
.@{fa-css-prefix}-at:before { content: @fa-var-at; }
.@{fa-css-prefix}-audible:before { content: @fa-var-audible; }
.@{fa-css-prefix}-audio-description:before { content: @fa-var-audio-description; }
.@{fa-css-prefix}-autoprefixer:before { content: @fa-var-autoprefixer; }
.@{fa-css-prefix}-avianex:before { content: @fa-var-avianex; }
.@{fa-css-prefix}-aviato:before { content: @fa-var-aviato; }
.@{fa-css-prefix}-aws:before { content: @fa-var-aws; }
.@{fa-css-prefix}-backward:before { content: @fa-var-backward; }
.@{fa-css-prefix}-balance-scale:before { content: @fa-var-balance-scale; }
.@{fa-css-prefix}-ban:before { content: @fa-var-ban; }
.@{fa-css-prefix}-band-aid:before { content: @fa-var-band-aid; }
.@{fa-css-prefix}-bandcamp:before { content: @fa-var-bandcamp; }
.@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; }
.@{fa-css-prefix}-bars:before { content: @fa-var-bars; }
.@{fa-css-prefix}-baseball-ball:before { content: @fa-var-baseball-ball; }
.@{fa-css-prefix}-basketball-ball:before { content: @fa-var-basketball-ball; }
.@{fa-css-prefix}-bath:before { content: @fa-var-bath; }
.@{fa-css-prefix}-battery-empty:before { content: @fa-var-battery-empty; }
.@{fa-css-prefix}-battery-full:before { content: @fa-var-battery-full; }
.@{fa-css-prefix}-battery-half:before { content: @fa-var-battery-half; }
.@{fa-css-prefix}-battery-quarter:before { content: @fa-var-battery-quarter; }
.@{fa-css-prefix}-battery-three-quarters:before { content: @fa-var-battery-three-quarters; }
.@{fa-css-prefix}-bed:before { content: @fa-var-bed; }
.@{fa-css-prefix}-beer:before { content: @fa-var-beer; }
.@{fa-css-prefix}-behance:before { content: @fa-var-behance; }
.@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; }
.@{fa-css-prefix}-bell:before { content: @fa-var-bell; }
.@{fa-css-prefix}-bell-slash:before { content: @fa-var-bell-slash; }
.@{fa-css-prefix}-bicycle:before { content: @fa-var-bicycle; }
.@{fa-css-prefix}-bimobject:before { content: @fa-var-bimobject; }
.@{fa-css-prefix}-binoculars:before { content: @fa-var-binoculars; }
.@{fa-css-prefix}-birthday-cake:before { content: @fa-var-birthday-cake; }
.@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; }
.@{fa-css-prefix}-bitcoin:before { content: @fa-var-bitcoin; }
.@{fa-css-prefix}-bity:before { content: @fa-var-bity; }
.@{fa-css-prefix}-black-tie:before { content: @fa-var-black-tie; }
.@{fa-css-prefix}-blackberry:before { content: @fa-var-blackberry; }
.@{fa-css-prefix}-blender:before { content: @fa-var-blender; }
.@{fa-css-prefix}-blind:before { content: @fa-var-blind; }
.@{fa-css-prefix}-blogger:before { content: @fa-var-blogger; }
.@{fa-css-prefix}-blogger-b:before { content: @fa-var-blogger-b; }
.@{fa-css-prefix}-bluetooth:before { content: @fa-var-bluetooth; }
.@{fa-css-prefix}-bluetooth-b:before { content: @fa-var-bluetooth-b; }
.@{fa-css-prefix}-bold:before { content: @fa-var-bold; }
.@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; }
.@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; }
.@{fa-css-prefix}-book:before { content: @fa-var-book; }
.@{fa-css-prefix}-book-open:before { content: @fa-var-book-open; }
.@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; }
.@{fa-css-prefix}-bowling-ball:before { content: @fa-var-bowling-ball; }
.@{fa-css-prefix}-box:before { content: @fa-var-box; }
.@{fa-css-prefix}-box-open:before { content: @fa-var-box-open; }
.@{fa-css-prefix}-boxes:before { content: @fa-var-boxes; }
.@{fa-css-prefix}-braille:before { content: @fa-var-braille; }
.@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; }
.@{fa-css-prefix}-briefcase-medical:before { content: @fa-var-briefcase-medical; }
.@{fa-css-prefix}-broadcast-tower:before { content: @fa-var-broadcast-tower; }
.@{fa-css-prefix}-broom:before { content: @fa-var-broom; }
.@{fa-css-prefix}-btc:before { content: @fa-var-btc; }
.@{fa-css-prefix}-bug:before { content: @fa-var-bug; }
.@{fa-css-prefix}-building:before { content: @fa-var-building; }
.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; }
.@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; }
.@{fa-css-prefix}-burn:before { content: @fa-var-burn; }
.@{fa-css-prefix}-buromobelexperte:before { content: @fa-var-buromobelexperte; }
.@{fa-css-prefix}-bus:before { content: @fa-var-bus; }
.@{fa-css-prefix}-buysellads:before { content: @fa-var-buysellads; }
.@{fa-css-prefix}-calculator:before { content: @fa-var-calculator; }
.@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; }
.@{fa-css-prefix}-calendar-alt:before { content: @fa-var-calendar-alt; }
.@{fa-css-prefix}-calendar-check:before { content: @fa-var-calendar-check; }
.@{fa-css-prefix}-calendar-minus:before { content: @fa-var-calendar-minus; }
.@{fa-css-prefix}-calendar-plus:before { content: @fa-var-calendar-plus; }
.@{fa-css-prefix}-calendar-times:before { content: @fa-var-calendar-times; }
.@{fa-css-prefix}-camera:before { content: @fa-var-camera; }
.@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; }
.@{fa-css-prefix}-capsules:before { content: @fa-var-capsules; }
.@{fa-css-prefix}-car:before { content: @fa-var-car; }
.@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; }
.@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; }
.@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; }
.@{fa-css-prefix}-caret-square-down:before { content: @fa-var-caret-square-down; }
.@{fa-css-prefix}-caret-square-left:before { content: @fa-var-caret-square-left; }
.@{fa-css-prefix}-caret-square-right:before { content: @fa-var-caret-square-right; }
.@{fa-css-prefix}-caret-square-up:before { content: @fa-var-caret-square-up; }
.@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; }
.@{fa-css-prefix}-cart-arrow-down:before { content: @fa-var-cart-arrow-down; }
.@{fa-css-prefix}-cart-plus:before { content: @fa-var-cart-plus; }
.@{fa-css-prefix}-cc-amazon-pay:before { content: @fa-var-cc-amazon-pay; }
.@{fa-css-prefix}-cc-amex:before { content: @fa-var-cc-amex; }
.@{fa-css-prefix}-cc-apple-pay:before { content: @fa-var-cc-apple-pay; }
.@{fa-css-prefix}-cc-diners-club:before { content: @fa-var-cc-diners-club; }
.@{fa-css-prefix}-cc-discover:before { content: @fa-var-cc-discover; }
.@{fa-css-prefix}-cc-jcb:before { content: @fa-var-cc-jcb; }
.@{fa-css-prefix}-cc-mastercard:before { content: @fa-var-cc-mastercard; }
.@{fa-css-prefix}-cc-paypal:before { content: @fa-var-cc-paypal; }
.@{fa-css-prefix}-cc-stripe:before { content: @fa-var-cc-stripe; }
.@{fa-css-prefix}-cc-visa:before { content: @fa-var-cc-visa; }
.@{fa-css-prefix}-centercode:before { content: @fa-var-centercode; }
.@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; }
.@{fa-css-prefix}-chalkboard:before { content: @fa-var-chalkboard; }
.@{fa-css-prefix}-chalkboard-teacher:before { content: @fa-var-chalkboard-teacher; }
.@{fa-css-prefix}-chart-area:before { content: @fa-var-chart-area; }
.@{fa-css-prefix}-chart-bar:before { content: @fa-var-chart-bar; }
.@{fa-css-prefix}-chart-line:before { content: @fa-var-chart-line; }
.@{fa-css-prefix}-chart-pie:before { content: @fa-var-chart-pie; }
.@{fa-css-prefix}-check:before { content: @fa-var-check; }
.@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; }
.@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; }
.@{fa-css-prefix}-chess:before { content: @fa-var-chess; }
.@{fa-css-prefix}-chess-bishop:before { content: @fa-var-chess-bishop; }
.@{fa-css-prefix}-chess-board:before { content: @fa-var-chess-board; }
.@{fa-css-prefix}-chess-king:before { content: @fa-var-chess-king; }
.@{fa-css-prefix}-chess-knight:before { content: @fa-var-chess-knight; }
.@{fa-css-prefix}-chess-pawn:before { content: @fa-var-chess-pawn; }
.@{fa-css-prefix}-chess-queen:before { content: @fa-var-chess-queen; }
.@{fa-css-prefix}-chess-rook:before { content: @fa-var-chess-rook; }
.@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; }
.@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; }
.@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; }
.@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; }
.@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; }
.@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; }
.@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; }
.@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; }
.@{fa-css-prefix}-child:before { content: @fa-var-child; }
.@{fa-css-prefix}-chrome:before { content: @fa-var-chrome; }
.@{fa-css-prefix}-church:before { content: @fa-var-church; }
.@{fa-css-prefix}-circle:before { content: @fa-var-circle; }
.@{fa-css-prefix}-circle-notch:before { content: @fa-var-circle-notch; }
.@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; }
.@{fa-css-prefix}-clipboard-check:before { content: @fa-var-clipboard-check; }
.@{fa-css-prefix}-clipboard-list:before { content: @fa-var-clipboard-list; }
.@{fa-css-prefix}-clock:before { content: @fa-var-clock; }
.@{fa-css-prefix}-clone:before { content: @fa-var-clone; }
.@{fa-css-prefix}-closed-captioning:before { content: @fa-var-closed-captioning; }
.@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; }
.@{fa-css-prefix}-cloud-download-alt:before { content: @fa-var-cloud-download-alt; }
.@{fa-css-prefix}-cloud-upload-alt:before { content: @fa-var-cloud-upload-alt; }
.@{fa-css-prefix}-cloudscale:before { content: @fa-var-cloudscale; }
.@{fa-css-prefix}-cloudsmith:before { content: @fa-var-cloudsmith; }
.@{fa-css-prefix}-cloudversify:before { content: @fa-var-cloudversify; }
.@{fa-css-prefix}-code:before { content: @fa-var-code; }
.@{fa-css-prefix}-code-branch:before { content: @fa-var-code-branch; }
.@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; }
.@{fa-css-prefix}-codiepie:before { content: @fa-var-codiepie; }
.@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; }
.@{fa-css-prefix}-cog:before { content: @fa-var-cog; }
.@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; }
.@{fa-css-prefix}-coins:before { content: @fa-var-coins; }
.@{fa-css-prefix}-columns:before { content: @fa-var-columns; }
.@{fa-css-prefix}-comment:before { content: @fa-var-comment; }
.@{fa-css-prefix}-comment-alt:before { content: @fa-var-comment-alt; }
.@{fa-css-prefix}-comment-dots:before { content: @fa-var-comment-dots; }
.@{fa-css-prefix}-comment-slash:before { content: @fa-var-comment-slash; }
.@{fa-css-prefix}-comments:before { content: @fa-var-comments; }
.@{fa-css-prefix}-compact-disc:before { content: @fa-var-compact-disc; }
.@{fa-css-prefix}-compass:before { content: @fa-var-compass; }
.@{fa-css-prefix}-compress:before { content: @fa-var-compress; }
.@{fa-css-prefix}-connectdevelop:before { content: @fa-var-connectdevelop; }
.@{fa-css-prefix}-contao:before { content: @fa-var-contao; }
.@{fa-css-prefix}-copy:before { content: @fa-var-copy; }
.@{fa-css-prefix}-copyright:before { content: @fa-var-copyright; }
.@{fa-css-prefix}-couch:before { content: @fa-var-couch; }
.@{fa-css-prefix}-cpanel:before { content: @fa-var-cpanel; }
.@{fa-css-prefix}-creative-commons:before { content: @fa-var-creative-commons; }
.@{fa-css-prefix}-creative-commons-by:before { content: @fa-var-creative-commons-by; }
.@{fa-css-prefix}-creative-commons-nc:before { content: @fa-var-creative-commons-nc; }
.@{fa-css-prefix}-creative-commons-nc-eu:before { content: @fa-var-creative-commons-nc-eu; }
.@{fa-css-prefix}-creative-commons-nc-jp:before { content: @fa-var-creative-commons-nc-jp; }
.@{fa-css-prefix}-creative-commons-nd:before { content: @fa-var-creative-commons-nd; }
.@{fa-css-prefix}-creative-commons-pd:before { content: @fa-var-creative-commons-pd; }
.@{fa-css-prefix}-creative-commons-pd-alt:before { content: @fa-var-creative-commons-pd-alt; }
.@{fa-css-prefix}-creative-commons-remix:before { content: @fa-var-creative-commons-remix; }
.@{fa-css-prefix}-creative-commons-sa:before { content: @fa-var-creative-commons-sa; }
.@{fa-css-prefix}-creative-commons-sampling:before { content: @fa-var-creative-commons-sampling; }
.@{fa-css-prefix}-creative-commons-sampling-plus:before { content: @fa-var-creative-commons-sampling-plus; }
.@{fa-css-prefix}-creative-commons-share:before { content: @fa-var-creative-commons-share; }
.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; }
.@{fa-css-prefix}-crop:before { content: @fa-var-crop; }
.@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; }
.@{fa-css-prefix}-crow:before { content: @fa-var-crow; }
.@{fa-css-prefix}-crown:before { content: @fa-var-crown; }
.@{fa-css-prefix}-css3:before { content: @fa-var-css3; }
.@{fa-css-prefix}-css3-alt:before { content: @fa-var-css3-alt; }
.@{fa-css-prefix}-cube:before { content: @fa-var-cube; }
.@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; }
.@{fa-css-prefix}-cut:before { content: @fa-var-cut; }
.@{fa-css-prefix}-cuttlefish:before { content: @fa-var-cuttlefish; }
.@{fa-css-prefix}-d-and-d:before { content: @fa-var-d-and-d; }
.@{fa-css-prefix}-dashcube:before { content: @fa-var-dashcube; }
.@{fa-css-prefix}-database:before { content: @fa-var-database; }
.@{fa-css-prefix}-deaf:before { content: @fa-var-deaf; }
.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; }
.@{fa-css-prefix}-deploydog:before { content: @fa-var-deploydog; }
.@{fa-css-prefix}-deskpro:before { content: @fa-var-deskpro; }
.@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; }
.@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; }
.@{fa-css-prefix}-diagnoses:before { content: @fa-var-diagnoses; }
.@{fa-css-prefix}-dice:before { content: @fa-var-dice; }
.@{fa-css-prefix}-dice-five:before { content: @fa-var-dice-five; }
.@{fa-css-prefix}-dice-four:before { content: @fa-var-dice-four; }
.@{fa-css-prefix}-dice-one:before { content: @fa-var-dice-one; }
.@{fa-css-prefix}-dice-six:before { content: @fa-var-dice-six; }
.@{fa-css-prefix}-dice-three:before { content: @fa-var-dice-three; }
.@{fa-css-prefix}-dice-two:before { content: @fa-var-dice-two; }
.@{fa-css-prefix}-digg:before { content: @fa-var-digg; }
.@{fa-css-prefix}-digital-ocean:before { content: @fa-var-digital-ocean; }
.@{fa-css-prefix}-discord:before { content: @fa-var-discord; }
.@{fa-css-prefix}-discourse:before { content: @fa-var-discourse; }
.@{fa-css-prefix}-divide:before { content: @fa-var-divide; }
.@{fa-css-prefix}-dna:before { content: @fa-var-dna; }
.@{fa-css-prefix}-dochub:before { content: @fa-var-dochub; }
.@{fa-css-prefix}-docker:before { content: @fa-var-docker; }
.@{fa-css-prefix}-dollar-sign:before { content: @fa-var-dollar-sign; }
.@{fa-css-prefix}-dolly:before { content: @fa-var-dolly; }
.@{fa-css-prefix}-dolly-flatbed:before { content: @fa-var-dolly-flatbed; }
.@{fa-css-prefix}-donate:before { content: @fa-var-donate; }
.@{fa-css-prefix}-door-closed:before { content: @fa-var-door-closed; }
.@{fa-css-prefix}-door-open:before { content: @fa-var-door-open; }
.@{fa-css-prefix}-dot-circle:before { content: @fa-var-dot-circle; }
.@{fa-css-prefix}-dove:before { content: @fa-var-dove; }
.@{fa-css-prefix}-download:before { content: @fa-var-download; }
.@{fa-css-prefix}-draft2digital:before { content: @fa-var-draft2digital; }
.@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; }
.@{fa-css-prefix}-dribbble-square:before { content: @fa-var-dribbble-square; }
.@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; }
.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; }
.@{fa-css-prefix}-dumbbell:before { content: @fa-var-dumbbell; }
.@{fa-css-prefix}-dyalog:before { content: @fa-var-dyalog; }
.@{fa-css-prefix}-earlybirds:before { content: @fa-var-earlybirds; }
.@{fa-css-prefix}-ebay:before { content: @fa-var-ebay; }
.@{fa-css-prefix}-edge:before { content: @fa-var-edge; }
.@{fa-css-prefix}-edit:before { content: @fa-var-edit; }
.@{fa-css-prefix}-eject:before { content: @fa-var-eject; }
.@{fa-css-prefix}-elementor:before { content: @fa-var-elementor; }
.@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; }
.@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; }
.@{fa-css-prefix}-ember:before { content: @fa-var-ember; }
.@{fa-css-prefix}-empire:before { content: @fa-var-empire; }
.@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; }
.@{fa-css-prefix}-envelope-open:before { content: @fa-var-envelope-open; }
.@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; }
.@{fa-css-prefix}-envira:before { content: @fa-var-envira; }
.@{fa-css-prefix}-equals:before { content: @fa-var-equals; }
.@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; }
.@{fa-css-prefix}-erlang:before { content: @fa-var-erlang; }
.@{fa-css-prefix}-ethereum:before { content: @fa-var-ethereum; }
.@{fa-css-prefix}-etsy:before { content: @fa-var-etsy; }
.@{fa-css-prefix}-euro-sign:before { content: @fa-var-euro-sign; }
.@{fa-css-prefix}-exchange-alt:before { content: @fa-var-exchange-alt; }
.@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; }
.@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; }
.@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; }
.@{fa-css-prefix}-expand:before { content: @fa-var-expand; }
.@{fa-css-prefix}-expand-arrows-alt:before { content: @fa-var-expand-arrows-alt; }
.@{fa-css-prefix}-expeditedssl:before { content: @fa-var-expeditedssl; }
.@{fa-css-prefix}-external-link-alt:before { content: @fa-var-external-link-alt; }
.@{fa-css-prefix}-external-link-square-alt:before { content: @fa-var-external-link-square-alt; }
.@{fa-css-prefix}-eye:before { content: @fa-var-eye; }
.@{fa-css-prefix}-eye-dropper:before { content: @fa-var-eye-dropper; }
.@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; }
.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; }
.@{fa-css-prefix}-facebook-f:before { content: @fa-var-facebook-f; }
.@{fa-css-prefix}-facebook-messenger:before { content: @fa-var-facebook-messenger; }
.@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; }
.@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; }
.@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; }
.@{fa-css-prefix}-fax:before { content: @fa-var-fax; }
.@{fa-css-prefix}-feather:before { content: @fa-var-feather; }
.@{fa-css-prefix}-female:before { content: @fa-var-female; }
.@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; }
.@{fa-css-prefix}-file:before { content: @fa-var-file; }
.@{fa-css-prefix}-file-alt:before { content: @fa-var-file-alt; }
.@{fa-css-prefix}-file-archive:before { content: @fa-var-file-archive; }
.@{fa-css-prefix}-file-audio:before { content: @fa-var-file-audio; }
.@{fa-css-prefix}-file-code:before { content: @fa-var-file-code; }
.@{fa-css-prefix}-file-excel:before { content: @fa-var-file-excel; }
.@{fa-css-prefix}-file-image:before { content: @fa-var-file-image; }
.@{fa-css-prefix}-file-medical:before { content: @fa-var-file-medical; }
.@{fa-css-prefix}-file-medical-alt:before { content: @fa-var-file-medical-alt; }
.@{fa-css-prefix}-file-pdf:before { content: @fa-var-file-pdf; }
.@{fa-css-prefix}-file-powerpoint:before { content: @fa-var-file-powerpoint; }
.@{fa-css-prefix}-file-video:before { content: @fa-var-file-video; }
.@{fa-css-prefix}-file-word:before { content: @fa-var-file-word; }
.@{fa-css-prefix}-film:before { content: @fa-var-film; }
.@{fa-css-prefix}-filter:before { content: @fa-var-filter; }
.@{fa-css-prefix}-fire:before { content: @fa-var-fire; }
.@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; }
.@{fa-css-prefix}-firefox:before { content: @fa-var-firefox; }
.@{fa-css-prefix}-first-aid:before { content: @fa-var-first-aid; }
.@{fa-css-prefix}-first-order:before { content: @fa-var-first-order; }
.@{fa-css-prefix}-first-order-alt:before { content: @fa-var-first-order-alt; }
.@{fa-css-prefix}-firstdraft:before { content: @fa-var-firstdraft; }
.@{fa-css-prefix}-flag:before { content: @fa-var-flag; }
.@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; }
.@{fa-css-prefix}-flask:before { content: @fa-var-flask; }
.@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; }
.@{fa-css-prefix}-flipboard:before { content: @fa-var-flipboard; }
.@{fa-css-prefix}-fly:before { content: @fa-var-fly; }
.@{fa-css-prefix}-folder:before { content: @fa-var-folder; }
.@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; }
.@{fa-css-prefix}-font:before { content: @fa-var-font; }
.@{fa-css-prefix}-font-awesome:before { content: @fa-var-font-awesome; }
.@{fa-css-prefix}-font-awesome-alt:before { content: @fa-var-font-awesome-alt; }
.@{fa-css-prefix}-font-awesome-flag:before { content: @fa-var-font-awesome-flag; }
.@{fa-css-prefix}-font-awesome-logo-full:before { content: @fa-var-font-awesome-logo-full; }
.@{fa-css-prefix}-fonticons:before { content: @fa-var-fonticons; }
.@{fa-css-prefix}-fonticons-fi:before { content: @fa-var-fonticons-fi; }
.@{fa-css-prefix}-football-ball:before { content: @fa-var-football-ball; }
.@{fa-css-prefix}-fort-awesome:before { content: @fa-var-fort-awesome; }
.@{fa-css-prefix}-fort-awesome-alt:before { content: @fa-var-fort-awesome-alt; }
.@{fa-css-prefix}-forumbee:before { content: @fa-var-forumbee; }
.@{fa-css-prefix}-forward:before { content: @fa-var-forward; }
.@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; }
.@{fa-css-prefix}-free-code-camp:before { content: @fa-var-free-code-camp; }
.@{fa-css-prefix}-freebsd:before { content: @fa-var-freebsd; }
.@{fa-css-prefix}-frog:before { content: @fa-var-frog; }
.@{fa-css-prefix}-frown:before { content: @fa-var-frown; }
.@{fa-css-prefix}-fulcrum:before { content: @fa-var-fulcrum; }
.@{fa-css-prefix}-futbol:before { content: @fa-var-futbol; }
.@{fa-css-prefix}-galactic-republic:before { content: @fa-var-galactic-republic; }
.@{fa-css-prefix}-galactic-senate:before { content: @fa-var-galactic-senate; }
.@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; }
.@{fa-css-prefix}-gas-pump:before { content: @fa-var-gas-pump; }
.@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; }
.@{fa-css-prefix}-gem:before { content: @fa-var-gem; }
.@{fa-css-prefix}-genderless:before { content: @fa-var-genderless; }
.@{fa-css-prefix}-get-pocket:before { content: @fa-var-get-pocket; }
.@{fa-css-prefix}-gg:before { content: @fa-var-gg; }
.@{fa-css-prefix}-gg-circle:before { content: @fa-var-gg-circle; }
.@{fa-css-prefix}-gift:before { content: @fa-var-gift; }
.@{fa-css-prefix}-git:before { content: @fa-var-git; }
.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; }
.@{fa-css-prefix}-github:before { content: @fa-var-github; }
.@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; }
.@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; }
.@{fa-css-prefix}-gitkraken:before { content: @fa-var-gitkraken; }
.@{fa-css-prefix}-gitlab:before { content: @fa-var-gitlab; }
.@{fa-css-prefix}-gitter:before { content: @fa-var-gitter; }
.@{fa-css-prefix}-glass-martini:before { content: @fa-var-glass-martini; }
.@{fa-css-prefix}-glasses:before { content: @fa-var-glasses; }
.@{fa-css-prefix}-glide:before { content: @fa-var-glide; }
.@{fa-css-prefix}-glide-g:before { content: @fa-var-glide-g; }
.@{fa-css-prefix}-globe:before { content: @fa-var-globe; }
.@{fa-css-prefix}-gofore:before { content: @fa-var-gofore; }
.@{fa-css-prefix}-golf-ball:before { content: @fa-var-golf-ball; }
.@{fa-css-prefix}-goodreads:before { content: @fa-var-goodreads; }
.@{fa-css-prefix}-goodreads-g:before { content: @fa-var-goodreads-g; }
.@{fa-css-prefix}-google:before { content: @fa-var-google; }
.@{fa-css-prefix}-google-drive:before { content: @fa-var-google-drive; }
.@{fa-css-prefix}-google-play:before { content: @fa-var-google-play; }
.@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; }
.@{fa-css-prefix}-google-plus-g:before { content: @fa-var-google-plus-g; }
.@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; }
.@{fa-css-prefix}-google-wallet:before { content: @fa-var-google-wallet; }
.@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; }
.@{fa-css-prefix}-gratipay:before { content: @fa-var-gratipay; }
.@{fa-css-prefix}-grav:before { content: @fa-var-grav; }
.@{fa-css-prefix}-greater-than:before { content: @fa-var-greater-than; }
.@{fa-css-prefix}-greater-than-equal:before { content: @fa-var-greater-than-equal; }
.@{fa-css-prefix}-gripfire:before { content: @fa-var-gripfire; }
.@{fa-css-prefix}-grunt:before { content: @fa-var-grunt; }
.@{fa-css-prefix}-gulp:before { content: @fa-var-gulp; }
.@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; }
.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; }
.@{fa-css-prefix}-hacker-news-square:before { content: @fa-var-hacker-news-square; }
.@{fa-css-prefix}-hand-holding:before { content: @fa-var-hand-holding; }
.@{fa-css-prefix}-hand-holding-heart:before { content: @fa-var-hand-holding-heart; }
.@{fa-css-prefix}-hand-holding-usd:before { content: @fa-var-hand-holding-usd; }
.@{fa-css-prefix}-hand-lizard:before { content: @fa-var-hand-lizard; }
.@{fa-css-prefix}-hand-paper:before { content: @fa-var-hand-paper; }
.@{fa-css-prefix}-hand-peace:before { content: @fa-var-hand-peace; }
.@{fa-css-prefix}-hand-point-down:before { content: @fa-var-hand-point-down; }
.@{fa-css-prefix}-hand-point-left:before { content: @fa-var-hand-point-left; }
.@{fa-css-prefix}-hand-point-right:before { content: @fa-var-hand-point-right; }
.@{fa-css-prefix}-hand-point-up:before { content: @fa-var-hand-point-up; }
.@{fa-css-prefix}-hand-pointer:before { content: @fa-var-hand-pointer; }
.@{fa-css-prefix}-hand-rock:before { content: @fa-var-hand-rock; }
.@{fa-css-prefix}-hand-scissors:before { content: @fa-var-hand-scissors; }
.@{fa-css-prefix}-hand-spock:before { content: @fa-var-hand-spock; }
.@{fa-css-prefix}-hands:before { content: @fa-var-hands; }
.@{fa-css-prefix}-hands-helping:before { content: @fa-var-hands-helping; }
.@{fa-css-prefix}-handshake:before { content: @fa-var-handshake; }
.@{fa-css-prefix}-hashtag:before { content: @fa-var-hashtag; }
.@{fa-css-prefix}-hdd:before { content: @fa-var-hdd; }
.@{fa-css-prefix}-heading:before { content: @fa-var-heading; }
.@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; }
.@{fa-css-prefix}-heart:before { content: @fa-var-heart; }
.@{fa-css-prefix}-heartbeat:before { content: @fa-var-heartbeat; }
.@{fa-css-prefix}-helicopter:before { content: @fa-var-helicopter; }
.@{fa-css-prefix}-hips:before { content: @fa-var-hips; }
.@{fa-css-prefix}-hire-a-helper:before { content: @fa-var-hire-a-helper; }
.@{fa-css-prefix}-history:before { content: @fa-var-history; }
.@{fa-css-prefix}-hockey-puck:before { content: @fa-var-hockey-puck; }
.@{fa-css-prefix}-home:before { content: @fa-var-home; }
.@{fa-css-prefix}-hooli:before { content: @fa-var-hooli; }
.@{fa-css-prefix}-hospital:before { content: @fa-var-hospital; }
.@{fa-css-prefix}-hospital-alt:before { content: @fa-var-hospital-alt; }
.@{fa-css-prefix}-hospital-symbol:before { content: @fa-var-hospital-symbol; }
.@{fa-css-prefix}-hotjar:before { content: @fa-var-hotjar; }
.@{fa-css-prefix}-hourglass:before { content: @fa-var-hourglass; }
.@{fa-css-prefix}-hourglass-end:before { content: @fa-var-hourglass-end; }
.@{fa-css-prefix}-hourglass-half:before { content: @fa-var-hourglass-half; }
.@{fa-css-prefix}-hourglass-start:before { content: @fa-var-hourglass-start; }
.@{fa-css-prefix}-houzz:before { content: @fa-var-houzz; }
.@{fa-css-prefix}-html5:before { content: @fa-var-html5; }
.@{fa-css-prefix}-hubspot:before { content: @fa-var-hubspot; }
.@{fa-css-prefix}-i-cursor:before { content: @fa-var-i-cursor; }
.@{fa-css-prefix}-id-badge:before { content: @fa-var-id-badge; }
.@{fa-css-prefix}-id-card:before { content: @fa-var-id-card; }
.@{fa-css-prefix}-id-card-alt:before { content: @fa-var-id-card-alt; }
.@{fa-css-prefix}-image:before { content: @fa-var-image; }
.@{fa-css-prefix}-images:before { content: @fa-var-images; }
.@{fa-css-prefix}-imdb:before { content: @fa-var-imdb; }
.@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; }
.@{fa-css-prefix}-indent:before { content: @fa-var-indent; }
.@{fa-css-prefix}-industry:before { content: @fa-var-industry; }
.@{fa-css-prefix}-infinity:before { content: @fa-var-infinity; }
.@{fa-css-prefix}-info:before { content: @fa-var-info; }
.@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; }
.@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; }
.@{fa-css-prefix}-internet-explorer:before { content: @fa-var-internet-explorer; }
.@{fa-css-prefix}-ioxhost:before { content: @fa-var-ioxhost; }
.@{fa-css-prefix}-italic:before { content: @fa-var-italic; }
.@{fa-css-prefix}-itunes:before { content: @fa-var-itunes; }
.@{fa-css-prefix}-itunes-note:before { content: @fa-var-itunes-note; }
.@{fa-css-prefix}-java:before { content: @fa-var-java; }
.@{fa-css-prefix}-jedi-order:before { content: @fa-var-jedi-order; }
.@{fa-css-prefix}-jenkins:before { content: @fa-var-jenkins; }
.@{fa-css-prefix}-joget:before { content: @fa-var-joget; }
.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; }
.@{fa-css-prefix}-js:before { content: @fa-var-js; }
.@{fa-css-prefix}-js-square:before { content: @fa-var-js-square; }
.@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; }
.@{fa-css-prefix}-key:before { content: @fa-var-key; }
.@{fa-css-prefix}-keybase:before { content: @fa-var-keybase; }
.@{fa-css-prefix}-keyboard:before { content: @fa-var-keyboard; }
.@{fa-css-prefix}-keycdn:before { content: @fa-var-keycdn; }
.@{fa-css-prefix}-kickstarter:before { content: @fa-var-kickstarter; }
.@{fa-css-prefix}-kickstarter-k:before { content: @fa-var-kickstarter-k; }
.@{fa-css-prefix}-kiwi-bird:before { content: @fa-var-kiwi-bird; }
.@{fa-css-prefix}-korvue:before { content: @fa-var-korvue; }
.@{fa-css-prefix}-language:before { content: @fa-var-language; }
.@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; }
.@{fa-css-prefix}-laravel:before { content: @fa-var-laravel; }
.@{fa-css-prefix}-lastfm:before { content: @fa-var-lastfm; }
.@{fa-css-prefix}-lastfm-square:before { content: @fa-var-lastfm-square; }
.@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; }
.@{fa-css-prefix}-leanpub:before { content: @fa-var-leanpub; }
.@{fa-css-prefix}-lemon:before { content: @fa-var-lemon; }
.@{fa-css-prefix}-less:before { content: @fa-var-less; }
.@{fa-css-prefix}-less-than:before { content: @fa-var-less-than; }
.@{fa-css-prefix}-less-than-equal:before { content: @fa-var-less-than-equal; }
.@{fa-css-prefix}-level-down-alt:before { content: @fa-var-level-down-alt; }
.@{fa-css-prefix}-level-up-alt:before { content: @fa-var-level-up-alt; }
.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; }
.@{fa-css-prefix}-lightbulb:before { content: @fa-var-lightbulb; }
.@{fa-css-prefix}-line:before { content: @fa-var-line; }
.@{fa-css-prefix}-link:before { content: @fa-var-link; }
.@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; }
.@{fa-css-prefix}-linkedin-in:before { content: @fa-var-linkedin-in; }
.@{fa-css-prefix}-linode:before { content: @fa-var-linode; }
.@{fa-css-prefix}-linux:before { content: @fa-var-linux; }
.@{fa-css-prefix}-lira-sign:before { content: @fa-var-lira-sign; }
.@{fa-css-prefix}-list:before { content: @fa-var-list; }
.@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; }
.@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; }
.@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; }
.@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; }
.@{fa-css-prefix}-lock:before { content: @fa-var-lock; }
.@{fa-css-prefix}-lock-open:before { content: @fa-var-lock-open; }
.@{fa-css-prefix}-long-arrow-alt-down:before { content: @fa-var-long-arrow-alt-down; }
.@{fa-css-prefix}-long-arrow-alt-left:before { content: @fa-var-long-arrow-alt-left; }
.@{fa-css-prefix}-long-arrow-alt-right:before { content: @fa-var-long-arrow-alt-right; }
.@{fa-css-prefix}-long-arrow-alt-up:before { content: @fa-var-long-arrow-alt-up; }
.@{fa-css-prefix}-low-vision:before { content: @fa-var-low-vision; }
.@{fa-css-prefix}-lyft:before { content: @fa-var-lyft; }
.@{fa-css-prefix}-magento:before { content: @fa-var-magento; }
.@{fa-css-prefix}-magic:before { content: @fa-var-magic; }
.@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; }
.@{fa-css-prefix}-male:before { content: @fa-var-male; }
.@{fa-css-prefix}-mandalorian:before { content: @fa-var-mandalorian; }
.@{fa-css-prefix}-map:before { content: @fa-var-map; }
.@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; }
.@{fa-css-prefix}-map-marker-alt:before { content: @fa-var-map-marker-alt; }
.@{fa-css-prefix}-map-pin:before { content: @fa-var-map-pin; }
.@{fa-css-prefix}-map-signs:before { content: @fa-var-map-signs; }
.@{fa-css-prefix}-mars:before { content: @fa-var-mars; }
.@{fa-css-prefix}-mars-double:before { content: @fa-var-mars-double; }
.@{fa-css-prefix}-mars-stroke:before { content: @fa-var-mars-stroke; }
.@{fa-css-prefix}-mars-stroke-h:before { content: @fa-var-mars-stroke-h; }
.@{fa-css-prefix}-mars-stroke-v:before { content: @fa-var-mars-stroke-v; }
.@{fa-css-prefix}-mastodon:before { content: @fa-var-mastodon; }
.@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; }
.@{fa-css-prefix}-medapps:before { content: @fa-var-medapps; }
.@{fa-css-prefix}-medium:before { content: @fa-var-medium; }
.@{fa-css-prefix}-medium-m:before { content: @fa-var-medium-m; }
.@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; }
.@{fa-css-prefix}-medrt:before { content: @fa-var-medrt; }
.@{fa-css-prefix}-meetup:before { content: @fa-var-meetup; }
.@{fa-css-prefix}-meh:before { content: @fa-var-meh; }
.@{fa-css-prefix}-memory:before { content: @fa-var-memory; }
.@{fa-css-prefix}-mercury:before { content: @fa-var-mercury; }
.@{fa-css-prefix}-microchip:before { content: @fa-var-microchip; }
.@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; }
.@{fa-css-prefix}-microphone-alt:before { content: @fa-var-microphone-alt; }
.@{fa-css-prefix}-microphone-alt-slash:before { content: @fa-var-microphone-alt-slash; }
.@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; }
.@{fa-css-prefix}-microsoft:before { content: @fa-var-microsoft; }
.@{fa-css-prefix}-minus:before { content: @fa-var-minus; }
.@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; }
.@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; }
.@{fa-css-prefix}-mix:before { content: @fa-var-mix; }
.@{fa-css-prefix}-mixcloud:before { content: @fa-var-mixcloud; }
.@{fa-css-prefix}-mizuni:before { content: @fa-var-mizuni; }
.@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; }
.@{fa-css-prefix}-mobile-alt:before { content: @fa-var-mobile-alt; }
.@{fa-css-prefix}-modx:before { content: @fa-var-modx; }
.@{fa-css-prefix}-monero:before { content: @fa-var-monero; }
.@{fa-css-prefix}-money-bill:before { content: @fa-var-money-bill; }
.@{fa-css-prefix}-money-bill-alt:before { content: @fa-var-money-bill-alt; }
.@{fa-css-prefix}-money-bill-wave:before { content: @fa-var-money-bill-wave; }
.@{fa-css-prefix}-money-bill-wave-alt:before { content: @fa-var-money-bill-wave-alt; }
.@{fa-css-prefix}-money-check:before { content: @fa-var-money-check; }
.@{fa-css-prefix}-money-check-alt:before { content: @fa-var-money-check-alt; }
.@{fa-css-prefix}-moon:before { content: @fa-var-moon; }
.@{fa-css-prefix}-motorcycle:before { content: @fa-var-motorcycle; }
.@{fa-css-prefix}-mouse-pointer:before { content: @fa-var-mouse-pointer; }
.@{fa-css-prefix}-music:before { content: @fa-var-music; }
.@{fa-css-prefix}-napster:before { content: @fa-var-napster; }
.@{fa-css-prefix}-neuter:before { content: @fa-var-neuter; }
.@{fa-css-prefix}-newspaper:before { content: @fa-var-newspaper; }
.@{fa-css-prefix}-nintendo-switch:before { content: @fa-var-nintendo-switch; }
.@{fa-css-prefix}-node:before { content: @fa-var-node; }
.@{fa-css-prefix}-node-js:before { content: @fa-var-node-js; }
.@{fa-css-prefix}-not-equal:before { content: @fa-var-not-equal; }
.@{fa-css-prefix}-notes-medical:before { content: @fa-var-notes-medical; }
.@{fa-css-prefix}-npm:before { content: @fa-var-npm; }
.@{fa-css-prefix}-ns8:before { content: @fa-var-ns8; }
.@{fa-css-prefix}-nutritionix:before { content: @fa-var-nutritionix; }
.@{fa-css-prefix}-object-group:before { content: @fa-var-object-group; }
.@{fa-css-prefix}-object-ungroup:before { content: @fa-var-object-ungroup; }
.@{fa-css-prefix}-odnoklassniki:before { content: @fa-var-odnoklassniki; }
.@{fa-css-prefix}-odnoklassniki-square:before { content: @fa-var-odnoklassniki-square; }
.@{fa-css-prefix}-old-republic:before { content: @fa-var-old-republic; }
.@{fa-css-prefix}-opencart:before { content: @fa-var-opencart; }
.@{fa-css-prefix}-openid:before { content: @fa-var-openid; }
.@{fa-css-prefix}-opera:before { content: @fa-var-opera; }
.@{fa-css-prefix}-optin-monster:before { content: @fa-var-optin-monster; }
.@{fa-css-prefix}-osi:before { content: @fa-var-osi; }
.@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; }
.@{fa-css-prefix}-page4:before { content: @fa-var-page4; }
.@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; }
.@{fa-css-prefix}-paint-brush:before { content: @fa-var-paint-brush; }
.@{fa-css-prefix}-palette:before { content: @fa-var-palette; }
.@{fa-css-prefix}-palfed:before { content: @fa-var-palfed; }
.@{fa-css-prefix}-pallet:before { content: @fa-var-pallet; }
.@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; }
.@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; }
.@{fa-css-prefix}-parachute-box:before { content: @fa-var-parachute-box; }
.@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; }
.@{fa-css-prefix}-parking:before { content: @fa-var-parking; }
.@{fa-css-prefix}-paste:before { content: @fa-var-paste; }
.@{fa-css-prefix}-patreon:before { content: @fa-var-patreon; }
.@{fa-css-prefix}-pause:before { content: @fa-var-pause; }
.@{fa-css-prefix}-pause-circle:before { content: @fa-var-pause-circle; }
.@{fa-css-prefix}-paw:before { content: @fa-var-paw; }
.@{fa-css-prefix}-paypal:before { content: @fa-var-paypal; }
.@{fa-css-prefix}-pen-square:before { content: @fa-var-pen-square; }
.@{fa-css-prefix}-pencil-alt:before { content: @fa-var-pencil-alt; }
.@{fa-css-prefix}-people-carry:before { content: @fa-var-people-carry; }
.@{fa-css-prefix}-percent:before { content: @fa-var-percent; }
.@{fa-css-prefix}-percentage:before { content: @fa-var-percentage; }
.@{fa-css-prefix}-periscope:before { content: @fa-var-periscope; }
.@{fa-css-prefix}-phabricator:before { content: @fa-var-phabricator; }
.@{fa-css-prefix}-phoenix-framework:before { content: @fa-var-phoenix-framework; }
.@{fa-css-prefix}-phoenix-squadron:before { content: @fa-var-phoenix-squadron; }
.@{fa-css-prefix}-phone:before { content: @fa-var-phone; }
.@{fa-css-prefix}-phone-slash:before { content: @fa-var-phone-slash; }
.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; }
.@{fa-css-prefix}-phone-volume:before { content: @fa-var-phone-volume; }
.@{fa-css-prefix}-php:before { content: @fa-var-php; }
.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; }
.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; }
.@{fa-css-prefix}-pied-piper-hat:before { content: @fa-var-pied-piper-hat; }
.@{fa-css-prefix}-pied-piper-pp:before { content: @fa-var-pied-piper-pp; }
.@{fa-css-prefix}-piggy-bank:before { content: @fa-var-piggy-bank; }
.@{fa-css-prefix}-pills:before { content: @fa-var-pills; }
.@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; }
.@{fa-css-prefix}-pinterest-p:before { content: @fa-var-pinterest-p; }
.@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; }
.@{fa-css-prefix}-plane:before { content: @fa-var-plane; }
.@{fa-css-prefix}-play:before { content: @fa-var-play; }
.@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; }
.@{fa-css-prefix}-playstation:before { content: @fa-var-playstation; }
.@{fa-css-prefix}-plug:before { content: @fa-var-plug; }
.@{fa-css-prefix}-plus:before { content: @fa-var-plus; }
.@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; }
.@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; }
.@{fa-css-prefix}-podcast:before { content: @fa-var-podcast; }
.@{fa-css-prefix}-poo:before { content: @fa-var-poo; }
.@{fa-css-prefix}-portrait:before { content: @fa-var-portrait; }
.@{fa-css-prefix}-pound-sign:before { content: @fa-var-pound-sign; }
.@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; }
.@{fa-css-prefix}-prescription-bottle:before { content: @fa-var-prescription-bottle; }
.@{fa-css-prefix}-prescription-bottle-alt:before { content: @fa-var-prescription-bottle-alt; }
.@{fa-css-prefix}-print:before { content: @fa-var-print; }
.@{fa-css-prefix}-procedures:before { content: @fa-var-procedures; }
.@{fa-css-prefix}-product-hunt:before { content: @fa-var-product-hunt; }
.@{fa-css-prefix}-project-diagram:before { content: @fa-var-project-diagram; }
.@{fa-css-prefix}-pushed:before { content: @fa-var-pushed; }
.@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; }
.@{fa-css-prefix}-python:before { content: @fa-var-python; }
.@{fa-css-prefix}-qq:before { content: @fa-var-qq; }
.@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; }
.@{fa-css-prefix}-question:before { content: @fa-var-question; }
.@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; }
.@{fa-css-prefix}-quidditch:before { content: @fa-var-quidditch; }
.@{fa-css-prefix}-quinscape:before { content: @fa-var-quinscape; }
.@{fa-css-prefix}-quora:before { content: @fa-var-quora; }
.@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; }
.@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; }
.@{fa-css-prefix}-r-project:before { content: @fa-var-r-project; }
.@{fa-css-prefix}-random:before { content: @fa-var-random; }
.@{fa-css-prefix}-ravelry:before { content: @fa-var-ravelry; }
.@{fa-css-prefix}-react:before { content: @fa-var-react; }
.@{fa-css-prefix}-readme:before { content: @fa-var-readme; }
.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; }
.@{fa-css-prefix}-receipt:before { content: @fa-var-receipt; }
.@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; }
.@{fa-css-prefix}-red-river:before { content: @fa-var-red-river; }
.@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; }
.@{fa-css-prefix}-reddit-alien:before { content: @fa-var-reddit-alien; }
.@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; }
.@{fa-css-prefix}-redo:before { content: @fa-var-redo; }
.@{fa-css-prefix}-redo-alt:before { content: @fa-var-redo-alt; }
.@{fa-css-prefix}-registered:before { content: @fa-var-registered; }
.@{fa-css-prefix}-rendact:before { content: @fa-var-rendact; }
.@{fa-css-prefix}-renren:before { content: @fa-var-renren; }
.@{fa-css-prefix}-reply:before { content: @fa-var-reply; }
.@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; }
.@{fa-css-prefix}-replyd:before { content: @fa-var-replyd; }
.@{fa-css-prefix}-researchgate:before { content: @fa-var-researchgate; }
.@{fa-css-prefix}-resolving:before { content: @fa-var-resolving; }
.@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; }
.@{fa-css-prefix}-ribbon:before { content: @fa-var-ribbon; }
.@{fa-css-prefix}-road:before { content: @fa-var-road; }
.@{fa-css-prefix}-robot:before { content: @fa-var-robot; }
.@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; }
.@{fa-css-prefix}-rocketchat:before { content: @fa-var-rocketchat; }
.@{fa-css-prefix}-rockrms:before { content: @fa-var-rockrms; }
.@{fa-css-prefix}-rss:before { content: @fa-var-rss; }
.@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; }
.@{fa-css-prefix}-ruble-sign:before { content: @fa-var-ruble-sign; }
.@{fa-css-prefix}-ruler:before { content: @fa-var-ruler; }
.@{fa-css-prefix}-ruler-combined:before { content: @fa-var-ruler-combined; }
.@{fa-css-prefix}-ruler-horizontal:before { content: @fa-var-ruler-horizontal; }
.@{fa-css-prefix}-ruler-vertical:before { content: @fa-var-ruler-vertical; }
.@{fa-css-prefix}-rupee-sign:before { content: @fa-var-rupee-sign; }
.@{fa-css-prefix}-safari:before { content: @fa-var-safari; }
.@{fa-css-prefix}-sass:before { content: @fa-var-sass; }
.@{fa-css-prefix}-save:before { content: @fa-var-save; }
.@{fa-css-prefix}-schlix:before { content: @fa-var-schlix; }
.@{fa-css-prefix}-school:before { content: @fa-var-school; }
.@{fa-css-prefix}-screwdriver:before { content: @fa-var-screwdriver; }
.@{fa-css-prefix}-scribd:before { content: @fa-var-scribd; }
.@{fa-css-prefix}-search:before { content: @fa-var-search; }
.@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; }
.@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; }
.@{fa-css-prefix}-searchengin:before { content: @fa-var-searchengin; }
.@{fa-css-prefix}-seedling:before { content: @fa-var-seedling; }
.@{fa-css-prefix}-sellcast:before { content: @fa-var-sellcast; }
.@{fa-css-prefix}-sellsy:before { content: @fa-var-sellsy; }
.@{fa-css-prefix}-server:before { content: @fa-var-server; }
.@{fa-css-prefix}-servicestack:before { content: @fa-var-servicestack; }
.@{fa-css-prefix}-share:before { content: @fa-var-share; }
.@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; }
.@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; }
.@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; }
.@{fa-css-prefix}-shekel-sign:before { content: @fa-var-shekel-sign; }
.@{fa-css-prefix}-shield-alt:before { content: @fa-var-shield-alt; }
.@{fa-css-prefix}-ship:before { content: @fa-var-ship; }
.@{fa-css-prefix}-shipping-fast:before { content: @fa-var-shipping-fast; }
.@{fa-css-prefix}-shirtsinbulk:before { content: @fa-var-shirtsinbulk; }
.@{fa-css-prefix}-shoe-prints:before { content: @fa-var-shoe-prints; }
.@{fa-css-prefix}-shopping-bag:before { content: @fa-var-shopping-bag; }
.@{fa-css-prefix}-shopping-basket:before { content: @fa-var-shopping-basket; }
.@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; }
.@{fa-css-prefix}-shower:before { content: @fa-var-shower; }
.@{fa-css-prefix}-sign:before { content: @fa-var-sign; }
.@{fa-css-prefix}-sign-in-alt:before { content: @fa-var-sign-in-alt; }
.@{fa-css-prefix}-sign-language:before { content: @fa-var-sign-language; }
.@{fa-css-prefix}-sign-out-alt:before { content: @fa-var-sign-out-alt; }
.@{fa-css-prefix}-signal:before { content: @fa-var-signal; }
.@{fa-css-prefix}-simplybuilt:before { content: @fa-var-simplybuilt; }
.@{fa-css-prefix}-sistrix:before { content: @fa-var-sistrix; }
.@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; }
.@{fa-css-prefix}-sith:before { content: @fa-var-sith; }
.@{fa-css-prefix}-skull:before { content: @fa-var-skull; }
.@{fa-css-prefix}-skyatlas:before { content: @fa-var-skyatlas; }
.@{fa-css-prefix}-skype:before { content: @fa-var-skype; }
.@{fa-css-prefix}-slack:before { content: @fa-var-slack; }
.@{fa-css-prefix}-slack-hash:before { content: @fa-var-slack-hash; }
.@{fa-css-prefix}-sliders-h:before { content: @fa-var-sliders-h; }
.@{fa-css-prefix}-slideshare:before { content: @fa-var-slideshare; }
.@{fa-css-prefix}-smile:before { content: @fa-var-smile; }
.@{fa-css-prefix}-smoking:before { content: @fa-var-smoking; }
.@{fa-css-prefix}-smoking-ban:before { content: @fa-var-smoking-ban; }
.@{fa-css-prefix}-snapchat:before { content: @fa-var-snapchat; }
.@{fa-css-prefix}-snapchat-ghost:before { content: @fa-var-snapchat-ghost; }
.@{fa-css-prefix}-snapchat-square:before { content: @fa-var-snapchat-square; }
.@{fa-css-prefix}-snowflake:before { content: @fa-var-snowflake; }
.@{fa-css-prefix}-sort:before { content: @fa-var-sort; }
.@{fa-css-prefix}-sort-alpha-down:before { content: @fa-var-sort-alpha-down; }
.@{fa-css-prefix}-sort-alpha-up:before { content: @fa-var-sort-alpha-up; }
.@{fa-css-prefix}-sort-amount-down:before { content: @fa-var-sort-amount-down; }
.@{fa-css-prefix}-sort-amount-up:before { content: @fa-var-sort-amount-up; }
.@{fa-css-prefix}-sort-down:before { content: @fa-var-sort-down; }
.@{fa-css-prefix}-sort-numeric-down:before { content: @fa-var-sort-numeric-down; }
.@{fa-css-prefix}-sort-numeric-up:before { content: @fa-var-sort-numeric-up; }
.@{fa-css-prefix}-sort-up:before { content: @fa-var-sort-up; }
.@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; }
.@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; }
.@{fa-css-prefix}-speakap:before { content: @fa-var-speakap; }
.@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; }
.@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; }
.@{fa-css-prefix}-square:before { content: @fa-var-square; }
.@{fa-css-prefix}-square-full:before { content: @fa-var-square-full; }
.@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; }
.@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; }
.@{fa-css-prefix}-star:before { content: @fa-var-star; }
.@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; }
.@{fa-css-prefix}-staylinked:before { content: @fa-var-staylinked; }
.@{fa-css-prefix}-steam:before { content: @fa-var-steam; }
.@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; }
.@{fa-css-prefix}-steam-symbol:before { content: @fa-var-steam-symbol; }
.@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; }
.@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; }
.@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; }
.@{fa-css-prefix}-sticker-mule:before { content: @fa-var-sticker-mule; }
.@{fa-css-prefix}-sticky-note:before { content: @fa-var-sticky-note; }
.@{fa-css-prefix}-stop:before { content: @fa-var-stop; }
.@{fa-css-prefix}-stop-circle:before { content: @fa-var-stop-circle; }
.@{fa-css-prefix}-stopwatch:before { content: @fa-var-stopwatch; }
.@{fa-css-prefix}-store:before { content: @fa-var-store; }
.@{fa-css-prefix}-store-alt:before { content: @fa-var-store-alt; }
.@{fa-css-prefix}-strava:before { content: @fa-var-strava; }
.@{fa-css-prefix}-stream:before { content: @fa-var-stream; }
.@{fa-css-prefix}-street-view:before { content: @fa-var-street-view; }
.@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; }
.@{fa-css-prefix}-stripe:before { content: @fa-var-stripe; }
.@{fa-css-prefix}-stripe-s:before { content: @fa-var-stripe-s; }
.@{fa-css-prefix}-stroopwafel:before { content: @fa-var-stroopwafel; }
.@{fa-css-prefix}-studiovinari:before { content: @fa-var-studiovinari; }
.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; }
.@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; }
.@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; }
.@{fa-css-prefix}-subway:before { content: @fa-var-subway; }
.@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; }
.@{fa-css-prefix}-sun:before { content: @fa-var-sun; }
.@{fa-css-prefix}-superpowers:before { content: @fa-var-superpowers; }
.@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; }
.@{fa-css-prefix}-supple:before { content: @fa-var-supple; }
.@{fa-css-prefix}-sync:before { content: @fa-var-sync; }
.@{fa-css-prefix}-sync-alt:before { content: @fa-var-sync-alt; }
.@{fa-css-prefix}-syringe:before { content: @fa-var-syringe; }
.@{fa-css-prefix}-table:before { content: @fa-var-table; }
.@{fa-css-prefix}-table-tennis:before { content: @fa-var-table-tennis; }
.@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; }
.@{fa-css-prefix}-tablet-alt:before { content: @fa-var-tablet-alt; }
.@{fa-css-prefix}-tablets:before { content: @fa-var-tablets; }
.@{fa-css-prefix}-tachometer-alt:before { content: @fa-var-tachometer-alt; }
.@{fa-css-prefix}-tag:before { content: @fa-var-tag; }
.@{fa-css-prefix}-tags:before { content: @fa-var-tags; }
.@{fa-css-prefix}-tape:before { content: @fa-var-tape; }
.@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; }
.@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; }
.@{fa-css-prefix}-teamspeak:before { content: @fa-var-teamspeak; }
.@{fa-css-prefix}-telegram:before { content: @fa-var-telegram; }
.@{fa-css-prefix}-telegram-plane:before { content: @fa-var-telegram-plane; }
.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; }
.@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; }
.@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; }
.@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; }
.@{fa-css-prefix}-th:before { content: @fa-var-th; }
.@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; }
.@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; }
.@{fa-css-prefix}-themeisle:before { content: @fa-var-themeisle; }
.@{fa-css-prefix}-thermometer:before { content: @fa-var-thermometer; }
.@{fa-css-prefix}-thermometer-empty:before { content: @fa-var-thermometer-empty; }
.@{fa-css-prefix}-thermometer-full:before { content: @fa-var-thermometer-full; }
.@{fa-css-prefix}-thermometer-half:before { content: @fa-var-thermometer-half; }
.@{fa-css-prefix}-thermometer-quarter:before { content: @fa-var-thermometer-quarter; }
.@{fa-css-prefix}-thermometer-three-quarters:before { content: @fa-var-thermometer-three-quarters; }
.@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; }
.@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; }
.@{fa-css-prefix}-thumbtack:before { content: @fa-var-thumbtack; }
.@{fa-css-prefix}-ticket-alt:before { content: @fa-var-ticket-alt; }
.@{fa-css-prefix}-times:before { content: @fa-var-times; }
.@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; }
.@{fa-css-prefix}-tint:before { content: @fa-var-tint; }
.@{fa-css-prefix}-toggle-off:before { content: @fa-var-toggle-off; }
.@{fa-css-prefix}-toggle-on:before { content: @fa-var-toggle-on; }
.@{fa-css-prefix}-toolbox:before { content: @fa-var-toolbox; }
.@{fa-css-prefix}-trade-federation:before { content: @fa-var-trade-federation; }
.@{fa-css-prefix}-trademark:before { content: @fa-var-trademark; }
.@{fa-css-prefix}-train:before { content: @fa-var-train; }
.@{fa-css-prefix}-transgender:before { content: @fa-var-transgender; }
.@{fa-css-prefix}-transgender-alt:before { content: @fa-var-transgender-alt; }
.@{fa-css-prefix}-trash:before { content: @fa-var-trash; }
.@{fa-css-prefix}-trash-alt:before { content: @fa-var-trash-alt; }
.@{fa-css-prefix}-tree:before { content: @fa-var-tree; }
.@{fa-css-prefix}-trello:before { content: @fa-var-trello; }
.@{fa-css-prefix}-tripadvisor:before { content: @fa-var-tripadvisor; }
.@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; }
.@{fa-css-prefix}-truck:before { content: @fa-var-truck; }
.@{fa-css-prefix}-truck-loading:before { content: @fa-var-truck-loading; }
.@{fa-css-prefix}-truck-moving:before { content: @fa-var-truck-moving; }
.@{fa-css-prefix}-tshirt:before { content: @fa-var-tshirt; }
.@{fa-css-prefix}-tty:before { content: @fa-var-tty; }
.@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; }
.@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; }
.@{fa-css-prefix}-tv:before { content: @fa-var-tv; }
.@{fa-css-prefix}-twitch:before { content: @fa-var-twitch; }
.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; }
.@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; }
.@{fa-css-prefix}-typo3:before { content: @fa-var-typo3; }
.@{fa-css-prefix}-uber:before { content: @fa-var-uber; }
.@{fa-css-prefix}-uikit:before { content: @fa-var-uikit; }
.@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; }
.@{fa-css-prefix}-underline:before { content: @fa-var-underline; }
.@{fa-css-prefix}-undo:before { content: @fa-var-undo; }
.@{fa-css-prefix}-undo-alt:before { content: @fa-var-undo-alt; }
.@{fa-css-prefix}-uniregistry:before { content: @fa-var-uniregistry; }
.@{fa-css-prefix}-universal-access:before { content: @fa-var-universal-access; }
.@{fa-css-prefix}-university:before { content: @fa-var-university; }
.@{fa-css-prefix}-unlink:before { content: @fa-var-unlink; }
.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; }
.@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; }
.@{fa-css-prefix}-untappd:before { content: @fa-var-untappd; }
.@{fa-css-prefix}-upload:before { content: @fa-var-upload; }
.@{fa-css-prefix}-usb:before { content: @fa-var-usb; }
.@{fa-css-prefix}-user:before { content: @fa-var-user; }
.@{fa-css-prefix}-user-alt:before { content: @fa-var-user-alt; }
.@{fa-css-prefix}-user-alt-slash:before { content: @fa-var-user-alt-slash; }
.@{fa-css-prefix}-user-astronaut:before { content: @fa-var-user-astronaut; }
.@{fa-css-prefix}-user-check:before { content: @fa-var-user-check; }
.@{fa-css-prefix}-user-circle:before { content: @fa-var-user-circle; }
.@{fa-css-prefix}-user-clock:before { content: @fa-var-user-clock; }
.@{fa-css-prefix}-user-cog:before { content: @fa-var-user-cog; }
.@{fa-css-prefix}-user-edit:before { content: @fa-var-user-edit; }
.@{fa-css-prefix}-user-friends:before { content: @fa-var-user-friends; }
.@{fa-css-prefix}-user-graduate:before { content: @fa-var-user-graduate; }
.@{fa-css-prefix}-user-lock:before { content: @fa-var-user-lock; }
.@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; }
.@{fa-css-prefix}-user-minus:before { content: @fa-var-user-minus; }
.@{fa-css-prefix}-user-ninja:before { content: @fa-var-user-ninja; }
.@{fa-css-prefix}-user-plus:before { content: @fa-var-user-plus; }
.@{fa-css-prefix}-user-secret:before { content: @fa-var-user-secret; }
.@{fa-css-prefix}-user-shield:before { content: @fa-var-user-shield; }
.@{fa-css-prefix}-user-slash:before { content: @fa-var-user-slash; }
.@{fa-css-prefix}-user-tag:before { content: @fa-var-user-tag; }
.@{fa-css-prefix}-user-tie:before { content: @fa-var-user-tie; }
.@{fa-css-prefix}-user-times:before { content: @fa-var-user-times; }
.@{fa-css-prefix}-users:before { content: @fa-var-users; }
.@{fa-css-prefix}-users-cog:before { content: @fa-var-users-cog; }
.@{fa-css-prefix}-ussunnah:before { content: @fa-var-ussunnah; }
.@{fa-css-prefix}-utensil-spoon:before { content: @fa-var-utensil-spoon; }
.@{fa-css-prefix}-utensils:before { content: @fa-var-utensils; }
.@{fa-css-prefix}-vaadin:before { content: @fa-var-vaadin; }
.@{fa-css-prefix}-venus:before { content: @fa-var-venus; }
.@{fa-css-prefix}-venus-double:before { content: @fa-var-venus-double; }
.@{fa-css-prefix}-venus-mars:before { content: @fa-var-venus-mars; }
.@{fa-css-prefix}-viacoin:before { content: @fa-var-viacoin; }
.@{fa-css-prefix}-viadeo:before { content: @fa-var-viadeo; }
.@{fa-css-prefix}-viadeo-square:before { content: @fa-var-viadeo-square; }
.@{fa-css-prefix}-vial:before { content: @fa-var-vial; }
.@{fa-css-prefix}-vials:before { content: @fa-var-vials; }
.@{fa-css-prefix}-viber:before { content: @fa-var-viber; }
.@{fa-css-prefix}-video:before { content: @fa-var-video; }
.@{fa-css-prefix}-video-slash:before { content: @fa-var-video-slash; }
.@{fa-css-prefix}-vimeo:before { content: @fa-var-vimeo; }
.@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; }
.@{fa-css-prefix}-vimeo-v:before { content: @fa-var-vimeo-v; }
.@{fa-css-prefix}-vine:before { content: @fa-var-vine; }
.@{fa-css-prefix}-vk:before { content: @fa-var-vk; }
.@{fa-css-prefix}-vnv:before { content: @fa-var-vnv; }
.@{fa-css-prefix}-volleyball-ball:before { content: @fa-var-volleyball-ball; }
.@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; }
.@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; }
.@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; }
.@{fa-css-prefix}-vuejs:before { content: @fa-var-vuejs; }
.@{fa-css-prefix}-walking:before { content: @fa-var-walking; }
.@{fa-css-prefix}-wallet:before { content: @fa-var-wallet; }
.@{fa-css-prefix}-warehouse:before { content: @fa-var-warehouse; }
.@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; }
.@{fa-css-prefix}-weight:before { content: @fa-var-weight; }
.@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; }
.@{fa-css-prefix}-whatsapp:before { content: @fa-var-whatsapp; }
.@{fa-css-prefix}-whatsapp-square:before { content: @fa-var-whatsapp-square; }
.@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; }
.@{fa-css-prefix}-whmcs:before { content: @fa-var-whmcs; }
.@{fa-css-prefix}-wifi:before { content: @fa-var-wifi; }
.@{fa-css-prefix}-wikipedia-w:before { content: @fa-var-wikipedia-w; }
.@{fa-css-prefix}-window-close:before { content: @fa-var-window-close; }
.@{fa-css-prefix}-window-maximize:before { content: @fa-var-window-maximize; }
.@{fa-css-prefix}-window-minimize:before { content: @fa-var-window-minimize; }
.@{fa-css-prefix}-window-restore:before { content: @fa-var-window-restore; }
.@{fa-css-prefix}-windows:before { content: @fa-var-windows; }
.@{fa-css-prefix}-wine-glass:before { content: @fa-var-wine-glass; }
.@{fa-css-prefix}-wolf-pack-battalion:before { content: @fa-var-wolf-pack-battalion; }
.@{fa-css-prefix}-won-sign:before { content: @fa-var-won-sign; }
.@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; }
.@{fa-css-prefix}-wordpress-simple:before { content: @fa-var-wordpress-simple; }
.@{fa-css-prefix}-wpbeginner:before { content: @fa-var-wpbeginner; }
.@{fa-css-prefix}-wpexplorer:before { content: @fa-var-wpexplorer; }
.@{fa-css-prefix}-wpforms:before { content: @fa-var-wpforms; }
.@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; }
.@{fa-css-prefix}-x-ray:before { content: @fa-var-x-ray; }
.@{fa-css-prefix}-xbox:before { content: @fa-var-xbox; }
.@{fa-css-prefix}-xing:before { content: @fa-var-xing; }
.@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; }
.@{fa-css-prefix}-y-combinator:before { content: @fa-var-y-combinator; }
.@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; }
.@{fa-css-prefix}-yandex:before { content: @fa-var-yandex; }
.@{fa-css-prefix}-yandex-international:before { content: @fa-var-yandex-international; }
.@{fa-css-prefix}-yelp:before { content: @fa-var-yelp; }
.@{fa-css-prefix}-yen-sign:before { content: @fa-var-yen-sign; }
.@{fa-css-prefix}-yoast:before { content: @fa-var-yoast; }
.@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; }
.@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; }

View File

@ -0,0 +1,27 @@
// Icon Sizes
// -------------------------
.larger(@factor) when (@factor > 0) {
.larger((@factor - 1));
.@{fa-css-prefix}-@{factor}x {
font-size: (@factor * 1em);
}
}
/* makes the font 33% larger relative to the icon container */
.@{fa-css-prefix}-lg {
font-size: (4em / 3);
line-height: (3em / 4);
vertical-align: -.0667em;
}
.@{fa-css-prefix}-xs {
font-size: .75em;
}
.@{fa-css-prefix}-sm {
font-size: .875em;
}
.larger(10);

View File

@ -0,0 +1,18 @@
// List Icons
// -------------------------
.@{fa-css-prefix}-ul {
list-style-type: none;
margin-left: @fa-li-width * 5/4;
padding-left: 0;
> li { position: relative; }
}
.@{fa-css-prefix}-li {
left: -@fa-li-width;
position: absolute;
text-align: center;
width: @fa-li-width;
line-height: inherit;
}

View File

@ -0,0 +1,57 @@
// Mixins
// --------------------------
.fa-icon() {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1;
vertical-align: -.125em;
}
.fa-icon-rotate(@degrees, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
transform: rotate(@degrees);
}
.fa-icon-flip(@horiz, @vert, @rotation) {
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
transform: scale(@horiz, @vert);
}
// Only display content to screen readers. A la Bootstrap 4.
//
// See: http://a11yproject.com/posts/how-to-hide-content/
.sr-only() {
border: 0;
clip: rect(0,0,0,0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
// Use in conjunction with .sr-only to only display content when it's focused.
//
// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
//
// Credit: HTML5 Boilerplate
.sr-only-focusable() {
&:active,
&:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
}

View File

@ -0,0 +1,23 @@
// Rotated & Flipped Icons
// -------------------------
.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
.@{fa-css-prefix}-flip-horizontal.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(-1, -1, 2); }
// Hook for IE8-9
// -------------------------
:root {
.@{fa-css-prefix}-rotate-90,
.@{fa-css-prefix}-rotate-180,
.@{fa-css-prefix}-rotate-270,
.@{fa-css-prefix}-flip-horizontal,
.@{fa-css-prefix}-flip-vertical {
filter: none;
}
}

View File

@ -0,0 +1,5 @@
// Screen Readers
// -------------------------
.sr-only { .sr-only(); }
.sr-only-focusable { .sr-only-focusable(); }

View File

@ -0,0 +1,22 @@
// Stacked Icons
// -------------------------
.@{fa-css-prefix}-stack {
display: inline-block;
height: 2em;
line-height: 2em;
position: relative;
vertical-align: middle;
width: 2em;
}
.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
left: 0;
position: absolute;
text-align: center;
width: 100%;
}
.@{fa-css-prefix}-stack-1x { line-height: inherit; }
.@{fa-css-prefix}-stack-2x { font-size: 2em; }
.@{fa-css-prefix}-inverse { color: @fa-inverse; }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
/*!
* Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@import "_variables.less";
@font-face {
font-family: 'Font Awesome 5 Brands';
font-style: normal;
font-weight: normal;
src: url('@{fa-font-path}/fa-brands-400.eot');
src: url('@{fa-font-path}/fa-brands-400.eot?#iefix') format('embedded-opentype'),
url('@{fa-font-path}/fa-brands-400.woff2') format('woff2'),
url('@{fa-font-path}/fa-brands-400.woff') format('woff'),
url('@{fa-font-path}/fa-brands-400.ttf') format('truetype'),
url('@{fa-font-path}/fa-brands-400.svg#fontawesome') format('svg');
}
.fab {
font-family: 'Font Awesome 5 Brands';
}

Some files were not shown because too many files have changed in this diff Show More