diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..300c33f86 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM php:8.1.31-apache + +# Dockerfile's Metadata +LABEL name="GMDprivateServer" \ + description="A Geometry Dash Server Emulator" + +# Install necessary dependencies +RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && \ + docker-php-ext-install pdo pdo_mysql && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /var/www/html + +# Clone the repository +ARG BRANCH=master +RUN git clone --branch ${BRANCH} https://github.com/MegaSa1nt/GMDprivateServer.git . && \ + chown -R www-data:www-data /var/www/html + +# Export Apache's port +EXPOSE 80 + +# Start Apache +CMD ["apache2-foreground"] diff --git a/README.md b/README.md index b88ff999d..313ce9d42 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,11 @@ ## Geometry Dash Private Server Basically a Geometry Dash Server Emulator -Supported version of Geometry Dash: 1.0 - 2.2 +Supported version of Geometry Dash: 1.0 - 2.207 (See [the backwards compatibility section of this article](https://github.com/Cvolton/GMDprivateServer/wiki/Deliberate-differences-from-real-GD) for more information) -Required version of PHP: 5.5+ (tested up to 8.1.2) - -### Branches -- master - This is the main version of the server. -- untested - This branch contains the newest breaking changes. Note that this branch has virtually no support and therefore you're on your own if stuff breaks. +Required version of PHP: 7.0+ (tested up to 8.3) ### Setup 1) Upload the files on a webserver @@ -18,13 +14,39 @@ Required version of PHP: 5.5+ (tested up to 8.1.2) 3) Edit the links in GeometryDash.exe (some are base64 encoded since 2.1, remember that) #### Updating the server -See [README.md in the `_updates`](_updates/README.md) +1) Upload the files on a webserver +2) Set `$installed` to false in config/dashboard.php +3) Run main dashboard's page ### Credits Base for account settings and the private messaging system by someguy28 -Using this for XOR encryption - https://github.com/sathoro/php-xor-cipher - (incl/lib/XORCipher.php) +XOR encryption — https://github.com/sathoro/php-xor-cipher — (incl/lib/XORCipher.php) + +Cloud save encryption — https://github.com/defuse/php-encryption — (incl/lib/defuse-crypto.phar) + +Mail verification — https://github.com/phpmailer/phpmailer — (config/mail) + +JQuery — https://github.com/jquery/jquery — (dashboard/lib/jq.js) + +Image dominant color picker — https://github.com/swaydeng/imgcolr — (dashboard/lib/imgcolr.js) + +Media cover — https://github.com/aadsm/jsmediatags — (dashboard/lib/jsmediatags.js) + +Audio duration — https://github.com/JamesHeinrich/getID3 — (config/getid3) + +Proxies list — https://github.com/SevenworksDev/proxy-list — (config/proxies.txt) + +Common VPNs list — https://github.com/X4BNet/lists_vpn — (config/vpns.txt) + +Discord Webhooks — https://github.com/renzbobz/DiscordWebhook-PHP — (config/webhooks/DiscordWebhook.php) + +GD icons — https://github.com/oatmealine/gd-icon-renderer-web — (any page with player's username) + +Cloudflare IPs List — https://www.cloudflare.com/ips — (incl/lib/mainLib.php & incl/lib/ipCheck.php) + +Translit — https://github.com/ashtokalo/php-translit — (config/translit) -Using this for cloud save encryption - https://github.com/defuse/php-encryption - (incl/lib/defuse-crypto.phar) +Snow — https://embed.im/snow — (dashboard) Most of the stuff in generateHash.php has been figured out by pavlukivan and Italian APK Downloader, so credits to them diff --git a/acceptGJFriendRequest20.php b/acceptGJFriendRequest20.php index 8dad437ae..431012219 100644 --- a/acceptGJFriendRequest20.php +++ b/acceptGJFriendRequest20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/accounts/accountManagement.php b/accounts/accountManagement.php index 017fe4c01..f3c323ad0 100644 --- a/accounts/accountManagement.php +++ b/accounts/accountManagement.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/accounts/activate.php b/accounts/activate.php new file mode 100644 index 000000000..9d9a7a71e --- /dev/null +++ b/accounts/activate.php @@ -0,0 +1,23 @@ +prepare("SELECT accountID FROM accounts WHERE mail = :mail"); + $check->execute([':mail' => $mail]); + $check = $check->fetch(); + if(empty($check)) { + $gs->logAction(0, 4, 1); + die("Nothing found!"); + } else { + $query = $db->prepare("UPDATE accounts SET isActive = '1', mail = 'activated' WHERE accountID = :acc"); + $query->execute([':acc' => $check["accountID"]]); + $gs->logAction($check["accountID"], 3, 1); + die("Account was successfully activated!"); + } +} \ No newline at end of file diff --git a/accounts/backupGJAccount.php b/accounts/backupGJAccount.php index 9357299f5..c5d2a2f54 100644 --- a/accounts/backupGJAccount.php +++ b/accounts/backupGJAccount.php @@ -1,19 +1,18 @@ prepare("SELECT accountID FROM accounts WHERE userName = :userName"); $query->execute([':userName' => $userName]); $accountID = $query->fetchColumn(); -} else { - $accountID = ExploitPatch::remove($_POST["accountID"]); -} +} else $accountID = ExploitPatch::number($_POST["accountID"]); -if(!is_numeric($accountID)){ - exit("-1"); -} +if(!is_numeric($accountID)) exit("-1"); $pass = 0; if(!empty($_POST["password"])) $pass = GeneratePass::isValid($accountID, $_POST["password"]); elseif(!empty($_POST["gjp2"])) $pass = GeneratePass::isGJP2Valid($accountID, $_POST["gjp2"]); -if ($pass == 1) { - $saveDataArr = explode(";",$saveData); //splitting ccgamemanager and cclocallevels - $saveData = str_replace("-","+",$saveDataArr[0]); //decoding - $saveData = str_replace("_","/",$saveData); - $saveData = base64_decode($saveData); +if($pass == 1) { + $saveDataArr = explode(";",$saveData); + $saveData = ExploitPatch::url_base64_decode($saveDataArr[0]); $saveData = gzdecode($saveData); $orbs = explode("14",$saveData)[1]; - $orbs = explode("",$orbs)[0]; + $orbs = explode("",$orbs)[0] ?? 0; $lvls = explode("GS_value",$saveData)[1]; $lvls = explode("4",$lvls)[1]; - $lvls = explode("",$lvls)[0]; - $protected_key_encoded = ""; - //if($cloudSaveEncryption == 0){ - $saveData = str_replace("GJA_002".$password."", "GJA_002password", $saveData); //replacing pass - //file_put_contents($userName, $saveData); - $saveData = gzencode($saveData); //encoding back - $saveData = base64_encode($saveData); - $saveData = str_replace("+","-",$saveData); - $saveData = str_replace("/","_",$saveData); - $saveData = $saveData . ";" . $saveDataArr[1]; //merging ccgamemanager and cclocallevels - /*}else if($cloudSaveEncryption == 1){ - $saveData = ExploitPatch::remove($_POST["saveData"]); - $protected_key = KeyProtectedByPassword::createRandomPasswordProtectedKey($password); - $protected_key_encoded = $protected_key->saveToAsciiSafeString(); - $user_key = $protected_key->unlockKey($password); - $saveData = Crypto::encrypt($saveData, $user_key); - }*/ - //$query = $db->prepare("UPDATE `accounts` SET `saveData` = :saveData WHERE userName = :userName"); - //$query->execute([':saveData' => $saveData, ':userName' => $userName]); + $lvls = explode("",$lvls)[0] ?? 0; + $saveData = str_replace("GJA_002".$password."", "GJA_002password", $saveData); + $saveData = gzencode($saveData); + $saveData = ExploitPatch::url_base64_encode($saveData); + $saveData = $saveData . ";" . $saveDataArr[1]; file_put_contents("../data/accounts/$accountID",$saveData); file_put_contents("../data/accounts/keys/$accountID",""); - $query = $db->prepare("SELECT extID FROM users WHERE userName = :userName LIMIT 1"); - $query->execute([':userName' => $userName]); - $result = $query->fetchAll(); - $result = $result[0]; - $extID = $result["extID"]; $query = $db->prepare("UPDATE `users` SET `orbs` = :orbs, `completedLvls` = :lvls WHERE extID = :extID"); - $query->execute([':orbs' => $orbs, ':extID' => $extID, ':lvls' => $lvls]); + $query->execute([':orbs' => $orbs, ':extID' => $accountID, ':lvls' => $lvls]); + $gs->logAction($accountID, 5, $userName, filesize("../data/accounts/$accountID"), $orbs, $lvls); echo "1"; -} -else -{ - echo -1; +} else { + $gs->logAction($accountID, 7, $userName, strlen($saveData)); + echo "-1"; } ?> diff --git a/accounts/loginGJAccount.php b/accounts/loginGJAccount.php index 9aa1a03d4..7a8467d82 100644 --- a/accounts/loginGJAccount.php +++ b/accounts/loginGJAccount.php @@ -1,54 +1,29 @@ getIP(); $udid = ExploitPatch::remove($_POST["udid"]); -$userName = ExploitPatch::remove($_POST["userName"]); -//registering +$userName = ExploitPatch::charclean($_POST["userName"]); $query = $db->prepare("SELECT accountID FROM accounts WHERE userName LIKE :userName"); $query->execute([':userName' => $userName]); -if($query->rowCount() == 0){ - exit("-1"); -} -$id = $query->fetchColumn(); - +if($query->rowCount() == 0) exit("-1"); +$accountID = $query->fetchColumn(); $pass = 0; if(!empty($_POST["password"])) $pass = GeneratePass::isValidUsrname($userName, $_POST["password"]); elseif(!empty($_POST["gjp2"])) $pass = GeneratePass::isGJP2ValidUsrname($userName, $_POST["gjp2"]); -if ($pass == 1) { //success - //userID - $query2 = $db->prepare("SELECT userID FROM users WHERE extID = :id"); - - $query2->execute([':id' => $id]); - if ($query2->rowCount() > 0) { - $userID = $query2->fetchColumn(); - } else { - $query = $db->prepare("INSERT INTO users (isRegistered, extID, userName) - VALUES (1, :id, :userName)"); - - $query->execute([':id' => $id, ':userName' => $userName]); - $userID = $db->lastInsertId(); - } - //logging - $query6 = $db->prepare("INSERT INTO actions (type, value, timestamp, value2) VALUES - ('2',:username,:time,:ip)"); - $query6->execute([':username' => $userName, ':time' => time(), ':ip' => $ip]); - //result - echo $id.",".$userID; - if(!is_numeric($udid)){ +if($pass == 1) { + $gs->logAction($accountID, 2); + $userID = $gs->getUserID($accountID, $userName); + if(!is_numeric($udid)) { $query2 = $db->prepare("SELECT userID FROM users WHERE extID = :udid"); $query2->execute([':udid' => $udid]); $usrid2 = $query2->fetchColumn(); $query2 = $db->prepare("UPDATE levels SET userID = :userID, extID = :extID WHERE userID = :usrid2"); - $query2->execute([':userID' => $userID, ':extID' => $id, ':usrid2' => $usrid2]); + $query2->execute([':userID' => $userID, ':extID' => $accountID, ':usrid2' => $usrid2]); } -}elseif ($pass == -1){ //failure - echo -12; -}else{ - echo -1; -} + exit($accountID.",".$userID); +} elseif($pass == '-1') exit('-12'); +exit('-1'); ?> \ No newline at end of file diff --git a/accounts/registerGJAccount.php b/accounts/registerGJAccount.php index 708b8f1aa..81145074d 100644 --- a/accounts/registerGJAccount.php +++ b/accounts/registerGJAccount.php @@ -1,35 +1,57 @@ 20) - exit("-4"); - //checking if name is taken +require_once "../incl/lib/generatePass.php"; +require_once "../incl/lib/automod.php"; +if(Automod::isAccountsDisabled(0)) exit('-1'); +if(!isset($preactivateAccounts)) $preactivateAccounts = true; +if(!isset($filterUsernames)) global $filterUsernames; +if(!empty($_POST["userName"]) AND !empty($_POST["password"]) AND !empty($_POST["email"])) { + $userName = str_replace(' ', '', ExploitPatch::charclean($_POST["userName"])); + $password = $_POST["password"]; + $email = ExploitPatch::rucharclean($_POST["email"]); + if($filterUsernames >= 1) { + $bannedUsernamesList = array_map('strtolower', $bannedUsernames); + switch($filterUsernames) { + case 1: + if(in_array(strtolower($userName), $bannedUsernamesList)) exit("-4"); + break; + case 2: + foreach($bannedUsernamesList as $bannedUsername) { + if(!empty($bannedUsername) && mb_strpos(strtolower($userName), $bannedUsername) !== false) exit("-4"); + } + } + } + if(strlen($userName) > 20) exit("-4"); + if(strlen($userName) < 3) exit("-9"); + if(strlen($password) < 6) exit("-8"); + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) exit("-6"); + if($mailEnabled) { + $checkMail = $db->prepare("SELECT count(*) FROM accounts WHERE email LIKE :mail"); + $checkMail->execute([':mail' => $email]); + $checkMail = $checkMail->fetchColumn(); + if($checkMail > 0) exit("-3"); + } $query2 = $db->prepare("SELECT count(*) FROM accounts WHERE userName LIKE :userName"); $query2->execute([':userName' => $userName]); $regusrs = $query2->fetchColumn(); - if ($regusrs > 0) { + if($regusrs > 0) { echo "-2"; - }else{ + } else { $hashpass = password_hash($password, PASSWORD_DEFAULT); $gjp2 = GeneratePass::GJP2hash($password); $query = $db->prepare("INSERT INTO accounts (userName, password, email, registerDate, isActive, gjp2) VALUES (:userName, :password, :email, :time, :isActive, :gjp)"); $query->execute([':userName' => $userName, ':password' => $hashpass, ':email' => $email, ':time' => time(), ':isActive' => $preactivateAccounts ? 1 : 0, ':gjp' => $gjp2]); + $accountID = $db->lastInsertId(); echo "1"; + $gs->logAction($accountID, 1, $userName, $email, $gs->getUserID($accountID, $userName)); + $gs->sendLogsRegisterWebhook($accountID); + if($mailEnabled) $gs->mail($email, $userName); } -} +} else echo "-1"; ?> diff --git a/accounts/syncGJAccount.php b/accounts/syncGJAccount.php index 3b46d01b9..5b3f4e9a3 100644 --- a/accounts/syncGJAccount.php +++ b/accounts/syncGJAccount.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/accounts/syncGJAccount20.php b/accounts/syncGJAccount20.php index bf292b674..5cc392af9 100644 --- a/accounts/syncGJAccount20.php +++ b/accounts/syncGJAccount20.php @@ -1,35 +1,35 @@ prepare("SELECT accountID FROM accounts WHERE userName = :userName"); $query->execute([':userName' => $userName]); $accountID = $query->fetchColumn(); } else { - $accountID = ExploitPatch::remove($_POST["accountID"]); + $accountID = ExploitPatch::number($_POST["accountID"]); } $pass = 0; if(!empty($_POST["password"])) $pass = GeneratePass::isValid($accountID, $_POST["password"]); elseif(!empty($_POST["gjp2"])) $pass = GeneratePass::isGJP2Valid($accountID, $_POST["gjp2"]); -if ($pass == 1) { - if(!is_numeric($accountID) || !file_exists("../data/accounts/$accountID")){ +if($pass == 1) { + if(!is_numeric($accountID) || !file_exists("../data/accounts/$accountID")) { exit("-1"); - }else{ + } else { $saveData = file_get_contents("../data/accounts/$accountID"); - if(file_exists("../data/accounts/keys/$accountID") && substr($saveData,0,3) != "H4s"){ + if(file_exists("../data/accounts/keys/$accountID") && substr($saveData,0,3) != "H4s") { $protected_key_encoded = file_get_contents("../data/accounts/keys/$accountID"); $protected_key = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded); $user_key = $protected_key->unlockKey($password); @@ -38,12 +38,15 @@ file_put_contents("../data/accounts/$accountID",$saveData); file_put_contents("../data/accounts/keys/$accountID",""); } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) { + $gs->logAction($accountID, 11, $userName, 2); exit("-3"); } } } + $gs->logAction($accountID, 10, $userName, strlen($saveData)); echo $saveData.";21;30;a;a"; -}else{ +} else { + $gs->logAction($accountID, 11, $userName, 1); echo -2; } ?> \ No newline at end of file diff --git a/blockGJUser20.php b/blockGJUser20.php index 503ca626e..18a2d5ab2 100644 --- a/blockGJUser20.php +++ b/blockGJUser20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/config/dailyChests.php b/config/dailyChests.php index c48d969ed..5ce5172f1 100644 --- a/config/dailyChests.php +++ b/config/dailyChests.php @@ -25,4 +25,4 @@ //REWARD TIMES (in seconds) $chest1wait = 3600; $chest2wait = 14400; -?> +?> \ No newline at end of file diff --git a/config/dashboard.php b/config/dashboard.php new file mode 100644 index 000000000..957dcc0b9 --- /dev/null +++ b/config/dashboard.php @@ -0,0 +1,112 @@ + diff --git a/config/discord.php b/config/discord.php index ba141f7cc..4f6fcd65a 100644 --- a/config/discord.php +++ b/config/discord.php @@ -1,5 +1,204 @@ + And people: <@user_id> + And channels: <#channel_id> + + $rateNotificationText — Text to show when rating a level + $unrateNotificationText — Text to show when unrating a level + $suggestNotificationText — Text to show when suggesting a level + $banNotificationText — Text to show when banning/unbanning people + $dailyNotificationText — Text to show when new daily level appears + $weeklyNotificationText — Text to show when new weekly level appears + $eventNotificationText — Text to show when new event level appears + $logsRegisterNotificationText — Text to show when someone registeres new account + $logsLevelChangedNotificationText — Text to show when someone changes some level + $logsAccountChangedNotificationText — Text to show when someone changes some account + $logsListChangedNotificationText — Text to show when someone changes some list + $logsModChangedNotificationText — Text to show when someone changes some moderator + $logsGauntletChangedNotificationText — Text to show when someone changes some Gauntlet + $logsMapPackChangedNotificationText — Text to show when someone changes some Map Pack + $warningsNotificationText — Text to show when warning is sent +*/ + +$rateNotificationText = ""; +$unrateNotificationText = ""; +$suggestNotificationText = ""; +$banNotificationText = ""; +$dailyNotificationText = ""; +$weeklyNotificationText = ""; +$eventNotificationText = ""; +$logsRegisterNotificationText = ""; +$logsLevelChangedNotificationText = ""; +$logsAccountChangedNotificationText = ""; +$logsListChangedNotificationText = ""; +$logsModChangedNotificationText = ""; +$logsGauntletChangedNotificationText = ""; +$logsMapPackChangedNotificationText = ""; +$warningsNotificationText = ""; ?> \ No newline at end of file diff --git a/config/getid3/getid3.lib.php b/config/getid3/getid3.lib.php new file mode 100644 index 000000000..aec550b30 --- /dev/null +++ b/config/getid3/getid3.lib.php @@ -0,0 +1,1842 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// // +// getid3.lib.php - part of getID3() // +// see readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +if(!defined('GETID3_LIBXML_OPTIONS') && defined('LIBXML_VERSION')) { + if(LIBXML_VERSION >= 20621) { + define('GETID3_LIBXML_OPTIONS', LIBXML_NOENT | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_COMPACT); + } else { + define('GETID3_LIBXML_OPTIONS', LIBXML_NOENT | LIBXML_NONET | LIBXML_NOWARNING); + } +} + +class getid3_lib +{ + /** + * @param string $string + * @param bool $hex + * @param bool $spaces + * @param string|bool $htmlencoding + * + * @return string + */ + public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { + $returnstring = ''; + for ($i = 0; $i < strlen($string); $i++) { + if ($hex) { + $returnstring .= str_pad(dechex(ord($string[$i])), 2, '0', STR_PAD_LEFT); + } else { + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string[$i]) ? $string[$i] : '¤'); + } + if ($spaces) { + $returnstring .= ' '; + } + } + if (!empty($htmlencoding)) { + if ($htmlencoding === true) { + $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean + } + $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding); + } + return $returnstring; + } + + /** + * Truncates a floating-point number at the decimal point. + * + * @param float $floatnumber + * + * @return float|int returns int (if possible, otherwise float) + */ + public static function trunc($floatnumber) { + if ($floatnumber >= 1) { + $truncatednumber = floor($floatnumber); + } elseif ($floatnumber <= -1) { + $truncatednumber = ceil($floatnumber); + } else { + $truncatednumber = 0; + } + if (self::intValueSupported($truncatednumber)) { + $truncatednumber = (int) $truncatednumber; + } + return $truncatednumber; + } + + /** + * @param int|null $variable + * @param int $increment + * + * @return bool + */ + public static function safe_inc(&$variable, $increment=1) { + if (isset($variable)) { + $variable += $increment; + } else { + $variable = $increment; + } + return true; + } + + /** + * @param int|float $floatnum + * + * @return int|float + */ + public static function CastAsInt($floatnum) { + // convert to float if not already + $floatnum = (float) $floatnum; + + // convert a float to type int, only if possible + if (self::trunc($floatnum) == $floatnum) { + // it's not floating point + if (self::intValueSupported($floatnum)) { + // it's within int range + $floatnum = (int) $floatnum; + } + } + return $floatnum; + } + + /** + * @param int $num + * + * @return bool + */ + public static function intValueSupported($num) { + // check if integers are 64-bit + static $hasINT64 = null; + if ($hasINT64 === null) { // 10x faster than is_null() + $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1 + if (!$hasINT64 && !defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); + } + } + // if integers are 64-bit - no other check required + if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) { + return true; + } + return false; + } + + /** + * Perform a division, guarding against division by zero + * + * @param float|int $numerator + * @param float|int $denominator + * @param float|int $fallback + * @return float|int + */ + public static function SafeDiv($numerator, $denominator, $fallback = 0) { + return $denominator ? $numerator / $denominator : $fallback; + } + + /** + * @param string $fraction + * + * @return float + */ + public static function DecimalizeFraction($fraction) { + list($numerator, $denominator) = explode('/', $fraction); + return (int) $numerator / ($denominator ? $denominator : 1); + } + + /** + * @param string $binarynumerator + * + * @return float + */ + public static function DecimalBinary2Float($binarynumerator) { + $numerator = self::Bin2Dec($binarynumerator); + $denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); + return ($numerator / $denominator); + } + + /** + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + * + * @param string $binarypointnumber + * @param int $maxbits + * + * @return array + */ + public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { + if (strpos($binarypointnumber, '.') === false) { + $binarypointnumber = '0.'.$binarypointnumber; + } elseif ($binarypointnumber[0] == '.') { + $binarypointnumber = '0'.$binarypointnumber; + } + $exponent = 0; + while (($binarypointnumber[0] != '1') || (substr($binarypointnumber, 1, 1) != '.')) { + if (substr($binarypointnumber, 1, 1) == '.') { + $exponent--; + $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); + } else { + $pointpos = strpos($binarypointnumber, '.'); + $exponent += ($pointpos - 1); + $binarypointnumber = str_replace('.', '', $binarypointnumber); + $binarypointnumber = $binarypointnumber[0].'.'.substr($binarypointnumber, 1); + } + } + $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); + return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent); + } + + /** + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + * + * @param float $floatvalue + * + * @return string + */ + public static function Float2BinaryDecimal($floatvalue) { + $maxbits = 128; // to how many bits of precision should the calculations be taken? + $intpart = self::trunc($floatvalue); + $floatpart = abs($floatvalue - $intpart); + $pointbitstring = ''; + while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { + $floatpart *= 2; + $pointbitstring .= (string) self::trunc($floatpart); + $floatpart -= self::trunc($floatpart); + } + $binarypointnumber = decbin($intpart).'.'.$pointbitstring; + return $binarypointnumber; + } + + /** + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html + * + * @param float $floatvalue + * @param int $bits + * + * @return string|false + */ + public static function Float2String($floatvalue, $bits) { + $exponentbits = 0; + $fractionbits = 0; + switch ($bits) { + case 32: + $exponentbits = 8; + $fractionbits = 23; + break; + + case 64: + $exponentbits = 11; + $fractionbits = 52; + break; + + default: + return false; + } + if ($floatvalue >= 0) { + $signbit = '0'; + } else { + $signbit = '1'; + } + $normalizedbinary = self::NormalizeBinaryPoint(self::Float2BinaryDecimal($floatvalue), $fractionbits); + $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent + $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); + $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); + + return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); + } + + /** + * @param string $byteword + * + * @return float|false + */ + public static function LittleEndian2Float($byteword) { + return self::BigEndian2Float(strrev($byteword)); + } + + /** + * ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic + * + * @link https://web.archive.org/web/20120325162206/http://www.psc.edu/general/software/packages/ieee/ieee.php + * @link http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html + * + * @param string $byteword + * + * @return float|false + */ + public static function BigEndian2Float($byteword) { + $bitword = self::BigEndian2Bin($byteword); + if (!$bitword) { + return 0; + } + $signbit = $bitword[0]; + $floatvalue = 0; + $exponentbits = 0; + $fractionbits = 0; + + switch (strlen($byteword) * 8) { + case 32: + $exponentbits = 8; + $fractionbits = 23; + break; + + case 64: + $exponentbits = 11; + $fractionbits = 52; + break; + + case 80: + // 80-bit Apple SANE format + // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ + $exponentstring = substr($bitword, 1, 15); + $isnormalized = intval($bitword[16]); + $fractionstring = substr($bitword, 17, 63); + $exponent = pow(2, self::Bin2Dec($exponentstring) - 16383); + $fraction = $isnormalized + self::DecimalBinary2Float($fractionstring); + $floatvalue = $exponent * $fraction; + if ($signbit == '1') { + $floatvalue *= -1; + } + return $floatvalue; + + default: + return false; + } + $exponentstring = substr($bitword, 1, $exponentbits); + $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits); + $exponent = self::Bin2Dec($exponentstring); + $fraction = self::Bin2Dec($fractionstring); + + if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { + // Not a Number + $floatvalue = NAN; + } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { + if ($signbit == '1') { + $floatvalue = -INF; + } else { + $floatvalue = INF; + } + } elseif (($exponent == 0) && ($fraction == 0)) { + if ($signbit == '1') { + $floatvalue = -0.0; + } else { + $floatvalue = 0.0; + } + } elseif (($exponent == 0) && ($fraction != 0)) { + // These are 'unnormalized' values + $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * self::DecimalBinary2Float($fractionstring); + if ($signbit == '1') { + $floatvalue *= -1; + } + } elseif ($exponent != 0) { + $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + self::DecimalBinary2Float($fractionstring)); + if ($signbit == '1') { + $floatvalue *= -1; + } + } + return (float) $floatvalue; + } + + /** + * @param string $byteword + * @param bool $synchsafe + * @param bool $signed + * + * @return int|float|false + * @throws Exception + */ + public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { + $intvalue = 0; + $bytewordlen = strlen($byteword); + if ($bytewordlen == 0) { + return false; + } + for ($i = 0; $i < $bytewordlen; $i++) { + if ($synchsafe) { // disregard MSB, effectively 7-bit bytes + //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems + $intvalue += (ord($byteword[$i]) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); + } else { + $intvalue += ord($byteword[$i]) * pow(256, ($bytewordlen - 1 - $i)); + } + } + if ($signed && !$synchsafe) { + // synchsafe ints are not allowed to be signed + if ($bytewordlen <= PHP_INT_SIZE) { + $signMaskBit = 0x80 << (8 * ($bytewordlen - 1)); + if ($intvalue & $signMaskBit) { + $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); + } + } else { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in self::BigEndian2Int()'); + } + } + return self::CastAsInt($intvalue); + } + + /** + * @param string $byteword + * @param bool $signed + * + * @return int|float|false + */ + public static function LittleEndian2Int($byteword, $signed=false) { + return self::BigEndian2Int(strrev($byteword), false, $signed); + } + + /** + * @param string $byteword + * + * @return string + */ + public static function LittleEndian2Bin($byteword) { + return self::BigEndian2Bin(strrev($byteword)); + } + + /** + * @param string $byteword + * + * @return string + */ + public static function BigEndian2Bin($byteword) { + $binvalue = ''; + $bytewordlen = strlen($byteword); + for ($i = 0; $i < $bytewordlen; $i++) { + $binvalue .= str_pad(decbin(ord($byteword[$i])), 8, '0', STR_PAD_LEFT); + } + return $binvalue; + } + + /** + * @param int $number + * @param int $minbytes + * @param bool $synchsafe + * @param bool $signed + * + * @return string + * @throws Exception + */ + public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { + if ($number < 0) { + throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers'); + } + $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); + $intstring = ''; + if ($signed) { + if ($minbytes > PHP_INT_SIZE) { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in self::BigEndian2String()'); + } + $number = $number & (0x80 << (8 * ($minbytes - 1))); + } + while ($number != 0) { + $quotient = ($number / ($maskbyte + 1)); + $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring; + $number = floor($quotient); + } + return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT); + } + + /** + * @param int $number + * + * @return string + */ + public static function Dec2Bin($number) { + if (!is_numeric($number)) { + // https://github.com/JamesHeinrich/getID3/issues/299 + trigger_error('TypeError: Dec2Bin(): Argument #1 ($number) must be numeric, '.gettype($number).' given', E_USER_WARNING); + return ''; + } + $bytes = array(); + while ($number >= 256) { + $bytes[] = (int) (($number / 256) - (floor($number / 256))) * 256; + $number = floor($number / 256); + } + $bytes[] = (int) $number; + $binstring = ''; + foreach ($bytes as $i => $byte) { + $binstring = (($i == count($bytes) - 1) ? decbin($byte) : str_pad(decbin($byte), 8, '0', STR_PAD_LEFT)).$binstring; + } + return $binstring; + } + + /** + * @param string $binstring + * @param bool $signed + * + * @return int|float + */ + public static function Bin2Dec($binstring, $signed=false) { + $signmult = 1; + if ($signed) { + if ($binstring[0] == '1') { + $signmult = -1; + } + $binstring = substr($binstring, 1); + } + $decvalue = 0; + for ($i = 0; $i < strlen($binstring); $i++) { + $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); + } + return self::CastAsInt($decvalue * $signmult); + } + + /** + * @param string $binstring + * + * @return string + */ + public static function Bin2String($binstring) { + // return 'hi' for input of '0110100001101001' + $string = ''; + $binstringreversed = strrev($binstring); + for ($i = 0; $i < strlen($binstringreversed); $i += 8) { + $string = chr(self::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; + } + return $string; + } + + /** + * @param int $number + * @param int $minbytes + * @param bool $synchsafe + * + * @return string + */ + public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { + $intstring = ''; + while ($number > 0) { + if ($synchsafe) { + $intstring = $intstring.chr($number & 127); + $number >>= 7; + } else { + $intstring = $intstring.chr($number & 255); + $number >>= 8; + } + } + return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); + } + + /** + * @param mixed $array1 + * @param mixed $array2 + * + * @return array|false + */ + public static function array_merge_clobber($array1, $array2) { + // written by kcØhireability*com + // taken from http://www.php.net/manual/en/function.array-merge-recursive.php + if (!is_array($array1) || !is_array($array2)) { + return false; + } + $newarray = $array1; + foreach ($array2 as $key => $val) { + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { + $newarray[$key] = self::array_merge_clobber($newarray[$key], $val); + } else { + $newarray[$key] = $val; + } + } + return $newarray; + } + + /** + * @param mixed $array1 + * @param mixed $array2 + * + * @return array|false + */ + public static function array_merge_noclobber($array1, $array2) { + if (!is_array($array1) || !is_array($array2)) { + return false; + } + $newarray = $array1; + foreach ($array2 as $key => $val) { + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { + $newarray[$key] = self::array_merge_noclobber($newarray[$key], $val); + } elseif (!isset($newarray[$key])) { + $newarray[$key] = $val; + } + } + return $newarray; + } + + /** + * @param mixed $array1 + * @param mixed $array2 + * + * @return array|false|null + */ + public static function flipped_array_merge_noclobber($array1, $array2) { + if (!is_array($array1) || !is_array($array2)) { + return false; + } + # naturally, this only works non-recursively + $newarray = array_flip($array1); + foreach (array_flip($array2) as $key => $val) { + if (!isset($newarray[$key])) { + $newarray[$key] = count($newarray); + } + } + return array_flip($newarray); + } + + /** + * @param array $theArray + * + * @return bool + */ + public static function ksort_recursive(&$theArray) { + ksort($theArray); + foreach ($theArray as $key => $value) { + if (is_array($value)) { + self::ksort_recursive($theArray[$key]); + } + } + return true; + } + + /** + * @param string $filename + * @param int $numextensions + * + * @return string + */ + public static function fileextension($filename, $numextensions=1) { + if (strstr($filename, '.')) { + $reversedfilename = strrev($filename); + $offset = 0; + for ($i = 0; $i < $numextensions; $i++) { + $offset = strpos($reversedfilename, '.', $offset + 1); + if ($offset === false) { + return ''; + } + } + return strrev(substr($reversedfilename, 0, $offset)); + } + return ''; + } + + /** + * @param int $seconds + * + * @return string + */ + public static function PlaytimeString($seconds) { + $sign = (($seconds < 0) ? '-' : ''); + $seconds = round(abs($seconds)); + $H = (int) floor( $seconds / 3600); + $M = (int) floor(($seconds - (3600 * $H) ) / 60); + $S = (int) round( $seconds - (3600 * $H) - (60 * $M) ); + return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); + } + + /** + * @param int $macdate + * + * @return int|float + */ + public static function DateMac2Unix($macdate) { + // Macintosh timestamp: seconds since 00:00h January 1, 1904 + // UNIX timestamp: seconds since 00:00h January 1, 1970 + return self::CastAsInt($macdate - 2082844800); + } + + /** + * @param string $rawdata + * + * @return float + */ + public static function FixedPoint8_8($rawdata) { + return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); + } + + /** + * @param string $rawdata + * + * @return float + */ + public static function FixedPoint16_16($rawdata) { + return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); + } + + /** + * @param string $rawdata + * + * @return float + */ + public static function FixedPoint2_30($rawdata) { + $binarystring = self::BigEndian2Bin($rawdata); + return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); + } + + + /** + * @param string $ArrayPath + * @param string $Separator + * @param mixed $Value + * + * @return array + */ + public static function CreateDeepArray($ArrayPath, $Separator, $Value) { + // assigns $Value to a nested array path: + // $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt') + // is the same as: + // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); + // or + // $foo['path']['to']['my'] = 'file.txt'; + $ArrayPath = ltrim($ArrayPath, $Separator); + $ReturnedArray = array(); + if (($pos = strpos($ArrayPath, $Separator)) !== false) { + $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); + } else { + $ReturnedArray[$ArrayPath] = $Value; + } + return $ReturnedArray; + } + + /** + * @param array $arraydata + * @param bool $returnkey + * + * @return int|false + */ + public static function array_max($arraydata, $returnkey=false) { + $maxvalue = false; + $maxkey = false; + foreach ($arraydata as $key => $value) { + if (!is_array($value)) { + if (($maxvalue === false) || ($value > $maxvalue)) { + $maxvalue = $value; + $maxkey = $key; + } + } + } + return ($returnkey ? $maxkey : $maxvalue); + } + + /** + * @param array $arraydata + * @param bool $returnkey + * + * @return int|false + */ + public static function array_min($arraydata, $returnkey=false) { + $minvalue = false; + $minkey = false; + foreach ($arraydata as $key => $value) { + if (!is_array($value)) { + if (($minvalue === false) || ($value < $minvalue)) { + $minvalue = $value; + $minkey = $key; + } + } + } + return ($returnkey ? $minkey : $minvalue); + } + + /** + * @param string $XMLstring + * + * @return array|false + */ + public static function XML2array($XMLstring) { + if (function_exists('simplexml_load_string') && function_exists('libxml_disable_entity_loader')) { + // http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html + // https://core.trac.wordpress.org/changeset/29378 + // This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading is + // disabled by default, but is still needed when LIBXML_NOENT is used. + $loader = @libxml_disable_entity_loader(true); + $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', GETID3_LIBXML_OPTIONS); + $return = self::SimpleXMLelement2array($XMLobject); + @libxml_disable_entity_loader($loader); + return $return; + } + return false; + } + + /** + * @param SimpleXMLElement|array|mixed $XMLobject + * + * @return mixed + */ + public static function SimpleXMLelement2array($XMLobject) { + if (!is_object($XMLobject) && !is_array($XMLobject)) { + return $XMLobject; + } + $XMLarray = $XMLobject instanceof SimpleXMLElement ? get_object_vars($XMLobject) : $XMLobject; + foreach ($XMLarray as $key => $value) { + $XMLarray[$key] = self::SimpleXMLelement2array($value); + } + return $XMLarray; + } + + /** + * Returns checksum for a file from starting position to absolute end position. + * + * @param string $file + * @param int $offset + * @param int $end + * @param string $algorithm + * + * @return string|false + * @throws getid3_exception + */ + public static function hash_data($file, $offset, $end, $algorithm) { + if (!self::intValueSupported($end)) { + return false; + } + if (!in_array($algorithm, array('md5', 'sha1'))) { + throw new getid3_exception('Invalid algorithm ('.$algorithm.') in self::hash_data()'); + } + + $size = $end - $offset; + + $fp = fopen($file, 'rb'); + fseek($fp, $offset); + $ctx = hash_init($algorithm); + while ($size > 0) { + $buffer = fread($fp, min($size, getID3::FREAD_BUFFER_SIZE)); + hash_update($ctx, $buffer); + $size -= getID3::FREAD_BUFFER_SIZE; + } + $hash = hash_final($ctx); + fclose($fp); + + return $hash; + } + + /** + * @param string $filename_source + * @param string $filename_dest + * @param int $offset + * @param int $length + * + * @return bool + * @throws Exception + * + * @deprecated Unused, may be removed in future versions of getID3 + */ + public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { + if (!self::intValueSupported($offset + $length)) { + throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); + } + if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) { + if (($fp_dest = fopen($filename_dest, 'wb'))) { + if (fseek($fp_src, $offset) == 0) { + $byteslefttowrite = $length; + while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) { + $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); + $byteslefttowrite -= $byteswritten; + } + fclose($fp_dest); + return true; + } else { + fclose($fp_src); + throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source); + } + } else { + throw new Exception('failed to create file for writing '.$filename_dest); + } + } else { + throw new Exception('failed to open file for reading '.$filename_source); + } + } + + /** + * @param int $charval + * + * @return string + */ + public static function iconv_fallback_int_utf8($charval) { + if ($charval < 128) { + // 0bbbbbbb + $newcharstring = chr($charval); + } elseif ($charval < 2048) { + // 110bbbbb 10bbbbbb + $newcharstring = chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } elseif ($charval < 65536) { + // 1110bbbb 10bbbbbb 10bbbbbb + $newcharstring = chr(($charval >> 12) | 0xE0); + $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } else { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $newcharstring = chr(($charval >> 18) | 0xF0); + $newcharstring .= chr(($charval >> 12) | 0xC0); + $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } + return $newcharstring; + } + + /** + * ISO-8859-1 => UTF-8 + * + * @param string $string + * @param bool $bom + * + * @return string + */ + public static function iconv_fallback_iso88591_utf8($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xEF\xBB\xBF"; + } + for ($i = 0; $i < strlen($string); $i++) { + $charval = ord($string[$i]); + $newcharstring .= self::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + /** + * ISO-8859-1 => UTF-16BE + * + * @param string $string + * @param bool $bom + * + * @return string + */ + public static function iconv_fallback_iso88591_utf16be($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFE\xFF"; + } + for ($i = 0; $i < strlen($string); $i++) { + $newcharstring .= "\x00".$string[$i]; + } + return $newcharstring; + } + + /** + * ISO-8859-1 => UTF-16LE + * + * @param string $string + * @param bool $bom + * + * @return string + */ + public static function iconv_fallback_iso88591_utf16le($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFF\xFE"; + } + for ($i = 0; $i < strlen($string); $i++) { + $newcharstring .= $string[$i]."\x00"; + } + return $newcharstring; + } + + /** + * ISO-8859-1 => UTF-16LE (BOM) + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_iso88591_utf16($string) { + return self::iconv_fallback_iso88591_utf16le($string, true); + } + + /** + * UTF-8 => ISO-8859-1 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf8_iso88591($string) { + $newcharstring = ''; + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string[$offset]) | 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) & + ((ord($string[($offset + 1)]) & 0x3F) << 12) & + ((ord($string[($offset + 2)]) & 0x3F) << 6) & + (ord($string[($offset + 3)]) & 0x3F); + $offset += 4; + } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) & + ((ord($string[($offset + 1)]) & 0x3F) << 6) & + (ord($string[($offset + 2)]) & 0x3F); + $offset += 3; + } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) & + (ord($string[($offset + 1)]) & 0x3F); + $offset += 2; + } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string[$offset]); + $offset += 1; + } else { + // error? throw some kind of warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + } + return $newcharstring; + } + + /** + * UTF-8 => UTF-16BE + * + * @param string $string + * @param bool $bom + * + * @return string + */ + public static function iconv_fallback_utf8_utf16be($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFE\xFF"; + } + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string[$offset]) | 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) & + ((ord($string[($offset + 1)]) & 0x3F) << 12) & + ((ord($string[($offset + 2)]) & 0x3F) << 6) & + (ord($string[($offset + 3)]) & 0x3F); + $offset += 4; + } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) & + ((ord($string[($offset + 1)]) & 0x3F) << 6) & + (ord($string[($offset + 2)]) & 0x3F); + $offset += 3; + } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) & + (ord($string[($offset + 1)]) & 0x3F); + $offset += 2; + } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string[$offset]); + $offset += 1; + } else { + // error? throw some kind of warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 65536) ? self::BigEndian2String($charval, 2) : "\x00".'?'); + } + } + return $newcharstring; + } + + /** + * UTF-8 => UTF-16LE + * + * @param string $string + * @param bool $bom + * + * @return string + */ + public static function iconv_fallback_utf8_utf16le($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFF\xFE"; + } + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string[$offset]) | 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) & + ((ord($string[($offset + 1)]) & 0x3F) << 12) & + ((ord($string[($offset + 2)]) & 0x3F) << 6) & + (ord($string[($offset + 3)]) & 0x3F); + $offset += 4; + } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) & + ((ord($string[($offset + 1)]) & 0x3F) << 6) & + (ord($string[($offset + 2)]) & 0x3F); + $offset += 3; + } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) & + (ord($string[($offset + 1)]) & 0x3F); + $offset += 2; + } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string[$offset]); + $offset += 1; + } else { + // error? maybe throw some warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 65536) ? self::LittleEndian2String($charval, 2) : '?'."\x00"); + } + } + return $newcharstring; + } + + /** + * UTF-8 => UTF-16LE (BOM) + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf8_utf16($string) { + return self::iconv_fallback_utf8_utf16le($string, true); + } + + /** + * UTF-16BE => UTF-8 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf16be_utf8($string) { + if (substr($string, 0, 2) == "\xFE\xFF") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= self::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + /** + * UTF-16LE => UTF-8 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf16le_utf8($string) { + if (substr($string, 0, 2) == "\xFF\xFE") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= self::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + /** + * UTF-16BE => ISO-8859-1 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf16be_iso88591($string) { + if (substr($string, 0, 2) == "\xFE\xFF") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + return $newcharstring; + } + + /** + * UTF-16LE => ISO-8859-1 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf16le_iso88591($string) { + if (substr($string, 0, 2) == "\xFF\xFE") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + return $newcharstring; + } + + /** + * UTF-16 (BOM) => ISO-8859-1 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf16_iso88591($string) { + $bom = substr($string, 0, 2); + if ($bom == "\xFE\xFF") { + return self::iconv_fallback_utf16be_iso88591(substr($string, 2)); + } elseif ($bom == "\xFF\xFE") { + return self::iconv_fallback_utf16le_iso88591(substr($string, 2)); + } + return $string; + } + + /** + * UTF-16 (BOM) => UTF-8 + * + * @param string $string + * + * @return string + */ + public static function iconv_fallback_utf16_utf8($string) { + $bom = substr($string, 0, 2); + if ($bom == "\xFE\xFF") { + return self::iconv_fallback_utf16be_utf8(substr($string, 2)); + } elseif ($bom == "\xFF\xFE") { + return self::iconv_fallback_utf16le_utf8(substr($string, 2)); + } + return $string; + } + + /** + * @param string $in_charset + * @param string $out_charset + * @param string $string + * + * @return string + * @throws Exception + */ + public static function iconv_fallback($in_charset, $out_charset, $string) { + + if ($in_charset == $out_charset) { + return $string; + } + + // mb_convert_encoding() available + if (function_exists('mb_convert_encoding')) { + if ((strtoupper($in_charset) == 'UTF-16') && (substr($string, 0, 2) != "\xFE\xFF") && (substr($string, 0, 2) != "\xFF\xFE")) { + // if BOM missing, mb_convert_encoding will mishandle the conversion, assume UTF-16BE and prepend appropriate BOM + $string = "\xFF\xFE".$string; + } + if ((strtoupper($in_charset) == 'UTF-16') && (strtoupper($out_charset) == 'UTF-8')) { + if (($string == "\xFF\xFE") || ($string == "\xFE\xFF")) { + // if string consists of only BOM, mb_convert_encoding will return the BOM unmodified + return ''; + } + } + if ($converted_string = @mb_convert_encoding($string, $out_charset, $in_charset)) { + switch ($out_charset) { + case 'ISO-8859-1': + $converted_string = rtrim($converted_string, "\x00"); + break; + } + return $converted_string; + } + return $string; + + // iconv() available + } elseif (function_exists('iconv')) { + if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { + switch ($out_charset) { + case 'ISO-8859-1': + $converted_string = rtrim($converted_string, "\x00"); + break; + } + return $converted_string; + } + + // iconv() may sometimes fail with "illegal character in input string" error message + // and return an empty string, but returning the unconverted string is more useful + return $string; + } + + + // neither mb_convert_encoding or iconv() is available + static $ConversionFunctionList = array(); + if (empty($ConversionFunctionList)) { + $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; + $ConversionFunctionList['ISO-8859-1']['UTF-16'] = 'iconv_fallback_iso88591_utf16'; + $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be'; + $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le'; + $ConversionFunctionList['UTF-8']['ISO-8859-1'] = 'iconv_fallback_utf8_iso88591'; + $ConversionFunctionList['UTF-8']['UTF-16'] = 'iconv_fallback_utf8_utf16'; + $ConversionFunctionList['UTF-8']['UTF-16BE'] = 'iconv_fallback_utf8_utf16be'; + $ConversionFunctionList['UTF-8']['UTF-16LE'] = 'iconv_fallback_utf8_utf16le'; + $ConversionFunctionList['UTF-16']['ISO-8859-1'] = 'iconv_fallback_utf16_iso88591'; + $ConversionFunctionList['UTF-16']['UTF-8'] = 'iconv_fallback_utf16_utf8'; + $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591'; + $ConversionFunctionList['UTF-16LE']['UTF-8'] = 'iconv_fallback_utf16le_utf8'; + $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591'; + $ConversionFunctionList['UTF-16BE']['UTF-8'] = 'iconv_fallback_utf16be_utf8'; + } + if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) { + $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; + return self::$ConversionFunction($string); + } + throw new Exception('PHP does not has mb_convert_encoding() or iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); + } + + /** + * @param mixed $data + * @param string $charset + * + * @return mixed + */ + public static function recursiveMultiByteCharString2HTML($data, $charset='ISO-8859-1') { + if (is_string($data)) { + return self::MultiByteCharString2HTML($data, $charset); + } elseif (is_array($data)) { + $return_data = array(); + foreach ($data as $key => $value) { + $return_data[$key] = self::recursiveMultiByteCharString2HTML($value, $charset); + } + return $return_data; + } + // integer, float, objects, resources, etc + return $data; + } + + /** + * @param string|int|float $string + * @param string $charset + * + * @return string + */ + public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { + $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string + $HTMLstring = ''; + + switch (strtolower($charset)) { + case '1251': + case '1252': + case '866': + case '932': + case '936': + case '950': + case 'big5': + case 'big5-hkscs': + case 'cp1251': + case 'cp1252': + case 'cp866': + case 'euc-jp': + case 'eucjp': + case 'gb2312': + case 'ibm866': + case 'iso-8859-1': + case 'iso-8859-15': + case 'iso8859-1': + case 'iso8859-15': + case 'koi8-r': + case 'koi8-ru': + case 'koi8r': + case 'shift_jis': + case 'sjis': + case 'win-1251': + case 'windows-1251': + case 'windows-1252': + $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); + break; + + case 'utf-8': + $strlen = strlen($string); + for ($i = 0; $i < $strlen; $i++) { + $char_ord_val = ord($string[$i]); + $charval = 0; + if ($char_ord_val < 0x80) { + $charval = $char_ord_val; + } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F && $i+3 < $strlen) { + $charval = (($char_ord_val & 0x07) << 18); + $charval += ((ord($string[++$i]) & 0x3F) << 12); + $charval += ((ord($string[++$i]) & 0x3F) << 6); + $charval += (ord($string[++$i]) & 0x3F); + } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07 && $i+2 < $strlen) { + $charval = (($char_ord_val & 0x0F) << 12); + $charval += ((ord($string[++$i]) & 0x3F) << 6); + $charval += (ord($string[++$i]) & 0x3F); + } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03 && $i+1 < $strlen) { + $charval = (($char_ord_val & 0x1F) << 6); + $charval += (ord($string[++$i]) & 0x3F); + } + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= htmlentities(chr($charval)); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + case 'utf-16le': + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + case 'utf-16be': + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::BigEndian2Int(substr($string, $i, 2)); + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + default: + $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()'; + break; + } + return $HTMLstring; + } + + /** + * @param int $namecode + * + * @return string + */ + public static function RGADnameLookup($namecode) { + static $RGADname = array(); + if (empty($RGADname)) { + $RGADname[0] = 'not set'; + $RGADname[1] = 'Track Gain Adjustment'; + $RGADname[2] = 'Album Gain Adjustment'; + } + + return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : ''); + } + + /** + * @param int $originatorcode + * + * @return string + */ + public static function RGADoriginatorLookup($originatorcode) { + static $RGADoriginator = array(); + if (empty($RGADoriginator)) { + $RGADoriginator[0] = 'unspecified'; + $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer'; + $RGADoriginator[2] = 'set by user'; + $RGADoriginator[3] = 'determined automatically'; + } + + return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : ''); + } + + /** + * @param int $rawadjustment + * @param int $signbit + * + * @return float + */ + public static function RGADadjustmentLookup($rawadjustment, $signbit) { + $adjustment = (float) $rawadjustment / 10; + if ($signbit == 1) { + $adjustment *= -1; + } + return $adjustment; + } + + /** + * @param int $namecode + * @param int $originatorcode + * @param int $replaygain + * + * @return string + */ + public static function RGADgainString($namecode, $originatorcode, $replaygain) { + if ($replaygain < 0) { + $signbit = '1'; + } else { + $signbit = '0'; + } + $storedreplaygain = intval(round($replaygain * 10)); + $gainstring = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT); + $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT); + $gainstring .= $signbit; + $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT); + + return $gainstring; + } + + /** + * @param float $amplitude + * + * @return float + */ + public static function RGADamplitude2dB($amplitude) { + return 20 * log10($amplitude); + } + + /** + * @param string $imgData + * @param array $imageinfo + * + * @return array|false + */ + public static function GetDataImageSize($imgData, &$imageinfo=array()) { + if (PHP_VERSION_ID >= 50400) { + $GetDataImageSize = @getimagesizefromstring($imgData, $imageinfo); + if ($GetDataImageSize === false || !isset($GetDataImageSize[0], $GetDataImageSize[1])) { + return false; + } + $GetDataImageSize['height'] = $GetDataImageSize[0]; + $GetDataImageSize['width'] = $GetDataImageSize[1]; + return $GetDataImageSize; + } + static $tempdir = ''; + if (empty($tempdir)) { + if (function_exists('sys_get_temp_dir')) { + $tempdir = sys_get_temp_dir(); // https://github.com/JamesHeinrich/getID3/issues/52 + } + + // yes this is ugly, feel free to suggest a better way + if (include_once(dirname(__FILE__).'/getid3.php')) { + $getid3_temp = new getID3(); + if ($getid3_temp_tempdir = $getid3_temp->tempdir) { + $tempdir = $getid3_temp_tempdir; + } + unset($getid3_temp, $getid3_temp_tempdir); + } + } + $GetDataImageSize = false; + if ($tempfilename = tempnam($tempdir, 'gI3')) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { + fwrite($tmp, $imgData); + fclose($tmp); + $GetDataImageSize = @getimagesize($tempfilename, $imageinfo); + if (($GetDataImageSize === false) || !isset($GetDataImageSize[0]) || !isset($GetDataImageSize[1])) { + return false; + } + $GetDataImageSize['height'] = $GetDataImageSize[0]; + $GetDataImageSize['width'] = $GetDataImageSize[1]; + } + unlink($tempfilename); + } + return $GetDataImageSize; + } + + /** + * @param string $mime_type + * + * @return string + */ + public static function ImageExtFromMime($mime_type) { + // temporary way, works OK for now, but should be reworked in the future + return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type); + } + + /** + * @param array $ThisFileInfo + * @param bool $option_tags_html default true (just as in the main getID3 class) + * + * @return bool + */ + public static function CopyTagsToComments(&$ThisFileInfo, $option_tags_html=true) { + // Copy all entries from ['tags'] into common ['comments'] + if (!empty($ThisFileInfo['tags'])) { + + // Some tag types can only support limited character sets and may contain data in non-standard encoding (usually ID3v1) + // and/or poorly-transliterated tag values that are also in tag formats that do support full-range character sets + // To make the output more user-friendly, process the potentially-problematic tag formats last to enhance the chance that + // the first entries in [comments] are the most correct and the "bad" ones (if any) come later. + // https://github.com/JamesHeinrich/getID3/issues/338 + $processLastTagTypes = array('id3v1','riff'); + foreach ($processLastTagTypes as $processLastTagType) { + if (isset($ThisFileInfo['tags'][$processLastTagType])) { + // bubble ID3v1 to the end, if present to aid in detecting bad ID3v1 encodings + $temp = $ThisFileInfo['tags'][$processLastTagType]; + unset($ThisFileInfo['tags'][$processLastTagType]); + $ThisFileInfo['tags'][$processLastTagType] = $temp; + unset($temp); + } + } + foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + foreach ($tagdata as $key => $value) { + if (!empty($value)) { + if (empty($ThisFileInfo['comments'][$tagname])) { + + // fall through and append value + + } elseif ($tagtype == 'id3v1') { + + $newvaluelength = strlen(trim($value)); + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { + $oldvaluelength = strlen(trim($existingvalue)); + if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) { + // new value is identical but shorter-than (or equal-length to) one already in comments - skip + break 2; + } + + if (function_exists('mb_convert_encoding')) { + if (trim($value) == trim(substr(mb_convert_encoding($existingvalue, $ThisFileInfo['id3v1']['encoding'], $ThisFileInfo['encoding']), 0, 30))) { + // value stored in ID3v1 appears to be probably the multibyte value transliterated (badly) into ISO-8859-1 in ID3v1. + // As an example, Foobar2000 will do this if you tag a file with Chinese or Arabic or Cyrillic or something that doesn't fit into ISO-8859-1 the ID3v1 will consist of mostly "?" characters, one per multibyte unrepresentable character + break 2; + } + } + } + + } elseif (!is_array($value)) { + + $newvaluelength = strlen(trim($value)); + $newvaluelengthMB = mb_strlen(trim($value)); + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { + $oldvaluelength = strlen(trim($existingvalue)); + $oldvaluelengthMB = mb_strlen(trim($existingvalue)); + if (($newvaluelengthMB == $oldvaluelengthMB) && ($existingvalue == getid3_lib::iconv_fallback('UTF-8', 'ASCII', $value))) { + // https://github.com/JamesHeinrich/getID3/issues/338 + // check for tags containing extended characters that may have been forced into limited-character storage (e.g. UTF8 values into ASCII) + // which will usually display unrepresentable characters as "?" + $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); + break; + } + if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { + $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); + break; + } + } + + } + if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { + $value = (is_string($value) ? trim($value) : $value); + if (!is_int($key) && !ctype_digit($key)) { + $ThisFileInfo['comments'][$tagname][$key] = $value; + } else { + if (!isset($ThisFileInfo['comments'][$tagname])) { + $ThisFileInfo['comments'][$tagname] = array($value); + } else { + $ThisFileInfo['comments'][$tagname][] = $value; + } + } + } + } + } + } + } + + // attempt to standardize spelling of returned keys + if (!empty($ThisFileInfo['comments'])) { + $StandardizeFieldNames = array( + 'tracknumber' => 'track_number', + 'track' => 'track_number', + ); + foreach ($StandardizeFieldNames as $badkey => $goodkey) { + if (array_key_exists($badkey, $ThisFileInfo['comments']) && !array_key_exists($goodkey, $ThisFileInfo['comments'])) { + $ThisFileInfo['comments'][$goodkey] = $ThisFileInfo['comments'][$badkey]; + unset($ThisFileInfo['comments'][$badkey]); + } + } + } + + if ($option_tags_html) { + // Copy ['comments'] to ['comments_html'] + if (!empty($ThisFileInfo['comments'])) { + foreach ($ThisFileInfo['comments'] as $field => $values) { + if ($field == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + continue; + } + foreach ($values as $index => $value) { + if (is_array($value)) { + $ThisFileInfo['comments_html'][$field][$index] = $value; + } else { + $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); + } + } + } + } + } + + } + return true; + } + + /** + * @param string $key + * @param int $begin + * @param int $end + * @param string $file + * @param string $name + * + * @return string + */ + public static function EmbeddedLookup($key, $begin, $end, $file, $name) { + + // Cached + static $cache; + if (isset($cache[$file][$name])) { + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); + } + + // Init + $keylength = strlen($key); + $line_count = $end - $begin - 7; + + // Open php file + $fp = fopen($file, 'r'); + + // Discard $begin lines + for ($i = 0; $i < ($begin + 3); $i++) { + fgets($fp, 1024); + } + + // Loop thru line + while (0 < $line_count--) { + + // Read line + $line = ltrim(fgets($fp, 1024), "\t "); + + // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key + //$keycheck = substr($line, 0, $keylength); + //if ($key == $keycheck) { + // $cache[$file][$name][$keycheck] = substr($line, $keylength + 1); + // break; + //} + + // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key + //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); + $explodedLine = explode("\t", $line, 2); + $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : ''); + $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : ''); + $cache[$file][$name][$ThisKey] = trim($ThisValue); + } + + // Close and return + fclose($fp); + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); + } + + /** + * @param string $filename + * @param string $sourcefile + * @param bool $DieOnFailure + * + * @return bool + * @throws Exception + */ + public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { + global $GETID3_ERRORARRAY; + + if (file_exists($filename)) { + if (include_once($filename)) { + return true; + } else { + $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; + } + } else { + $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; + } + if ($DieOnFailure) { + throw new Exception($diemessage); + } else { + $GETID3_ERRORARRAY[] = $diemessage; + } + return false; + } + + /** + * @param string $string + * + * @return string + */ + public static function trimNullByte($string) { + return trim($string, "\x00"); + } + + /** + * @param string $path + * + * @return float|bool + */ + public static function getFileSizeSyscall($path) { + $commandline = null; + $filesize = false; + + if (GETID3_OS_ISWINDOWS) { + if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini: + $filesystem = new COM('Scripting.FileSystemObject'); + $file = $filesystem->GetFile($path); + $filesize = $file->Size(); + unset($filesystem, $file); + } else { + $commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI'; + } + } else { + $commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\''; + } + if (isset($commandline)) { + $output = trim(`$commandline`); + if (ctype_digit($output)) { + $filesize = (float) $output; + } + } + return $filesize; + } + + /** + * @param string $filename + * + * @return string|false + */ + public static function truepath($filename) { + // 2017-11-08: this could use some improvement, patches welcome + if (preg_match('#^(\\\\\\\\|//)[a-z0-9]#i', $filename, $matches)) { + // PHP's built-in realpath function does not work on UNC Windows shares + $goodpath = array(); + foreach (explode('/', str_replace('\\', '/', $filename)) as $part) { + if ($part == '.') { + continue; + } + if ($part == '..') { + if (count($goodpath)) { + array_pop($goodpath); + } else { + // cannot step above this level, already at top level + return false; + } + } else { + $goodpath[] = $part; + } + } + return implode(DIRECTORY_SEPARATOR, $goodpath); + } + return realpath($filename); + } + + /** + * Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268) + * + * @param string $path A path. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * + * @return string + */ + public static function mb_basename($path, $suffix = '') { + $splited = preg_split('#/#', rtrim($path, '/ ')); + return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1); + } + +} diff --git a/config/getid3/getid3.php b/config/getid3/getid3.php new file mode 100644 index 000000000..41ef6408b --- /dev/null +++ b/config/getid3/getid3.php @@ -0,0 +1,2494 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// // +// Please see readme.txt for more information // +// /// +///////////////////////////////////////////////////////////////// + +// define a constant rather than looking up every time it is needed +if (!defined('GETID3_OS_ISWINDOWS')) { + define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); +} +// Get base path of getID3() - ONCE +if (!defined('GETID3_INCLUDEPATH')) { + define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); +} +if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE + define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8)); +} + +/* +https://www.getid3.org/phpBB3/viewtopic.php?t=2114 +If you are running into a the problem where filenames with special characters are being handled +incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed, +and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line: +*/ +//setlocale(LC_CTYPE, 'en_US.UTF-8'); + +// attempt to define temp dir as something flexible but reliable +$temp_dir = ini_get('upload_tmp_dir'); +if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { + $temp_dir = ''; +} +if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1 + // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts + $temp_dir = sys_get_temp_dir(); +} +$temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10 +$open_basedir = ini_get('open_basedir'); +if ($open_basedir) { + // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" + $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); + $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); + if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { + $temp_dir .= DIRECTORY_SEPARATOR; + } + $found_valid_tempdir = false; + $open_basedirs = explode(PATH_SEPARATOR, $open_basedir); + foreach ($open_basedirs as $basedir) { + if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { + $basedir .= DIRECTORY_SEPARATOR; + } + if (strpos($temp_dir, $basedir) === 0) { + $found_valid_tempdir = true; + break; + } + } + if (!$found_valid_tempdir) { + $temp_dir = ''; + } + unset($open_basedirs, $found_valid_tempdir, $basedir); +} +if (!$temp_dir) { + $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir +} +// $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system +if (!defined('GETID3_TEMP_DIR')) { + define('GETID3_TEMP_DIR', $temp_dir); +} +unset($open_basedir, $temp_dir); + +// End: Defines + + +class getID3 +{ + /* + * Settings + */ + + /** + * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE + * + * @var string + */ + public $encoding = 'UTF-8'; + + /** + * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' + * + * @var string + */ + public $encoding_id3v1 = 'ISO-8859-1'; + + /** + * ID3v1 should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'Windows-1251' or 'KOI8-R'. If true attempt to detect these encodings, but may return incorrect values for some tags actually in ISO-8859-1 encoding + * + * @var bool + */ + public $encoding_id3v1_autodetect = false; + + /* + * Optional tag checks - disable for speed. + */ + + /** + * Read and process ID3v1 tags + * + * @var bool + */ + public $option_tag_id3v1 = true; + + /** + * Read and process ID3v2 tags + * + * @var bool + */ + public $option_tag_id3v2 = true; + + /** + * Read and process Lyrics3 tags + * + * @var bool + */ + public $option_tag_lyrics3 = true; + + /** + * Read and process APE tags + * + * @var bool + */ + public $option_tag_apetag = true; + + /** + * Copy tags to root key 'tags' and encode to $this->encoding + * + * @var bool + */ + public $option_tags_process = true; + + /** + * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + * + * @var bool + */ + public $option_tags_html = true; + + /* + * Optional tag/comment calculations + */ + + /** + * Calculate additional info such as bitrate, channelmode etc + * + * @var bool + */ + public $option_extra_info = true; + + /* + * Optional handling of embedded attachments (e.g. images) + */ + + /** + * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility + * + * @var bool|string + */ + public $option_save_attachments = true; + + /* + * Optional calculations + */ + + /** + * Get MD5 sum of data part - slow + * + * @var bool + */ + public $option_md5_data = false; + + /** + * Use MD5 of source file if available - only FLAC and OptimFROG + * + * @var bool + */ + public $option_md5_data_source = false; + + /** + * Get SHA1 sum of data part - slow + * + * @var bool + */ + public $option_sha1_data = false; + + /** + * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on + * PHP_INT_MAX) + * + * @var bool|null + */ + public $option_max_2gb_check; + + /** + * Read buffer size in bytes + * + * @var int + */ + public $option_fread_buffer_size = 32768; + + + + // module-specific options + + /** archive.rar + * if true use PHP RarArchive extension, if false (non-extension parsing not yet written in getID3) + * + * @var bool + */ + public $options_archive_rar_use_php_rar_extension = true; + + /** archive.gzip + * Optional file list - disable for speed. + * Decode gzipped files, if possible, and parse recursively (.tar.gz for example). + * + * @var bool + */ + public $options_archive_gzip_parse_contents = false; + + /** audio.midi + * if false only parse most basic information, much faster for some files but may be inaccurate + * + * @var bool + */ + public $options_audio_midi_scanwholefile = true; + + /** audio.mp3 + * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, + * unrecommended, but may provide data from otherwise-unusable files. + * + * @var bool + */ + public $options_audio_mp3_allow_bruteforce = false; + + /** audio.mp3 + * number of frames to scan to determine if MPEG-audio sequence is valid + * Lower this number to 5-20 for faster scanning + * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams + * + * @var int + */ + public $options_audio_mp3_mp3_valid_check_frames = 50; + + /** audio.wavpack + * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK, + * significantly faster for very large files but other data may be missed + * + * @var bool + */ + public $options_audio_wavpack_quick_parsing = false; + + /** audio-video.flv + * Break out of the loop if too many frames have been scanned; only scan this + * many if meta frame does not contain useful duration. + * + * @var int + */ + public $options_audiovideo_flv_max_frames = 100000; + + /** audio-video.matroska + * If true, do not return information about CLUSTER chunks, since there's a lot of them + * and they're not usually useful [default: TRUE]. + * + * @var bool + */ + public $options_audiovideo_matroska_hide_clusters = true; + + /** audio-video.matroska + * True to parse the whole file, not only header [default: FALSE]. + * + * @var bool + */ + public $options_audiovideo_matroska_parse_whole_file = false; + + /** audio-video.quicktime + * return all parsed data from all atoms if true, otherwise just returned parsed metadata + * + * @var bool + */ + public $options_audiovideo_quicktime_ReturnAtomData = false; + + /** audio-video.quicktime + * return all parsed data from all atoms if true, otherwise just returned parsed metadata + * + * @var bool + */ + public $options_audiovideo_quicktime_ParseAllPossibleAtoms = false; + + /** audio-video.swf + * return all parsed tags if true, otherwise do not return tags not parsed by getID3 + * + * @var bool + */ + public $options_audiovideo_swf_ReturnAllTagData = false; + + /** graphic.bmp + * return BMP palette + * + * @var bool + */ + public $options_graphic_bmp_ExtractPalette = false; + + /** graphic.bmp + * return image data + * + * @var bool + */ + public $options_graphic_bmp_ExtractData = false; + + /** graphic.png + * If data chunk is larger than this do not read it completely (getID3 only needs the first + * few dozen bytes for parsing). + * + * @var int + */ + public $options_graphic_png_max_data_bytes = 10000000; + + /** misc.pdf + * return full details of PDF Cross-Reference Table (XREF) + * + * @var bool + */ + public $options_misc_pdf_returnXREF = false; + + /** misc.torrent + * Assume all .torrent files are less than 1MB and just read entire thing into memory for easy processing. + * Override this value if you need to process files larger than 1MB + * + * @var int + */ + public $options_misc_torrent_max_torrent_filesize = 1048576; + + + + // Public variables + + /** + * Filename of file being analysed. + * + * @var string + */ + public $filename; + + /** + * Filepointer to file being analysed. + * + * @var resource + */ + public $fp; + + /** + * Result array. + * + * @var array + */ + public $info; + + /** + * @var string + */ + public $tempdir = GETID3_TEMP_DIR; + + /** + * @var int + */ + public $memory_limit = 0; + + /** + * @var string + */ + protected $startup_error = ''; + + /** + * @var string + */ + protected $startup_warning = ''; + + const VERSION = '1.9.23-202310190849'; + const FREAD_BUFFER_SIZE = 32768; + + const ATTACHMENTS_NONE = false; + const ATTACHMENTS_INLINE = true; + + /** + * @throws getid3_exception + */ + public function __construct() { + + // Check for PHP version + $required_php_version = '5.3.0'; + if (version_compare(PHP_VERSION, $required_php_version, '<')) { + $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n"; + return; + } + + // Check memory + $memoryLimit = ini_get('memory_limit'); + if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) { + // could be stored as "16M" rather than 16777216 for example + $memoryLimit = $matches[1] * 1048576; + } elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 + // could be stored as "2G" rather than 2147483648 for example + $memoryLimit = $matches[1] * 1073741824; + } + $this->memory_limit = $memoryLimit; + + if ($this->memory_limit <= 0) { + // memory limits probably disabled + } elseif ($this->memory_limit <= 4194304) { + $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n"; + } elseif ($this->memory_limit <= 12582912) { + $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n"; + } + + // Check safe_mode off + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); + } + + if (($mbstring_func_overload = (int) ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) { + // http://php.net/manual/en/mbstring.overload.php + // "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions" + // getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those. + $this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n"; + } + + // check for magic quotes in PHP < 5.4.0 (when these options were removed and getters always return false) + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + // Check for magic_quotes_runtime + if (function_exists('get_magic_quotes_runtime')) { + if (get_magic_quotes_runtime()) { // @phpstan-ignore-line + $this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n"; + } + } + // Check for magic_quotes_gpc + if (function_exists('get_magic_quotes_gpc')) { + if (get_magic_quotes_gpc()) { // @phpstan-ignore-line + $this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n"; + } + } + } + + // Load support library + if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { + $this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n"; + } + + if ($this->option_max_2gb_check === null) { + $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); + } + + + // Needed for Windows only: + // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC + // as well as other helper functions such as head, etc + // This path cannot contain spaces, but the below code will attempt to get the + // 8.3-equivalent path automatically + // IMPORTANT: This path must include the trailing slash + if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { + + $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path + + if (!is_dir($helperappsdir)) { + $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n"; + } elseif (strpos(realpath($helperappsdir), ' ') !== false) { + $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); + $path_so_far = array(); + foreach ($DirPieces as $key => $value) { + if (strpos($value, ' ') !== false) { + if (!empty($path_so_far)) { + $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); + $dir_listing = `$commandline`; + $lines = explode("\n", $dir_listing); + foreach ($lines as $line) { + $line = trim($line); + if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { + list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; + if ((strtoupper($filesize) == '') && (strtolower($filename) == strtolower($value))) { + $value = $shortname; + } + } + } + } else { + $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n"; + } + } + $path_so_far[] = $value; + } + $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); + } + define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); + } + + if (!empty($this->startup_error)) { + echo $this->startup_error; + throw new getid3_exception($this->startup_error); + } + } + + /** + * @return string + */ + public function version() { + return self::VERSION; + } + + /** + * @return int + */ + public function fread_buffer_size() { + return $this->option_fread_buffer_size; + } + + /** + * @param array $optArray + * + * @return bool + */ + public function setOption($optArray) { + if (!is_array($optArray) || empty($optArray)) { + return false; + } + foreach ($optArray as $opt => $val) { + if (isset($this->$opt) === false) { + continue; + } + $this->$opt = $val; + } + return true; + } + + /** + * @param string $filename + * @param int $filesize + * @param resource $fp + * + * @return bool + * + * @throws getid3_exception + */ + public function openfile($filename, $filesize=null, $fp=null) { + try { + if (!empty($this->startup_error)) { + throw new getid3_exception($this->startup_error); + } + if (!empty($this->startup_warning)) { + foreach (explode("\n", $this->startup_warning) as $startup_warning) { + $this->warning($startup_warning); + } + } + + // init result array and set parameters + $this->filename = $filename; + $this->info = array(); + $this->info['GETID3_VERSION'] = $this->version(); + $this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false); + + // remote files not supported + if (preg_match('#^(ht|f)tps?://#', $filename)) { + throw new getid3_exception('Remote files are not supported - please copy the file locally first'); + } + + $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); + //$filename = preg_replace('#(?fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720 + if (($fp != null) && ((get_resource_type($fp) == 'file') || (get_resource_type($fp) == 'stream'))) { + $this->fp = $fp; + } elseif ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { + // great + } else { + $errormessagelist = array(); + if (!is_readable($filename)) { + $errormessagelist[] = '!is_readable'; + } + if (!is_file($filename)) { + $errormessagelist[] = '!is_file'; + } + if (!file_exists($filename)) { + $errormessagelist[] = '!file_exists'; + } + if (empty($errormessagelist)) { + $errormessagelist[] = 'fopen failed'; + } + throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')'); + } + + $this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename)); + // set redundant parameters - might be needed in some include file + // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion + $filename = str_replace('\\', '/', $filename); + $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); + $this->info['filename'] = getid3_lib::mb_basename($filename); + $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; + + // set more parameters + $this->info['avdataoffset'] = 0; + $this->info['avdataend'] = $this->info['filesize']; + $this->info['fileformat'] = ''; // filled in later + $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used + $this->info['video']['dataformat'] = ''; // filled in later, unset if not used + $this->info['tags'] = array(); // filled in later, unset if not used + $this->info['error'] = array(); // filled in later, unset if not used + $this->info['warning'] = array(); // filled in later, unset if not used + $this->info['comments'] = array(); // filled in later, unset if not used + $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired + + // option_max_2gb_check + if ($this->option_max_2gb_check) { + // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) + // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize + // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer + $fseek = fseek($this->fp, 0, SEEK_END); + if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || + ($this->info['filesize'] < 0) || + (ftell($this->fp) < 0)) { + $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); + + if ($real_filesize === false) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.'); + } elseif (getid3_lib::intValueSupported($real_filesize)) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB, please report to info@getid3.org'); + } + $this->info['filesize'] = $real_filesize; + $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB) and is not properly supported by PHP.'); + } + } + + return true; + + } catch (Exception $e) { + $this->error($e->getMessage()); + } + return false; + } + + /** + * analyze file + * + * @param string $filename + * @param int $filesize + * @param string $original_filename + * @param resource $fp + * + * @return array + */ + public function analyze($filename, $filesize=null, $original_filename='', $fp=null) { + try { + if (!$this->openfile($filename, $filesize, $fp)) { + return $this->info; + } + + // Handle tags + foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + $option_tag = 'option_tag_'.$tag_name; + if ($this->$option_tag) { + $this->include_module('tag.'.$tag_name); + try { + $tag_class = 'getid3_'.$tag_name; + $tag = new $tag_class($this); + $tag->Analyze(); + } + catch (getid3_exception $e) { + throw $e; + } + } + } + if (isset($this->info['id3v2']['tag_offset_start'])) { + $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); + } + foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + if (isset($this->info[$tag_key]['tag_offset_start'])) { + $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); + } + } + + // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier + if (!$this->option_tag_id3v2) { + fseek($this->fp, 0); + $header = fread($this->fp, 10); + if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { + $this->info['id3v2']['header'] = true; + $this->info['id3v2']['majorversion'] = ord($header[3]); + $this->info['id3v2']['minorversion'] = ord($header[4]); + $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + } + } + + // read 32 kb file data + fseek($this->fp, $this->info['avdataoffset']); + $formattest = fread($this->fp, 32774); + + // determine format + $determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename)); + + // unable to determine file format + if (!$determined_format) { + fclose($this->fp); + return $this->error('unable to determine file format'); + } + + // check for illegal ID3 tags + if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { + if ($determined_format['fail_id3'] === 'ERROR') { + fclose($this->fp); + return $this->error('ID3 tags not allowed on this file type.'); + } elseif ($determined_format['fail_id3'] === 'WARNING') { + $this->warning('ID3 tags not allowed on this file type.'); + } + } + + // check for illegal APE tags + if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { + if ($determined_format['fail_ape'] === 'ERROR') { + fclose($this->fp); + return $this->error('APE tags not allowed on this file type.'); + } elseif ($determined_format['fail_ape'] === 'WARNING') { + $this->warning('APE tags not allowed on this file type.'); + } + } + + // set mime type + $this->info['mime_type'] = $determined_format['mime_type']; + + // supported format signature pattern detected, but module deleted + if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { + fclose($this->fp); + return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); + } + + // module requires mb_convert_encoding/iconv support + // Check encoding/iconv support + if (!empty($determined_format['iconv_req']) && !function_exists('mb_convert_encoding') && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { + $errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; + if (GETID3_OS_ISWINDOWS) { + $errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32'; + } else { + $errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch'; + } + return $this->error($errormessage); + } + + // include module + include_once(GETID3_INCLUDEPATH.$determined_format['include']); + + // instantiate module class + $class_name = 'getid3_'.$determined_format['module']; + if (!class_exists($class_name)) { + return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); + } + $class = new $class_name($this); + + // set module-specific options + foreach (get_object_vars($this) as $getid3_object_vars_key => $getid3_object_vars_value) { + if (preg_match('#^options_([^_]+)_([^_]+)_(.+)$#i', $getid3_object_vars_key, $matches)) { + list($dummy, $GOVgroup, $GOVmodule, $GOVsetting) = $matches; + $GOVgroup = (($GOVgroup == 'audiovideo') ? 'audio-video' : $GOVgroup); // variable names can only contain 0-9a-z_ so standardize here + if (($GOVgroup == $determined_format['group']) && ($GOVmodule == $determined_format['module'])) { + $class->$GOVsetting = $getid3_object_vars_value; + } + } + } + + $class->Analyze(); + unset($class); + + // close file + fclose($this->fp); + + // process all tags - copy to 'tags' and convert charsets + if ($this->option_tags_process) { + $this->HandleAllTags(); + } + + // perform more calculations + if ($this->option_extra_info) { + $this->ChannelsBitratePlaytimeCalculations(); + $this->CalculateCompressionRatioVideo(); + $this->CalculateCompressionRatioAudio(); + $this->CalculateReplayGain(); + $this->ProcessAudioStreams(); + } + + // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_md5_data) { + // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too + if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { + $this->getHashdata('md5'); + } + } + + // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_sha1_data) { + $this->getHashdata('sha1'); + } + + // remove undesired keys + $this->CleanUp(); + + } catch (Exception $e) { + $this->error('Caught exception: '.$e->getMessage()); + } + + // return info array + return $this->info; + } + + + /** + * Error handling. + * + * @param string $message + * + * @return array + */ + public function error($message) { + $this->CleanUp(); + if (!isset($this->info['error'])) { + $this->info['error'] = array(); + } + $this->info['error'][] = $message; + return $this->info; + } + + + /** + * Warning handling. + * + * @param string $message + * + * @return bool + */ + public function warning($message) { + $this->info['warning'][] = $message; + return true; + } + + + /** + * @return bool + */ + private function CleanUp() { + + // remove possible empty keys + $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); + foreach ($AVpossibleEmptyKeys as $dummy => $key) { + if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { + unset($this->info['audio'][$key]); + } + if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { + unset($this->info['video'][$key]); + } + } + + // remove empty root keys + if (!empty($this->info)) { + foreach ($this->info as $key => $value) { + if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { + unset($this->info[$key]); + } + } + } + + // remove meaningless entries from unknown-format files + if (empty($this->info['fileformat'])) { + if (isset($this->info['avdataoffset'])) { + unset($this->info['avdataoffset']); + } + if (isset($this->info['avdataend'])) { + unset($this->info['avdataend']); + } + } + + // remove possible duplicated identical entries + if (!empty($this->info['error'])) { + $this->info['error'] = array_values(array_unique($this->info['error'])); + } + if (!empty($this->info['warning'])) { + $this->info['warning'] = array_values(array_unique($this->info['warning'])); + } + + // remove "global variable" type keys + unset($this->info['php_memory_limit']); + + return true; + } + + /** + * Return array containing information about all supported formats. + * + * @return array + */ + public function GetFileFormatArray() { + static $format_info = array(); + if (empty($format_info)) { + $format_info = array( + + // Audio formats + + // AC-3 - audio - Dolby AC-3 / Dolby Digital + 'ac3' => array( + 'pattern' => '^\\x0B\\x77', + 'group' => 'audio', + 'module' => 'ac3', + 'mime_type' => 'audio/ac3', + ), + + // AAC - audio - Advanced Audio Coding (AAC) - ADIF format + 'adif' => array( + 'pattern' => '^ADIF', + 'group' => 'audio', + 'module' => 'aac', + 'mime_type' => 'audio/aac', + 'fail_ape' => 'WARNING', + ), + +/* + // AA - audio - Audible Audiobook + 'aa' => array( + 'pattern' => '^.{4}\\x57\\x90\\x75\\x36', + 'group' => 'audio', + 'module' => 'aa', + 'mime_type' => 'audio/audible', + ), +*/ + // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) + 'adts' => array( + 'pattern' => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]', + 'group' => 'audio', + 'module' => 'aac', + 'mime_type' => 'audio/aac', + 'fail_ape' => 'WARNING', + ), + + + // AU - audio - NeXT/Sun AUdio (AU) + 'au' => array( + 'pattern' => '^\\.snd', + 'group' => 'audio', + 'module' => 'au', + 'mime_type' => 'audio/basic', + ), + + // AMR - audio - Adaptive Multi Rate + 'amr' => array( + 'pattern' => '^\\x23\\x21AMR\\x0A', // #!AMR[0A] + 'group' => 'audio', + 'module' => 'amr', + 'mime_type' => 'audio/amr', + ), + + // AVR - audio - Audio Visual Research + 'avr' => array( + 'pattern' => '^2BIT', + 'group' => 'audio', + 'module' => 'avr', + 'mime_type' => 'application/octet-stream', + ), + + // BONK - audio - Bonk v0.9+ + 'bonk' => array( + 'pattern' => '^\\x00(BONK|INFO|META| ID3)', + 'group' => 'audio', + 'module' => 'bonk', + 'mime_type' => 'audio/xmms-bonk', + ), + + // DSF - audio - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital + 'dsf' => array( + 'pattern' => '^DSD ', // including trailing space: 44 53 44 20 + 'group' => 'audio', + 'module' => 'dsf', + 'mime_type' => 'audio/dsd', + ), + + // DSS - audio - Digital Speech Standard + 'dss' => array( + 'pattern' => '^[\\x02-\\x08]ds[s2]', + 'group' => 'audio', + 'module' => 'dss', + 'mime_type' => 'application/octet-stream', + ), + + // DSDIFF - audio - Direct Stream Digital Interchange File Format + 'dsdiff' => array( + 'pattern' => '^FRM8', + 'group' => 'audio', + 'module' => 'dsdiff', + 'mime_type' => 'audio/dsd', + ), + + // DTS - audio - Dolby Theatre System + 'dts' => array( + 'pattern' => '^\\x7F\\xFE\\x80\\x01', + 'group' => 'audio', + 'module' => 'dts', + 'mime_type' => 'audio/dts', + ), + + // FLAC - audio - Free Lossless Audio Codec + 'flac' => array( + 'pattern' => '^fLaC', + 'group' => 'audio', + 'module' => 'flac', + 'mime_type' => 'audio/flac', + ), + + // LA - audio - Lossless Audio (LA) + 'la' => array( + 'pattern' => '^LA0[2-4]', + 'group' => 'audio', + 'module' => 'la', + 'mime_type' => 'application/octet-stream', + ), + + // LPAC - audio - Lossless Predictive Audio Compression (LPAC) + 'lpac' => array( + 'pattern' => '^LPAC', + 'group' => 'audio', + 'module' => 'lpac', + 'mime_type' => 'application/octet-stream', + ), + + // MIDI - audio - MIDI (Musical Instrument Digital Interface) + 'midi' => array( + 'pattern' => '^MThd', + 'group' => 'audio', + 'module' => 'midi', + 'mime_type' => 'audio/midi', + ), + + // MAC - audio - Monkey's Audio Compressor + 'mac' => array( + 'pattern' => '^MAC ', + 'group' => 'audio', + 'module' => 'monkey', + 'mime_type' => 'audio/x-monkeys-audio', + ), + + + // MOD - audio - MODule (SoundTracker) + 'mod' => array( + //'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available + 'pattern' => '^.{1080}(M\\.K\\.)', + 'group' => 'audio', + 'module' => 'mod', + 'option' => 'mod', + 'mime_type' => 'audio/mod', + ), + + // MOD - audio - MODule (Impulse Tracker) + 'it' => array( + 'pattern' => '^IMPM', + 'group' => 'audio', + 'module' => 'mod', + //'option' => 'it', + 'mime_type' => 'audio/it', + ), + + // MOD - audio - MODule (eXtended Module, various sub-formats) + 'xm' => array( + 'pattern' => '^Extended Module', + 'group' => 'audio', + 'module' => 'mod', + //'option' => 'xm', + 'mime_type' => 'audio/xm', + ), + + // MOD - audio - MODule (ScreamTracker) + 's3m' => array( + 'pattern' => '^.{44}SCRM', + 'group' => 'audio', + 'module' => 'mod', + //'option' => 's3m', + 'mime_type' => 'audio/s3m', + ), + + // MPC - audio - Musepack / MPEGplus + 'mpc' => array( + 'pattern' => '^(MPCK|MP\\+)', + 'group' => 'audio', + 'module' => 'mpc', + 'mime_type' => 'audio/x-musepack', + ), + + // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) + 'mp3' => array( + 'pattern' => '^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\x0B\\x10-\\x1B\\x20-\\x2B\\x30-\\x3B\\x40-\\x4B\\x50-\\x5B\\x60-\\x6B\\x70-\\x7B\\x80-\\x8B\\x90-\\x9B\\xA0-\\xAB\\xB0-\\xBB\\xC0-\\xCB\\xD0-\\xDB\\xE0-\\xEB\\xF0-\\xFB]', + 'group' => 'audio', + 'module' => 'mp3', + 'mime_type' => 'audio/mpeg', + ), + + // OFR - audio - OptimFROG + 'ofr' => array( + 'pattern' => '^(\\*RIFF|OFR)', + 'group' => 'audio', + 'module' => 'optimfrog', + 'mime_type' => 'application/octet-stream', + ), + + // RKAU - audio - RKive AUdio compressor + 'rkau' => array( + 'pattern' => '^RKA', + 'group' => 'audio', + 'module' => 'rkau', + 'mime_type' => 'application/octet-stream', + ), + + // SHN - audio - Shorten + 'shn' => array( + 'pattern' => '^ajkg', + 'group' => 'audio', + 'module' => 'shorten', + 'mime_type' => 'audio/xmms-shn', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TAK - audio - Tom's lossless Audio Kompressor + 'tak' => array( + 'pattern' => '^tBaK', + 'group' => 'audio', + 'module' => 'tak', + 'mime_type' => 'application/octet-stream', + ), + + // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) + 'tta' => array( + 'pattern' => '^TTA', // could also be '^TTA(\\x01|\\x02|\\x03|2|1)' + 'group' => 'audio', + 'module' => 'tta', + 'mime_type' => 'application/octet-stream', + ), + + // VOC - audio - Creative Voice (VOC) + 'voc' => array( + 'pattern' => '^Creative Voice File', + 'group' => 'audio', + 'module' => 'voc', + 'mime_type' => 'audio/voc', + ), + + // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) + 'vqf' => array( + 'pattern' => '^TWIN', + 'group' => 'audio', + 'module' => 'vqf', + 'mime_type' => 'application/octet-stream', + ), + + // WV - audio - WavPack (v4.0+) + 'wv' => array( + 'pattern' => '^wvpk', + 'group' => 'audio', + 'module' => 'wavpack', + 'mime_type' => 'application/octet-stream', + ), + + + // Audio-Video formats + + // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio + 'asf' => array( + 'pattern' => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C', + 'group' => 'audio-video', + 'module' => 'asf', + 'mime_type' => 'video/x-ms-asf', + 'iconv_req' => false, + ), + + // BINK - audio/video - Bink / Smacker + 'bink' => array( + 'pattern' => '^(BIK|SMK)', + 'group' => 'audio-video', + 'module' => 'bink', + 'mime_type' => 'application/octet-stream', + ), + + // FLV - audio/video - FLash Video + 'flv' => array( + 'pattern' => '^FLV[\\x01]', + 'group' => 'audio-video', + 'module' => 'flv', + 'mime_type' => 'video/x-flv', + ), + + // IVF - audio/video - IVF + 'ivf' => array( + 'pattern' => '^DKIF', + 'group' => 'audio-video', + 'module' => 'ivf', + 'mime_type' => 'video/x-ivf', + ), + + // MKAV - audio/video - Mastroka + 'matroska' => array( + 'pattern' => '^\\x1A\\x45\\xDF\\xA3', + 'group' => 'audio-video', + 'module' => 'matroska', + 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska + ), + + // MPEG - audio/video - MPEG (Moving Pictures Experts Group) + 'mpeg' => array( + 'pattern' => '^\\x00\\x00\\x01[\\xB3\\xBA]', + 'group' => 'audio-video', + 'module' => 'mpeg', + 'mime_type' => 'video/mpeg', + ), + + // NSV - audio/video - Nullsoft Streaming Video (NSV) + 'nsv' => array( + 'pattern' => '^NSV[sf]', + 'group' => 'audio-video', + 'module' => 'nsv', + 'mime_type' => 'application/octet-stream', + ), + + // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) + 'ogg' => array( + 'pattern' => '^OggS', + 'group' => 'audio', + 'module' => 'ogg', + 'mime_type' => 'application/ogg', + 'fail_id3' => 'WARNING', + 'fail_ape' => 'WARNING', + ), + + // QT - audio/video - Quicktime + 'quicktime' => array( + 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', + 'group' => 'audio-video', + 'module' => 'quicktime', + 'mime_type' => 'video/quicktime', + ), + + // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF) + 'riff' => array( + 'pattern' => '^(RIFF|SDSS|FORM)', + 'group' => 'audio-video', + 'module' => 'riff', + 'mime_type' => 'audio/wav', + 'fail_ape' => 'WARNING', + ), + + // Real - audio/video - RealAudio, RealVideo + 'real' => array( + 'pattern' => '^\\.(RMF|ra)', + 'group' => 'audio-video', + 'module' => 'real', + 'mime_type' => 'audio/x-realaudio', + ), + + // SWF - audio/video - ShockWave Flash + 'swf' => array( + 'pattern' => '^(F|C)WS', + 'group' => 'audio-video', + 'module' => 'swf', + 'mime_type' => 'application/x-shockwave-flash', + ), + + // TS - audio/video - MPEG-2 Transport Stream + 'ts' => array( + 'pattern' => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern + 'group' => 'audio-video', + 'module' => 'ts', + 'mime_type' => 'video/MP2T', + ), + + // WTV - audio/video - Windows Recorded TV Show + 'wtv' => array( + 'pattern' => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D', + 'group' => 'audio-video', + 'module' => 'wtv', + 'mime_type' => 'video/x-ms-wtv', + ), + + + // Still-Image formats + + // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) + 'bmp' => array( + 'pattern' => '^BM', + 'group' => 'graphic', + 'module' => 'bmp', + 'mime_type' => 'image/bmp', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // GIF - still image - Graphics Interchange Format + 'gif' => array( + 'pattern' => '^GIF', + 'group' => 'graphic', + 'module' => 'gif', + 'mime_type' => 'image/gif', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // JPEG - still image - Joint Photographic Experts Group (JPEG) + 'jpg' => array( + 'pattern' => '^\\xFF\\xD8\\xFF', + 'group' => 'graphic', + 'module' => 'jpg', + 'mime_type' => 'image/jpeg', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // PCD - still image - Kodak Photo CD + 'pcd' => array( + 'pattern' => '^.{2048}PCD_IPI\\x00', + 'group' => 'graphic', + 'module' => 'pcd', + 'mime_type' => 'image/x-photo-cd', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // PNG - still image - Portable Network Graphics (PNG) + 'png' => array( + 'pattern' => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A', + 'group' => 'graphic', + 'module' => 'png', + 'mime_type' => 'image/png', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // SVG - still image - Scalable Vector Graphics (SVG) + 'svg' => array( + 'pattern' => '( 'graphic', + 'module' => 'svg', + 'mime_type' => 'image/svg+xml', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // TIFF - still image - Tagged Information File Format (TIFF) + 'tiff' => array( + 'pattern' => '^(II\\x2A\\x00|MM\\x00\\x2A)', + 'group' => 'graphic', + 'module' => 'tiff', + 'mime_type' => 'image/tiff', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // EFAX - still image - eFax (TIFF derivative) + 'efax' => array( + 'pattern' => '^\\xDC\\xFE', + 'group' => 'graphic', + 'module' => 'efax', + 'mime_type' => 'image/efax', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // Data formats + + // ISO - data - International Standards Organization (ISO) CD-ROM Image + 'iso' => array( + 'pattern' => '^.{32769}CD001', + 'group' => 'misc', + 'module' => 'iso', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + 'iconv_req' => false, + ), + + // HPK - data - HPK compressed data + 'hpk' => array( + 'pattern' => '^BPUL', + 'group' => 'archive', + 'module' => 'hpk', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // RAR - data - RAR compressed data + 'rar' => array( + 'pattern' => '^Rar\\!', + 'group' => 'archive', + 'module' => 'rar', + 'mime_type' => 'application/vnd.rar', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // SZIP - audio/data - SZIP compressed data + 'szip' => array( + 'pattern' => '^SZ\\x0A\\x04', + 'group' => 'archive', + 'module' => 'szip', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TAR - data - TAR compressed data + 'tar' => array( + 'pattern' => '^.{100}[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20\\x00]{12}[0-9\\x20\\x00]{12}', + 'group' => 'archive', + 'module' => 'tar', + 'mime_type' => 'application/x-tar', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // GZIP - data - GZIP compressed data + 'gz' => array( + 'pattern' => '^\\x1F\\x8B\\x08', + 'group' => 'archive', + 'module' => 'gzip', + 'mime_type' => 'application/gzip', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // ZIP - data - ZIP compressed data + 'zip' => array( + 'pattern' => '^PK\\x03\\x04', + 'group' => 'archive', + 'module' => 'zip', + 'mime_type' => 'application/zip', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // XZ - data - XZ compressed data + 'xz' => array( + 'pattern' => '^\\xFD7zXZ\\x00', + 'group' => 'archive', + 'module' => 'xz', + 'mime_type' => 'application/x-xz', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // XZ - data - XZ compressed data + '7zip' => array( + 'pattern' => '^7z\\xBC\\xAF\\x27\\x1C', + 'group' => 'archive', + 'module' => '7zip', + 'mime_type' => 'application/x-7z-compressed', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // Misc other formats + + // PAR2 - data - Parity Volume Set Specification 2.0 + 'par2' => array ( + 'pattern' => '^PAR2\\x00PKT', + 'group' => 'misc', + 'module' => 'par2', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // PDF - data - Portable Document Format + 'pdf' => array( + 'pattern' => '^\\x25PDF', + 'group' => 'misc', + 'module' => 'pdf', + 'mime_type' => 'application/pdf', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // MSOFFICE - data - ZIP compressed data + 'msoffice' => array( + 'pattern' => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document + 'group' => 'misc', + 'module' => 'msoffice', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TORRENT - .torrent + 'torrent' => array( + 'pattern' => '^(d8\\:announce|d7\\:comment)', + 'group' => 'misc', + 'module' => 'torrent', + 'mime_type' => 'application/x-bittorrent', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // CUE - data - CUEsheet (index to single-file disc images) + 'cue' => array( + 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents + 'group' => 'misc', + 'module' => 'cue', + 'mime_type' => 'application/octet-stream', + ), + + ); + } + + return $format_info; + } + + /** + * @param string $filedata + * @param string $filename + * + * @return mixed|false + */ + public function GetFileFormat(&$filedata, $filename='') { + // this function will determine the format of a file based on usually + // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, + // and in the case of ISO CD image, 6 bytes offset 32kb from the start + // of the file). + + // Identify file format - loop through $format_info and detect with reg expr + foreach ($this->GetFileFormatArray() as $format_name => $info) { + // The /s switch on preg_match() forces preg_match() NOT to treat + // newline (0x0A) characters as special chars but do a binary match + if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } + } + + + if (preg_match('#\\.mp[123a]$#i', $filename)) { + // Too many mp3 encoders on the market put garbage in front of mpeg files + // use assume format on these if format detection failed + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['mp3']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } elseif (preg_match('#\\.mp[cp\\+]$#i', $filename) && preg_match('#[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]#s', $filedata)) { + // old-format (SV4-SV6) Musepack header that has a very loose pattern match and could falsely match other data (e.g. corrupt mp3) + // only enable this pattern check if the filename ends in .mpc/mpp/mp+ + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['mpc']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { + // there's not really a useful consistent "magic" at the beginning of .cue files to identify them + // so until I think of something better, just go by filename if all other format checks fail + // and verify there's at least one instance of "TRACK xx AUDIO" in the file + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['cue']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } + + return false; + } + + /** + * Converts array to $encoding charset from $this->encoding. + * + * @param array $array + * @param string $encoding + */ + public function CharConvert(&$array, $encoding) { + + // identical encoding - end here + if ($encoding == $this->encoding) { + return; + } + + // loop thru array + foreach ($array as $key => $value) { + + // go recursive + if (is_array($value)) { + $this->CharConvert($array[$key], $encoding); + } + + // convert string + elseif (is_string($value)) { + $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); + } + } + } + + /** + * @return bool + */ + public function HandleAllTags() { + + // key name => array (tag name, character encoding) + static $tags; + if (empty($tags)) { + $tags = array( + 'asf' => array('asf' , 'UTF-16LE'), + 'midi' => array('midi' , 'ISO-8859-1'), + 'nsv' => array('nsv' , 'ISO-8859-1'), + 'ogg' => array('vorbiscomment' , 'UTF-8'), + 'png' => array('png' , 'UTF-8'), + 'tiff' => array('tiff' , 'ISO-8859-1'), + 'quicktime' => array('quicktime' , 'UTF-8'), + 'real' => array('real' , 'ISO-8859-1'), + 'vqf' => array('vqf' , 'ISO-8859-1'), + 'zip' => array('zip' , 'ISO-8859-1'), + 'riff' => array('riff' , 'ISO-8859-1'), + 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), + 'id3v1' => array('id3v1' , $this->encoding_id3v1), + 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 + 'ape' => array('ape' , 'UTF-8'), + 'cue' => array('cue' , 'ISO-8859-1'), + 'matroska' => array('matroska' , 'UTF-8'), + 'flac' => array('vorbiscomment' , 'UTF-8'), + 'divxtag' => array('divx' , 'ISO-8859-1'), + 'iptc' => array('iptc' , 'ISO-8859-1'), + 'dsdiff' => array('dsdiff' , 'ISO-8859-1'), + ); + } + + // loop through comments array + foreach ($tags as $comment_name => $tagname_encoding_array) { + list($tag_name, $encoding) = $tagname_encoding_array; + + // fill in default encoding type if not already present + if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { + $this->info[$comment_name]['encoding'] = $encoding; + } + + // copy comments if key name set + if (!empty($this->info[$comment_name]['comments'])) { + foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (is_string($value)) { + $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! + } + if (isset($value) && $value !== "") { + if (!is_numeric($key)) { + $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; + } else { + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; + } + } + } + if ($tag_key == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere + unset($this->info[$comment_name]['comments'][$tag_key]); + } + } + + if (!isset($this->info['tags'][$tag_name])) { + // comments are set but contain nothing but empty strings, so skip + continue; + } + + $this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']); // only copy gets converted! + + if ($this->option_tags_html) { + foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { + if ($tag_key == 'picture') { + // Do not to try to convert binary picture data to HTML + // https://github.com/JamesHeinrich/getID3/issues/178 + continue; + } + $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']); + } + } + + } + + } + + // pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere + if (!empty($this->info['tags'])) { + $unset_keys = array('tags', 'tags_html'); + foreach ($this->info['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + if ($tagname == 'picture') { + foreach ($tagdata as $key => $tagarray) { + $this->info['comments']['picture'][] = $tagarray; + if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { + if (isset($this->info['tags'][$tagtype][$tagname][$key])) { + unset($this->info['tags'][$tagtype][$tagname][$key]); + } + if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { + unset($this->info['tags_html'][$tagtype][$tagname][$key]); + } + } + } + } + } + foreach ($unset_keys as $unset_key) { + // remove possible empty keys from (e.g. [tags][id3v2][picture]) + if (empty($this->info[$unset_key][$tagtype]['picture'])) { + unset($this->info[$unset_key][$tagtype]['picture']); + } + if (empty($this->info[$unset_key][$tagtype])) { + unset($this->info[$unset_key][$tagtype]); + } + if (empty($this->info[$unset_key])) { + unset($this->info[$unset_key]); + } + } + // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) + if (isset($this->info[$tagtype]['comments']['picture'])) { + unset($this->info[$tagtype]['comments']['picture']); + } + if (empty($this->info[$tagtype]['comments'])) { + unset($this->info[$tagtype]['comments']); + } + if (empty($this->info[$tagtype])) { + unset($this->info[$tagtype]); + } + } + } + return true; + } + + /** + * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3 + * + * @param array $ThisFileInfo + * + * @return bool + */ + public function CopyTagsToComments(&$ThisFileInfo) { + return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html); + } + + /** + * @param string $algorithm + * + * @return array|bool + */ + public function getHashdata($algorithm) { + switch ($algorithm) { + case 'md5': + case 'sha1': + break; + + default: + return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); + } + + if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { + + // We cannot get an identical md5_data value for Ogg files where the comments + // span more than 1 Ogg page (compared to the same audio data with smaller + // comments) using the normal getID3() method of MD5'ing the data between the + // end of the comments and the end of the file (minus any trailing tags), + // because the page sequence numbers of the pages that the audio data is on + // do not match. Under normal circumstances, where comments are smaller than + // the nominal 4-8kB page size, then this is not a problem, but if there are + // very large comments, the only way around it is to strip off the comment + // tags with vorbiscomment and MD5 that file. + // This procedure must be applied to ALL Ogg files, not just the ones with + // comments larger than 1 page, because the below method simply MD5's the + // whole file with the comments stripped, not just the portion after the + // comments block (which is the standard getID3() method. + + // The above-mentioned problem of comments spanning multiple pages and changing + // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but + // currently vorbiscomment only works on OggVorbis files. + + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + + $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); + $this->info[$algorithm.'_data'] = false; + + } else { + + // Prevent user from aborting script + $old_abort = ignore_user_abort(true); + + // Create empty file + $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); + touch($empty); + + // Use vorbiscomment to make temp file without comments + $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); + $file = $this->info['filenamepath']; + + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { + + $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; + $VorbisCommentError = `$commandline`; + + } else { + + $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; + + } + + } else { + + $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; + $VorbisCommentError = `$commandline`; + + } + + if (!empty($VorbisCommentError)) { + + $this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError); + $this->info[$algorithm.'_data'] = false; + + } else { + + // Get hash of newly created file + switch ($algorithm) { + case 'md5': + $this->info[$algorithm.'_data'] = md5_file($temp); + break; + + case 'sha1': + $this->info[$algorithm.'_data'] = sha1_file($temp); + break; + } + } + + // Clean up + unlink($empty); + unlink($temp); + + // Reset abort setting + ignore_user_abort($old_abort); + + } + + } else { + + if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { + + // get hash from part of file + $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); + + } else { + + // get hash from whole file + switch ($algorithm) { + case 'md5': + $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); + break; + + case 'sha1': + $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); + break; + } + } + + } + return true; + } + + public function ChannelsBitratePlaytimeCalculations() { + + // set channelmode on audio + if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { + // ignore + } elseif ($this->info['audio']['channels'] == 1) { + $this->info['audio']['channelmode'] = 'mono'; + } elseif ($this->info['audio']['channels'] == 2) { + $this->info['audio']['channelmode'] = 'stereo'; + } + + // Calculate combined bitrate - audio + video + $CombinedBitrate = 0; + $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); + $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); + if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { + $this->info['bitrate'] = $CombinedBitrate; + } + //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { + // // for example, VBR MPEG video files cannot determine video bitrate: + // // should not set overall bitrate and playtime from audio bitrate only + // unset($this->info['bitrate']); + //} + + // video bitrate undetermined, but calculable + if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { + // if video bitrate not set + if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { + // AND if audio bitrate is set to same as overall bitrate + if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { + // AND if playtime is set + if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { + // AND if AV data offset start/end is known + // THEN we can calculate the video bitrate + $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); + $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; + } + } + } + } + + if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { + $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; + } + + if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { + $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; + } + if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { + if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { + // audio only + $this->info['audio']['bitrate'] = $this->info['bitrate']; + } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { + // video only + $this->info['video']['bitrate'] = $this->info['bitrate']; + } + } + + // Set playtime string + if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { + $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); + } + } + + /** + * @return bool + */ + public function CalculateCompressionRatioVideo() { + if (empty($this->info['video'])) { + return false; + } + if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { + return false; + } + if (empty($this->info['video']['bits_per_sample'])) { + return false; + } + + switch ($this->info['video']['dataformat']) { + case 'bmp': + case 'gif': + case 'jpeg': + case 'jpg': + case 'png': + case 'tiff': + $FrameRate = 1; + $PlaytimeSeconds = 1; + $BitrateCompressed = $this->info['filesize'] * 8; + break; + + default: + if (!empty($this->info['video']['frame_rate'])) { + $FrameRate = $this->info['video']['frame_rate']; + } else { + return false; + } + if (!empty($this->info['playtime_seconds'])) { + $PlaytimeSeconds = $this->info['playtime_seconds']; + } else { + return false; + } + if (!empty($this->info['video']['bitrate'])) { + $BitrateCompressed = $this->info['video']['bitrate']; + } else { + return false; + } + break; + } + $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; + + $this->info['video']['compression_ratio'] = getid3_lib::SafeDiv($BitrateCompressed, $BitrateUncompressed, 1); + return true; + } + + /** + * @return bool + */ + public function CalculateCompressionRatioAudio() { + if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { + return false; + } + $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); + + if (!empty($this->info['audio']['streams'])) { + foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { + if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { + $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); + } + } + } + return true; + } + + /** + * @return bool + */ + public function CalculateReplayGain() { + if (isset($this->info['replay_gain'])) { + if (!isset($this->info['replay_gain']['reference_volume'])) { + $this->info['replay_gain']['reference_volume'] = 89.0; + } + if (isset($this->info['replay_gain']['track']['adjustment'])) { + $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; + } + if (isset($this->info['replay_gain']['album']['adjustment'])) { + $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; + } + + if (isset($this->info['replay_gain']['track']['peak'])) { + $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); + } + if (isset($this->info['replay_gain']['album']['peak'])) { + $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); + } + } + return true; + } + + /** + * @return bool + */ + public function ProcessAudioStreams() { + if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { + if (!isset($this->info['audio']['streams'])) { + foreach ($this->info['audio'] as $key => $value) { + if ($key != 'streams') { + $this->info['audio']['streams'][0][$key] = $value; + } + } + } + } + return true; + } + + /** + * @return string|bool + */ + public function getid3_tempnam() { + return tempnam($this->tempdir, 'gI3'); + } + + /** + * @param string $name + * + * @return bool + * + * @throws getid3_exception + */ + public function include_module($name) { + //if (!file_exists($this->include_path.'module.'.$name.'.php')) { + if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { + throw new getid3_exception('Required module.'.$name.'.php is missing.'); + } + include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); + return true; + } + + /** + * @param string $filename + * + * @return bool + */ + public static function is_writable ($filename) { + $ret = is_writable($filename); + if (!$ret) { + $perms = fileperms($filename); + $ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002); + } + return $ret; + } + +} + + +abstract class getid3_handler +{ + + /** + * @var getID3 + */ + protected $getid3; // pointer + + /** + * Analyzing filepointer or string. + * + * @var bool + */ + protected $data_string_flag = false; + + /** + * String to analyze. + * + * @var string + */ + protected $data_string = ''; + + /** + * Seek position in string. + * + * @var int + */ + protected $data_string_position = 0; + + /** + * String length. + * + * @var int + */ + protected $data_string_length = 0; + + /** + * @var string + */ + private $dependency_to; + + /** + * getid3_handler constructor. + * + * @param getID3 $getid3 + * @param string $call_module + */ + public function __construct(getID3 $getid3, $call_module=null) { + $this->getid3 = $getid3; + + if ($call_module) { + $this->dependency_to = str_replace('getid3_', '', $call_module); + } + } + + /** + * Analyze from file pointer. + * + * @return bool + */ + abstract public function Analyze(); + + /** + * Analyze from string instead. + * + * @param string $string + */ + public function AnalyzeString($string) { + // Enter string mode + $this->setStringMode($string); + + // Save info + $saved_avdataoffset = $this->getid3->info['avdataoffset']; + $saved_avdataend = $this->getid3->info['avdataend']; + $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call + + // Reset some info + $this->getid3->info['avdataoffset'] = 0; + $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; + + // Analyze + $this->Analyze(); + + // Restore some info + $this->getid3->info['avdataoffset'] = $saved_avdataoffset; + $this->getid3->info['avdataend'] = $saved_avdataend; + $this->getid3->info['filesize'] = $saved_filesize; + + // Exit string mode + $this->data_string_flag = false; + } + + /** + * @param string $string + */ + public function setStringMode($string) { + $this->data_string_flag = true; + $this->data_string = $string; + $this->data_string_length = strlen($string); + } + + /** + * @phpstan-impure + * + * @return int|bool + */ + protected function ftell() { + if ($this->data_string_flag) { + return $this->data_string_position; + } + return ftell($this->getid3->fp); + } + + /** + * @param int $bytes + * + * @phpstan-impure + * + * @return string|false + * + * @throws getid3_exception + */ + protected function fread($bytes) { + if ($this->data_string_flag) { + $this->data_string_position += $bytes; + return substr($this->data_string, $this->data_string_position - $bytes, $bytes); + } + if ($bytes == 0) { + return ''; + } elseif ($bytes < 0) { + throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().')', 10); + } + $pos = $this->ftell() + $bytes; + if (!getid3_lib::intValueSupported($pos)) { + throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); + } + + //return fread($this->getid3->fp, $bytes); + /* + * https://www.getid3.org/phpBB3/viewtopic.php?t=1930 + * "I found out that the root cause for the problem was how getID3 uses the PHP system function fread(). + * It seems to assume that fread() would always return as many bytes as were requested. + * However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes. + * The call may return only part of the requested data and a new call is needed to get more." + */ + $contents = ''; + do { + //if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) { + if (($this->getid3->memory_limit > 0) && (($bytes / $this->getid3->memory_limit) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)" + throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10); + } + $part = fread($this->getid3->fp, $bytes); + $partLength = strlen($part); + $bytes -= $partLength; + $contents .= $part; + } while (($bytes > 0) && ($partLength > 0)); + return $contents; + } + + /** + * @param int $bytes + * @param int $whence + * + * @phpstan-impure + * + * @return int + * + * @throws getid3_exception + */ + protected function fseek($bytes, $whence=SEEK_SET) { + if ($this->data_string_flag) { + switch ($whence) { + case SEEK_SET: + $this->data_string_position = $bytes; + break; + + case SEEK_CUR: + $this->data_string_position += $bytes; + break; + + case SEEK_END: + $this->data_string_position = $this->data_string_length + $bytes; + break; + } + return 0; // fseek returns 0 on success + } + + $pos = $bytes; + if ($whence == SEEK_CUR) { + $pos = $this->ftell() + $bytes; + } elseif ($whence == SEEK_END) { + $pos = $this->getid3->info['filesize'] + $bytes; + } + if (!getid3_lib::intValueSupported($pos)) { + throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); + } + + // https://github.com/JamesHeinrich/getID3/issues/327 + $result = fseek($this->getid3->fp, $bytes, $whence); + if ($result !== 0) { // fseek returns 0 on success + throw new getid3_exception('cannot fseek('.$pos.'). resource/stream does not appear to support seeking', 10); + } + return $result; + } + + /** + * @phpstan-impure + * + * @return string|false + * + * @throws getid3_exception + */ + protected function fgets() { + // must be able to handle CR/LF/CRLF but not read more than one lineend + $buffer = ''; // final string we will return + $prevchar = ''; // save previously-read character for end-of-line checking + if ($this->data_string_flag) { + while (true) { + $thischar = substr($this->data_string, $this->data_string_position++, 1); + if (($prevchar == "\r") && ($thischar != "\n")) { + // read one byte too many, back up + $this->data_string_position--; + break; + } + $buffer .= $thischar; + if ($thischar == "\n") { + break; + } + if ($this->data_string_position >= $this->data_string_length) { + // EOF + break; + } + $prevchar = $thischar; + } + + } else { + + // Ideally we would just use PHP's fgets() function, however... + // it does not behave consistently with regards to mixed line endings, may be system-dependent + // and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs) + //return fgets($this->getid3->fp); + while (true) { + $thischar = fgetc($this->getid3->fp); + if (($prevchar == "\r") && ($thischar != "\n")) { + // read one byte too many, back up + fseek($this->getid3->fp, -1, SEEK_CUR); + break; + } + $buffer .= $thischar; + if ($thischar == "\n") { + break; + } + if (feof($this->getid3->fp)) { + break; + } + $prevchar = $thischar; + } + + } + return $buffer; + } + + /** + * @phpstan-impure + * + * @return bool + */ + protected function feof() { + if ($this->data_string_flag) { + return $this->data_string_position >= $this->data_string_length; + } + return feof($this->getid3->fp); + } + + /** + * @param string $module + * + * @return bool + */ + final protected function isDependencyFor($module) { + return $this->dependency_to == $module; + } + + /** + * @param string $text + * + * @return bool + */ + protected function error($text) { + $this->getid3->info['error'][] = $text; + + return false; + } + + /** + * @param string $text + * + * @return bool + */ + protected function warning($text) { + return $this->getid3->warning($text); + } + + /** + * @param string $text + */ + protected function notice($text) { + // does nothing for now + } + + /** + * @param string $name + * @param int $offset + * @param int $length + * @param string $image_mime + * + * @return string|null + * + * @throws Exception + * @throws getid3_exception + */ + public function saveAttachment($name, $offset, $length, $image_mime=null) { + $fp_dest = null; + $dest = null; + try { + + // do not extract at all + if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { + + $attachment = null; // do not set any + + // extract to return array + } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + + $this->fseek($offset); + $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory + if ($attachment === false || strlen($attachment) != $length) { + throw new Exception('failed to read attachment data'); + } + + // assume directory path is given + } else { + + // set up destination path + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory + throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); + } + $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); + + // create dest file + if (($fp_dest = fopen($dest, 'wb')) == false) { + throw new Exception('failed to create file '.$dest); + } + + // copy data + $this->fseek($offset); + $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); + $bytesleft = $length; + while ($bytesleft > 0) { + if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { + throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); + } + $bytesleft -= $byteswritten; + } + + fclose($fp_dest); + $attachment = $dest; + + } + + } catch (Exception $e) { + + // close and remove dest file if created + if (isset($fp_dest) && is_resource($fp_dest)) { + fclose($fp_dest); + } + + if (isset($dest) && file_exists($dest)) { + unlink($dest); + } + + // do not set any is case of error + $attachment = null; + $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); + + } + + // seek to the end of attachment + $this->fseek($offset + $length); + + return $attachment; + } + +} + + +class getid3_exception extends Exception +{ + public $message; +} diff --git a/config/getid3/module.audio.mp3.php b/config/getid3/module.audio.mp3.php new file mode 100644 index 000000000..0d8fee3e5 --- /dev/null +++ b/config/getid3/module.audio.mp3.php @@ -0,0 +1,2212 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mp3.php // +// module for analyzing MP3 files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} + + +class getid3_mp3 extends getid3_handler +{ + /** + * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, + * unrecommended, but may provide data from otherwise-unusable files. + * + * @var bool + */ + public $allow_bruteforce = false; + + /** + * number of frames to scan to determine if MPEG-audio sequence is valid + * Lower this number to 5-20 for faster scanning + * Increase this number to 50+ for most accurate detection of valid VBR/CBR mpeg-audio streams + * + * @var int + */ + public $mp3_valid_check_frames = 50; + + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + $initialOffset = $info['avdataoffset']; + + if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) { + if ($this->allow_bruteforce) { + $this->error('Rescanning file in BruteForce mode'); + $this->getOnlyMPEGaudioInfoBruteForce(); + } + } + + + if (isset($info['mpeg']['audio']['bitrate_mode'])) { + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + } + + $CurrentDataLAMEversionString = null; + if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) { + + $synchoffsetwarning = 'Unknown data before synch '; + if (isset($info['id3v2']['headerlength'])) { + $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, '; + } elseif ($initialOffset > 0) { + $synchoffsetwarning .= '(should be at '.$initialOffset.', '; + } else { + $synchoffsetwarning .= '(should be at beginning of file, '; + } + $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')'; + if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) { + + if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) { + + $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.'; + $info['audio']['codec'] = 'LAME'; + $CurrentDataLAMEversionString = 'LAME3.'; + + } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) { + + $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.'; + $info['audio']['codec'] = 'LAME'; + $CurrentDataLAMEversionString = 'LAME3.'; + + } + + } + $this->warning($synchoffsetwarning); + + } + + if (isset($info['mpeg']['audio']['LAME'])) { + $info['audio']['codec'] = 'LAME'; + if (!empty($info['mpeg']['audio']['LAME']['long_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00"); + } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00"); + } + } + + $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : '')); + if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) { + // a version number of LAME that does not end with a number like "LAME3.92" + // or with a closing parenthesis like "LAME3.88 (alpha)" + // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92) + + // not sure what the actual last frame length will be, but will be less than or equal to 1441 + $PossiblyLongerLAMEversion_FrameLength = 1441; + + // Not sure what version of LAME this is - look in padding of last frame for longer version string + $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; + $this->fseek($PossibleLAMEversionStringOffset); + $PossiblyLongerLAMEversion_Data = $this->fread($PossiblyLongerLAMEversion_FrameLength); + switch (substr($CurrentDataLAMEversionString, -1)) { + case 'a': + case 'b': + // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example + // need to trim off "a" to match longer string + $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1); + break; + } + if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) { + if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { + $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" + if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) { + if (!empty($info['audio']['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version']) && ($info['audio']['encoder'] == $info['mpeg']['audio']['LAME']['short_version'])) { + if (preg_match('#^LAME[0-9\\.]+#', $PossiblyLongerLAMEversion_NewString, $matches)) { + // "LAME3.100" -> "LAME3.100.1", but avoid including "(alpha)" and similar + $info['mpeg']['audio']['LAME']['short_version'] = $matches[0]; + } + } + $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; + } + } + } + } + if (!empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 "); + } + + switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') { + case 1: + case 2: + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; + break; + } + if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) { + switch ($info['audio']['dataformat']) { + case 'mp1': + case 'mp2': + case 'mp3': + $info['fileformat'] = $info['audio']['dataformat']; + break; + + default: + $this->warning('Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"'); + break; + } + } + + if (empty($info['fileformat'])) { + unset($info['fileformat']); + unset($info['audio']['bitrate_mode']); + unset($info['avdataoffset']); + unset($info['avdataend']); + return false; + } + + $info['mime_type'] = 'audio/mpeg'; + $info['audio']['lossless'] = false; + + // Calculate playtime + if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) { + // https://github.com/JamesHeinrich/getID3/issues/161 + // VBR header frame contains ~0.026s of silent audio data, but is not actually part of the original encoding and should be ignored + $xingVBRheaderFrameLength = ((isset($info['mpeg']['audio']['VBR_frames']) && isset($info['mpeg']['audio']['framelength'])) ? $info['mpeg']['audio']['framelength'] : 0); + + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset'] - $xingVBRheaderFrameLength) * 8 / $info['audio']['bitrate']; + } + + $info['audio']['encoder_options'] = $this->GuessEncoderOptions(); + + return true; + } + + /** + * @return string + */ + public function GuessEncoderOptions() { + // shortcuts + $info = &$this->getid3->info; + $thisfile_mpeg_audio = array(); + $thisfile_mpeg_audio_lame = array(); + if (!empty($info['mpeg']['audio'])) { + $thisfile_mpeg_audio = &$info['mpeg']['audio']; + if (!empty($thisfile_mpeg_audio['LAME'])) { + $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; + } + } + + $encoder_options = ''; + static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256); + + if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { + + $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; + + } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && isset($thisfile_mpeg_audio_lame['preset_used_id']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) { + + $encoder_options = $thisfile_mpeg_audio_lame['preset_used']; + + } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) { + + static $KnownEncoderValues = array(); + if (empty($KnownEncoderValues)) { + + //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name'; + $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95 + $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91 + $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3 + $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3 + $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3 + $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95 + $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3 + $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3 + + $KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93 + $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95 + $KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93 + $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95 + $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95 + $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95 + $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95 + $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14 + $KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91 + $KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95 + $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14 + $KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15 + $KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93 + $KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95 + $KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14 + $KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15 + $KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93 + $KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95 + } + + if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + + $encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; + + } elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + + $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; + + } elseif ($info['audio']['bitrate_mode'] == 'vbr') { + + // http://gabriel.mp3-tech.org/mp3infotag.html + // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h + + + $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10); + $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10); + $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value; + + } elseif ($info['audio']['bitrate_mode'] == 'cbr') { + + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + + } else { + + $encoder_options = strtoupper($info['audio']['bitrate_mode']); + + } + + } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) { + + $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr']; + + } elseif (!empty($info['audio']['bitrate'])) { + + if ($info['audio']['bitrate_mode'] == 'cbr') { + $encoder_options = strtoupper($info['audio']['bitrate_mode']).round($info['audio']['bitrate'] / 1000); + } else { + $encoder_options = strtoupper($info['audio']['bitrate_mode']); + } + + } + if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) { + $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min']; + } + + if (isset($thisfile_mpeg_audio['bitrate']) && $thisfile_mpeg_audio['bitrate'] === 'free') { + $encoder_options .= ' --freeformat'; + } + + if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) { + $encoder_options .= ' --nogap'; + } + + if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) { + $ExplodedOptions = explode(' ', $encoder_options, 4); + if ($ExplodedOptions[0] == '--r3mix') { + $ExplodedOptions[1] = 'r3mix'; + } + switch ($ExplodedOptions[0]) { + case '--preset': + case '--alt-preset': + case '--r3mix': + if ($ExplodedOptions[1] == 'fast') { + $ExplodedOptions[1] .= ' '.$ExplodedOptions[2]; + } + switch ($ExplodedOptions[1]) { + case 'portable': + case 'medium': + case 'standard': + case 'extreme': + case 'insane': + case 'fast portable': + case 'fast medium': + case 'fast standard': + case 'fast extreme': + case 'fast insane': + case 'r3mix': + static $ExpectedLowpass = array( + 'insane|20500' => 20500, + 'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91 + 'medium|18000' => 18000, + 'fast medium|18000' => 18000, + 'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 + 'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 + 'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 + 'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 + 'standard|19000' => 19000, + 'fast standard|19000' => 19000, + 'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92 + 'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91 + 'r3mix|18000' => 18000, // 3.94, 3.95 + ); + if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) { + $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency']; + } + break; + + default: + break; + } + break; + } + } + + if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) { + if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) { + $encoder_options .= ' --resample 44100'; + } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) { + $encoder_options .= ' --resample 48000'; + } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) { + switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) { + case 0: // <= 32000 + // may or may not be same as source frequency - ignore + break; + case 1: // 44100 + case 2: // 48000 + case 3: // 48000+ + $ExplodedOptions = explode(' ', $encoder_options, 4); + switch ($ExplodedOptions[0]) { + case '--preset': + case '--alt-preset': + switch ($ExplodedOptions[1]) { + case 'fast': + case 'portable': + case 'medium': + case 'standard': + case 'extreme': + case 'insane': + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + break; + + default: + static $ExpectedResampledRate = array( + 'phon+/lw/mw-eu/sw|16000' => 16000, + 'mw-us|24000' => 24000, // 3.95 + 'mw-us|32000' => 32000, // 3.93 + 'mw-us|16000' => 16000, // 3.92 + 'phone|16000' => 16000, + 'phone|11025' => 11025, // 3.94a15 + 'radio|32000' => 32000, // 3.94a15 + 'fm/radio|32000' => 32000, // 3.92 + 'fm|32000' => 32000, // 3.90 + 'voice|32000' => 32000); + if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) { + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + } + break; + } + break; + + case '--r3mix': + default: + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + break; + } + break; + } + } + } + if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) { + //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); + } + + return $encoder_options; + } + + /** + * @param int $offset + * @param array $info + * @param bool $recursivesearch + * @param bool $ScanAsCBR + * @param bool $FastMPEGheaderScan + * + * @return bool + */ + public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + static $MPEGaudioFrequencyLookup; + static $MPEGaudioChannelModeLookup; + static $MPEGaudioModeExtensionLookup; + static $MPEGaudioEmphasisLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); + } + + if ($this->fseek($offset) != 0) { + $this->error('decodeMPEGaudioHeader() failed to seek to next offset at '.$offset); + return false; + } + //$headerstring = $this->fread(1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame + $headerstring = $this->fread(226); // LAME header at offset 36 + 190 bytes of Xing/LAME data + + // MP3 audio frame structure: + // $aa $aa $aa $aa [$bb $bb] $cc... + // where $aa..$aa is the four-byte mpeg-audio header (below) + // $bb $bb is the optional 2-byte CRC + // and $cc... is the audio data + + $head4 = substr($headerstring, 0, 4); + $head4_key = getid3_lib::PrintHexBytes($head4, true, false, false); + static $MPEGaudioHeaderDecodeCache = array(); + if (isset($MPEGaudioHeaderDecodeCache[$head4_key])) { + $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4_key]; + } else { + $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4); + $MPEGaudioHeaderDecodeCache[$head4_key] = $MPEGheaderRawArray; + } + + static $MPEGaudioHeaderValidCache = array(); + if (!isset($MPEGaudioHeaderValidCache[$head4_key])) { // Not in cache + //$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) + $MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); + } + + // shortcut + if (!isset($info['mpeg']['audio'])) { + $info['mpeg']['audio'] = array(); + } + $thisfile_mpeg_audio = &$info['mpeg']['audio']; + + if ($MPEGaudioHeaderValidCache[$head4_key]) { + $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; + } else { + $this->warning('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset); + return false; + } + + if (!$FastMPEGheaderScan) { + $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']]; + $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']]; + + $thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']]; + $thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2); + $thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']]; + $thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection']; + $thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private']; + $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']]; + $thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright']; + $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original']; + $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']]; + + $info['audio']['channels'] = $thisfile_mpeg_audio['channels']; + $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; + + if ($thisfile_mpeg_audio['protection']) { + $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2)); + } + } + + if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) { + // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 + $this->warning('Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'); + $thisfile_mpeg_audio['raw']['bitrate'] = 0; + } + $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding']; + $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']]; + + if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) { + // only skip multiple frame check if free-format bitstream found at beginning of file + // otherwise is quite possibly simply corrupted data + $recursivesearch = false; + } + + // For Layer 2 there are some combinations of bitrate and mode which are not allowed. + if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) { + + $info['audio']['dataformat'] = 'mp2'; + switch ($thisfile_mpeg_audio['channelmode']) { + + case 'mono': + if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) { + // these are ok + } else { + $this->error($thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'); + return false; + } + break; + + case 'stereo': + case 'joint stereo': + case 'dual channel': + if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) { + // these are ok + } else { + $this->error(intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'); + return false; + } + break; + + } + + } + + + if ($info['audio']['sample_rate'] > 0) { + $thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); + } + + $nextframetestoffset = $offset + 1; + if ($thisfile_mpeg_audio['bitrate'] != 'free') { + + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + + if (isset($thisfile_mpeg_audio['framelength'])) { + $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength']; + } else { + $this->error('Frame at offset('.$offset.') is has an invalid frame length.'); + return false; + } + + } + + $ExpectedNumberOfAudioBytes = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // Variable-bitrate headers + + if (substr($headerstring, 4 + 32, 4) == 'VBRI') { + // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36) + // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html + + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; + $info['audio']['codec'] = 'Fraunhofer'; + + $SideInfoData = substr($headerstring, 4 + 2, 32); + + $FraunhoferVBROffset = 36; + + $thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion + $thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay + $thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality + $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes + $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames + $thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize + $thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale + $thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes + $thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames + + $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes']; + + $previousbyteoffset = $offset; + for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) { + $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes'])); + $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes']; + $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']); + $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset; + $previousbyteoffset += $Fraunhofer_OffsetN; + } + + + } else { + + // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36) + // depending on MPEG layer and number of channels + + $VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']); + $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4); + + if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) { + // 'Xing' is traditional Xing VBR frame + // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.) + // 'Info' *can* legally be used to specify a VBR file as well, however. + + // http://www.multiweb.cz/twoinches/MP3inside.htm + //00..03 = "Xing" or "Info" + //04..07 = Flags: + // 0x01 Frames Flag set if value for number of frames in file is stored + // 0x02 Bytes Flag set if value for filesize in bytes is stored + // 0x04 TOC Flag set if values for TOC are stored + // 0x08 VBR Scale Flag set if values for VBR scale is stored + //08..11 Frames: Number of frames in file (including the first Xing/Info one) + //12..15 Bytes: File length in Bytes + //16..115 TOC (Table of Contents): + // Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file. + // Each Byte has a value according this formula: + // (TOC[i] / 256) * fileLenInBytes + // So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use: + // TOC[(60/240)*100] = TOC[25] + // and corresponding Byte in file is then approximately at: + // (TOC[25]/256) * 5000000 + //116..119 VBR Scale + + + // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME +// if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') { + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + $thisfile_mpeg_audio['VBR_method'] = 'Xing'; +// } else { +// $ScanAsCBR = true; +// $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; +// } + + $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4)); + + $thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001); + $thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002); + $thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004); + $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008); + + if ($thisfile_mpeg_audio['xing_flags']['frames']) { + $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4)); + //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame + } + if ($thisfile_mpeg_audio['xing_flags']['bytes']) { + $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4)); + } + + //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + //if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + if (!empty($thisfile_mpeg_audio['VBR_frames'])) { + $used_filesize = 0; + if (!empty($thisfile_mpeg_audio['VBR_bytes'])) { + $used_filesize = $thisfile_mpeg_audio['VBR_bytes']; + } elseif (!empty($info['filesize'])) { + $used_filesize = $info['filesize']; + $used_filesize -= (isset($info['id3v2']['headerlength']) ? intval($info['id3v2']['headerlength']) : 0); + $used_filesize -= (isset($info['id3v1']) ? 128 : 0); + $used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0); + $this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes'); + } + + $framelengthfloat = $used_filesize / $thisfile_mpeg_audio['VBR_frames']; + + if ($thisfile_mpeg_audio['layer'] == '1') { + // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 + //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12; + } else { + // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 + //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144; + } + $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat); + } + + if ($thisfile_mpeg_audio['xing_flags']['toc']) { + $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100); + for ($i = 0; $i < 100; $i++) { + $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData[$i]); + } + } + if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) { + $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4)); + } + + + // http://gabriel.mp3-tech.org/mp3infotag.html + if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') { + + // shortcut + $thisfile_mpeg_audio['LAME'] = array(); + $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; + + + $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20); + $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9); + + //$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']); + $thisfile_mpeg_audio_lame['numeric_version'] = ''; + if (preg_match('#^LAME([0-9\\.a-z]*)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) { + $thisfile_mpeg_audio_lame['short_version'] = $matches[0]; + $thisfile_mpeg_audio_lame['numeric_version'] = $matches[1]; + } + if (strlen($thisfile_mpeg_audio_lame['numeric_version']) > 0) { + foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) { + $thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number); + } + //if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') { + if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207 + + // extra 11 chars are not part of version string when LAMEtag present + unset($thisfile_mpeg_audio_lame['long_version']); + + // It the LAME tag was only introduced in LAME v3.90 + // https://wiki.hydrogenaud.io/index.php/LAME#VBR_header_and_LAME_tag + // https://hydrogenaud.io/index.php?topic=9933 + + // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html + // are assuming a 'Xing' identifier offset of 0x24, which is the case for + // MPEG-1 non-mono, but not for other combinations + $LAMEtagOffsetContant = $VBRidOffset - 0x24; + + // shortcuts + $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array()); + $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD']; + $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track']; + $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album']; + $thisfile_mpeg_audio_lame['raw'] = array(); + $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw']; + + // byte $9B VBR Quality + // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications. + // Actually overwrites original Xing bytes + unset($thisfile_mpeg_audio['VBR_scale']); + $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1)); + + // bytes $9C-$A4 Encoder short VersionString + $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9); + + // byte $A5 Info Tag revision + VBR method + $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1)); + + $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4; + $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F; + $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']); + $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr' + + // byte $A6 Lowpass filter value + $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100; + + // bytes $A7-$AE Replay Gain + // https://web.archive.org/web/20021015212753/http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html + // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude" + if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') { + // LAME 3.94a16 and later - 9.23 fixed point + // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375 + $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608); + } else { + // LAME 3.94a15 and earlier - 32-bit floating point + // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15 + $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4)); + } + if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) { + unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); + } else { + $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); + } + + $thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2)); + $thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2)); + + + if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) { + + $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF; + $thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']); + $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']); + $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']); + + if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { + $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + } + $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; + $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; + } else { + unset($thisfile_mpeg_audio_lame_RGAD['track']); + } + if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) { + + $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF; + $thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']); + $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']); + $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']); + + if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { + $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + } + $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; + $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; + } else { + unset($thisfile_mpeg_audio_lame_RGAD['album']); + } + if (empty($thisfile_mpeg_audio_lame_RGAD)) { + unset($thisfile_mpeg_audio_lame['RGAD']); + } + + + // byte $AF Encoding flags + ATH Type + $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1)); + $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10); + $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20); + $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40); + $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80); + $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F; + + // byte $B0 if ABR {specified bitrate} else {minimal bitrate} + $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1)); + if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR) + $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; + } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR) + // ignore + } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate + $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; + } + + // bytes $B1-$B3 Encoder delays + $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3)); + $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12; + $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF; + + // byte $B4 Misc + $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1)); + $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03); + $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2; + $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5; + $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6; + $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping']; + $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']); + $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality']; + $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']); + + // byte $B5 MP3 Gain + $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true); + $thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain']; + $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6)); + + // bytes $B6-$B7 Preset and surround info + $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2)); + // Reserved = ($PresetSurroundBytes & 0xC000); + $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800); + $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']); + $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); + $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); + if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { + $this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'); + } + if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) { + // this may change if 3.90.4 ever comes out + $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3'; + } + + // bytes $B8-$BB MusicLength + $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4)); + $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']); + + // bytes $BC-$BD MusicCRC + $thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2)); + + // bytes $BE-$BF CRC-16 of Info Tag + $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2)); + + + // LAME CBR + if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1 && $thisfile_mpeg_audio['bitrate'] !== 'free') { + + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { + // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; + //} + + } + + } + } + } + + } else { + + // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header) + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + if ($recursivesearch) { + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) { + $recursivesearch = false; + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + } + if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') { + $this->warning('VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'); + } + } + + } + + } + + if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) { + if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) { + if ($this->isDependencyFor('matroska') || $this->isDependencyFor('riff')) { + // ignore, audio data is broken into chunks so will always be data "missing" + } + elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { + $this->warning('Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'); + } + else { + $this->warning('Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'); + } + } else { + if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { + // $prenullbytefileoffset = $this->ftell(); + // $this->fseek($info['avdataend']); + // $PossibleNullByte = $this->fread(1); + // $this->fseek($prenullbytefileoffset); + // if ($PossibleNullByte === "\x00") { + $info['avdataend']--; + // $this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'); + // } else { + // $this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'); + // } + } else { + $this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'); + } + } + } + + if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) { + if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { + $framebytelength = $this->FreeFormatFrameLength($offset, true); + if ($framebytelength > 0) { + $thisfile_mpeg_audio['framelength'] = $framebytelength; + if ($thisfile_mpeg_audio['layer'] == '1') { + // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 + $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + } else { + // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 + $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + } + } else { + $this->error('Error calculating frame length of free-format MP3 without Xing/LAME header'); + } + } + } + + if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') { + switch ($thisfile_mpeg_audio['bitrate_mode']) { + case 'vbr': + case 'abr': + $bytes_per_frame = 1152; + if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) { + $bytes_per_frame = 384; + } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) { + $bytes_per_frame = 576; + } + $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0); + if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { + $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; + $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion + } + break; + } + } + + // End variable-bitrate headers + //////////////////////////////////////////////////////////////////////////////////// + + if ($recursivesearch) { + + if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) { + return false; + } + if (!empty($this->getid3->info['mp3_validity_check_bitrates']) && !empty($thisfile_mpeg_audio['bitrate_mode']) && ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') && !empty($thisfile_mpeg_audio['VBR_bitrate'])) { + // https://github.com/JamesHeinrich/getID3/issues/287 + if (count(array_keys($this->getid3->info['mp3_validity_check_bitrates'])) == 1) { + list($cbr_bitrate_in_short_scan) = array_keys($this->getid3->info['mp3_validity_check_bitrates']); + $deviation_cbr_from_header_bitrate = abs($thisfile_mpeg_audio['VBR_bitrate'] - $cbr_bitrate_in_short_scan) / $cbr_bitrate_in_short_scan; + if ($deviation_cbr_from_header_bitrate < 0.01) { + // VBR header bitrate may differ slightly from true bitrate of frames, perhaps accounting for overhead of VBR header frame itself? + // If measured CBR bitrate is within 1% of specified bitrate in VBR header then assume that file is truly CBR + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + //$this->warning('VBR header ignored, assuming CBR '.round($cbr_bitrate_in_short_scan / 1000).'kbps based on scan of '.$this->mp3_valid_check_frames.' frames'); + } + } + } + if (isset($this->getid3->info['mp3_validity_check_bitrates'])) { + unset($this->getid3->info['mp3_validity_check_bitrates']); + } + + } + + + //if (false) { + // // experimental side info parsing section - not returning anything useful yet + // + // $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData); + // $SideInfoOffset = 0; + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { + // // MPEG-1 (mono) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $SideInfoOffset += 5; + // } else { + // // MPEG-1 (stereo, joint-stereo, dual-channel) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $SideInfoOffset += 3; + // } + // } else { // 2 or 2.5 + // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { + // // MPEG-2, MPEG-2.5 (mono) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // $SideInfoOffset += 1; + // } else { + // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // $SideInfoOffset += 2; + // } + // } + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { + // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { + // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 2; + // } + // } + // } + // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { + // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); + // $SideInfoOffset += 12; + // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // if ($thisfile_mpeg_audio['version'] == '1') { + // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); + // $SideInfoOffset += 4; + // } else { + // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // } + // $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // + // if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') { + // + // $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2); + // $SideInfoOffset += 2; + // $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // + // for ($region = 0; $region < 2; $region++) { + // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); + // $SideInfoOffset += 5; + // } + // $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0; + // + // for ($window = 0; $window < 3; $window++) { + // $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3); + // $SideInfoOffset += 3; + // } + // + // } else { + // + // for ($region = 0; $region < 3; $region++) { + // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); + // $SideInfoOffset += 5; + // } + // + // $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); + // $SideInfoOffset += 4; + // $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3); + // $SideInfoOffset += 3; + // $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0; + // } + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // } + // $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // } + // } + //} + + return true; + } + + /** + * @param int $offset + * @param int $nextframetestoffset + * @param bool $ScanAsCBR + * + * @return bool + */ + public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { + $info = &$this->getid3->info; + $firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']); + $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); + + $info['mp3_validity_check_bitrates'] = array(); + for ($i = 0; $i < $this->mp3_valid_check_frames; $i++) { + // check next (default: 50) frames for validity, to make sure we haven't run across a false synch + if (($nextframetestoffset + 4) >= $info['avdataend']) { + // end of file + return true; + } + + $nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) { + /** @phpstan-ignore-next-line */ + getid3_lib::safe_inc($info['mp3_validity_check_bitrates'][$nextframetestarray['mpeg']['audio']['bitrate']]); + if ($ScanAsCBR) { + // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header + if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) { + return false; + } + } + + + // next frame is OK, get ready to check the one after that + if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { + $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; + } else { + $this->error('Frame at offset ('.$offset.') is has an invalid frame length.'); + return false; + } + + } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) { + + // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK + return true; + + } else { + + // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence + $this->warning('Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'); + + return false; + } + } + return true; + } + + /** + * @param int $offset + * @param bool $deepscan + * + * @return int|false + */ + public function FreeFormatFrameLength($offset, $deepscan=false) { + $info = &$this->getid3->info; + + $this->fseek($offset); + $MPEGaudioData = $this->fread(32768); + + $SyncPattern1 = substr($MPEGaudioData, 0, 4); + // may be different pattern due to padding + $SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3]; + if ($SyncPattern2 === $SyncPattern1) { + $SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3]; + } + + $framelength = false; + $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4); + $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4); + if ($framelength1 > 4) { + $framelength = $framelength1; + } + if (($framelength2 > 4) && ($framelength2 < $framelength1)) { + $framelength = $framelength2; + } + if (!$framelength) { + + // LAME 3.88 has a different value for modeextension on the first frame vs the rest + $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4); + $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4); + + if ($framelength1 > 4) { + $framelength = $framelength1; + } + if (($framelength2 > 4) && ($framelength2 < $framelength1)) { + $framelength = $framelength2; + } + if (!$framelength) { + $this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset); + return false; + } else { + $this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'); + $info['audio']['codec'] = 'LAME'; + $info['audio']['encoder'] = 'LAME3.88'; + $SyncPattern1 = substr($SyncPattern1, 0, 3); + $SyncPattern2 = substr($SyncPattern2, 0, 3); + } + } + + if ($deepscan) { + + $ActualFrameLengthValues = array(); + $nextoffset = $offset + $framelength; + while ($nextoffset < ($info['avdataend'] - 6)) { + $this->fseek($nextoffset - 1); + $NextSyncPattern = $this->fread(6); + if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { + // good - found where expected + $ActualFrameLengthValues[] = $framelength; + } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) { + // ok - found one byte earlier than expected (last frame wasn't padded, first frame was) + $ActualFrameLengthValues[] = ($framelength - 1); + $nextoffset--; + } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) { + // ok - found one byte later than expected (last frame was padded, first frame wasn't) + $ActualFrameLengthValues[] = ($framelength + 1); + $nextoffset++; + } else { + $this->error('Did not find expected free-format sync pattern at offset '.$nextoffset); + return false; + } + $nextoffset += $framelength; + } + if (count($ActualFrameLengthValues) > 0) { + $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues))); + } + } + return $framelength; + } + + /** + * @return bool + */ + public function getOnlyMPEGaudioInfoBruteForce() { + $MPEGaudioHeaderDecodeCache = array(); + $MPEGaudioHeaderValidCache = array(); + $MPEGaudioHeaderLengthCache = array(); + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); + $LongMPEGversionLookup = array(); + $LongMPEGlayerLookup = array(); + $LongMPEGbitrateLookup = array(); + $LongMPEGpaddingLookup = array(); + $LongMPEGfrequencyLookup = array(); + $Distribution = array(); + $Distribution['bitrate'] = array(); + $Distribution['frequency'] = array(); + $Distribution['layer'] = array(); + $Distribution['version'] = array(); + $Distribution['padding'] = array(); + + $info = &$this->getid3->info; + $this->fseek($info['avdataoffset']); + + $max_frames_scan = 5000; + $frames_scanned = 0; + + $previousvalidframe = $info['avdataoffset']; + while ($this->ftell() < $info['avdataend']) { + set_time_limit(30); + $head4 = $this->fread(4); + if (strlen($head4) < 4) { + break; + } + if ($head4[0] != "\xFF") { + for ($i = 1; $i < 4; $i++) { + if ($head4[$i] == "\xFF") { + $this->fseek($i - 4, SEEK_CUR); + continue 2; + } + } + continue; + } + if (!isset($MPEGaudioHeaderDecodeCache[$head4])) { + $MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4); + } + if (!isset($MPEGaudioHeaderValidCache[$head4])) { + $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false); + } + if ($MPEGaudioHeaderValidCache[$head4]) { + + if (!isset($MPEGaudioHeaderLengthCache[$head4])) { + $LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']]; + $LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']]; + $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']]; + $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding']; + $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']]; + $MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength( + $LongMPEGbitrateLookup[$head4], + $LongMPEGversionLookup[$head4], + $LongMPEGlayerLookup[$head4], + $LongMPEGpaddingLookup[$head4], + $LongMPEGfrequencyLookup[$head4]); + } + if ($MPEGaudioHeaderLengthCache[$head4] > 4) { + $WhereWeWere = $this->ftell(); + $this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); + $next4 = $this->fread(4); + if ($next4[0] == "\xFF") { + if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { + $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4); + } + if (!isset($MPEGaudioHeaderValidCache[$next4])) { + $MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); + } + if ($MPEGaudioHeaderValidCache[$next4]) { + $this->fseek(-4, SEEK_CUR); + + $Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] = isset($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]) ? ++$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] : 1; + $Distribution['layer'][$LongMPEGlayerLookup[$head4]] = isset($Distribution['layer'][$LongMPEGlayerLookup[$head4]]) ? ++$Distribution['layer'][$LongMPEGlayerLookup[$head4]] : 1; + $Distribution['version'][$LongMPEGversionLookup[$head4]] = isset($Distribution['version'][$LongMPEGversionLookup[$head4]]) ? ++$Distribution['version'][$LongMPEGversionLookup[$head4]] : 1; + $Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] = isset($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]) ? ++$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] : 1; + $Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] = isset($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]) ? ++$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] : 1; + if (++$frames_scanned >= $max_frames_scan) { + $pct_data_scanned = getid3_lib::SafeDiv($this->ftell() - $info['avdataoffset'], $info['avdataend'] - $info['avdataoffset']); + $this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'); + foreach ($Distribution as $key1 => $value1) { + foreach ($value1 as $key2 => $value2) { + $Distribution[$key1][$key2] = $pct_data_scanned ? round($value2 / $pct_data_scanned) : 1; + } + } + break; + } + continue; + } + } + unset($next4); + $this->fseek($WhereWeWere - 3); + } + + } + } + foreach ($Distribution as $key => $value) { + ksort($Distribution[$key], SORT_NUMERIC); + } + ksort($Distribution['version'], SORT_STRING); + $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; + $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; + $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; + $info['mpeg']['audio']['version_distribution'] = $Distribution['version']; + $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; + if (count($Distribution['version']) > 1) { + $this->error('Corrupt file - more than one MPEG version detected'); + } + if (count($Distribution['layer']) > 1) { + $this->error('Corrupt file - more than one MPEG layer detected'); + } + if (count($Distribution['frequency']) > 1) { + $this->error('Corrupt file - more than one MPEG sample rate detected'); + } + + + $bittotal = 0; + foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) { + if ($bitratevalue != 'free') { + $bittotal += ($bitratevalue * $bitratecount); + } + } + $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); + if ($info['mpeg']['audio']['frame_count'] == 0) { + $this->error('no MPEG audio frames found'); + return false; + } + $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']); + $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); + $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); + + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); + $info['fileformat'] = $info['audio']['dataformat']; + + return true; + } + + /** + * @param int $avdataoffset + * @param bool $BitrateHistogram + * + * @return bool + */ + public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { + // looks for synch, decodes MPEG audio header + + $info = &$this->getid3->info; + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + } + + $this->fseek($avdataoffset); + $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); + if ($sync_seek_buffer_size <= 0) { + $this->error('Invalid $sync_seek_buffer_size at offset '.$avdataoffset); + return false; + } + $header = $this->fread($sync_seek_buffer_size); + $sync_seek_buffer_size = strlen($header); + $SynchSeekOffset = 0; + $SyncSeekAttempts = 0; + $SyncSeekAttemptsMax = 1000; + $FirstFrameThisfileInfo = null; + while ($SynchSeekOffset < $sync_seek_buffer_size) { + if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !$this->feof()) { + + if ($SynchSeekOffset > $sync_seek_buffer_size) { + // if a synch's not found within the first 128k bytes, then give up + $this->error('Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'); + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); + } + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); + } + if (empty($info['mpeg'])) { + unset($info['mpeg']); + } + return false; + } + } + + if (($SynchSeekOffset + 1) >= strlen($header)) { + $this->error('Could not find valid MPEG synch before end of file'); + return false; + } + + if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // possible synch detected + if (++$SyncSeekAttempts >= $SyncSeekAttemptsMax) { + // https://github.com/JamesHeinrich/getID3/issues/286 + // corrupt files claiming to be MP3, with a large number of 0xFF bytes near the beginning, can cause this loop to take a very long time + // should have escape condition to avoid spending too much time scanning a corrupt file + // if a synch's not found within the first 128k bytes, then give up + $this->error('Could not find valid MPEG audio synch after scanning '.$SyncSeekAttempts.' candidate offsets'); + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); + } + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); + } + if (empty($info['mpeg'])) { + unset($info['mpeg']); + } + return false; + } + $FirstFrameAVDataOffset = null; + if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { + $FirstFrameThisfileInfo = $info; + $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; + if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) { + // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's + // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below + unset($FirstFrameThisfileInfo); + } + } + + $dummy = $info; // only overwrite real data if valid header found + if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) { + $info = $dummy; + $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset; + switch (isset($info['fileformat']) ? $info['fileformat'] : '') { + case '': + case 'id3': + case 'ape': + case 'mp3': + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; + break; + } + if (isset($FirstFrameThisfileInfo) && isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { + if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { + // If there is garbage data between a valid VBR header frame and a sequence + // of valid MPEG-audio frames the VBR data is no longer discarded. + $info = $FirstFrameThisfileInfo; + $info['avdataoffset'] = $FirstFrameAVDataOffset; + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; + $dummy = $info; + unset($dummy['mpeg']['audio']); + $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; + $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; + if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { + $info = $dummy; + $info['avdataoffset'] = $GarbageOffsetEnd; + $this->warning('apparently-valid VBR header not used because could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd); + } else { + $this->warning('using data from VBR header even though could not find '.$this->mp3_valid_check_frames.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'); + } + } + } + if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) { + // VBR file with no VBR header + $BitrateHistogram = true; + } + + if ($BitrateHistogram) { + + $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); + $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); + + if ($info['mpeg']['audio']['version'] == '1') { + if ($info['mpeg']['audio']['layer'] == 3) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 2) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); + } + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); + } else { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); + } + + $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $synchstartoffset = $info['avdataoffset']; + $this->fseek($info['avdataoffset']); + + // you can play with these numbers: + $max_frames_scan = 50000; + $max_scan_segments = 10; + + // don't play with these numbers: + $FastMode = false; + $SynchErrorsFound = 0; + $frames_scanned = 0; + $this_scan_segment = 0; + $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments); + $pct_data_scanned = 0; + for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { + $frames_scanned_this_segment = 0; + $scan_start_offset = array(); + if ($this->ftell() >= $info['avdataend']) { + break; + } + $scan_start_offset[$current_segment] = max($this->ftell(), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); + if ($current_segment > 0) { + $this->fseek($scan_start_offset[$current_segment]); + $buffer_4k = $this->fread(4096); + for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { + if (($buffer_4k[$j] == "\xFF") && ($buffer_4k[($j + 1)] > "\xE0")) { // synch detected + if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { + $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; + if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { + $scan_start_offset[$current_segment] += $j; + break; + } + } + } + } + } + $synchstartoffset = $scan_start_offset[$current_segment]; + while (($synchstartoffset < $info['avdataend']) && $this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { + $FastMode = true; + $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; + + if (empty($dummy['mpeg']['audio']['framelength'])) { + $SynchErrorsFound++; + $synchstartoffset++; + } else { + getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]); + getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]); + getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]); + $synchstartoffset += $dummy['mpeg']['audio']['framelength']; + } + $frames_scanned++; + if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) { + $this_pct_scanned = getid3_lib::SafeDiv($this->ftell() - $scan_start_offset[$current_segment], $info['avdataend'] - $info['avdataoffset']); + if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) { + // file likely contains < $max_frames_scan, just scan as one segment + $max_scan_segments = 1; + $frames_scan_per_segment = $max_frames_scan; + } else { + $pct_data_scanned += $this_pct_scanned; + break; + } + } + } + } + if ($pct_data_scanned > 0) { + $this->warning('too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'); + foreach ($info['mpeg']['audio'] as $key1 => $value1) { + if (!preg_match('#_distribution$#i', $key1)) { + continue; + } + foreach ($value1 as $key2 => $value2) { + $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); + } + } + } + + if ($SynchErrorsFound > 0) { + $this->warning('Found '.$SynchErrorsFound.' synch errors in histogram analysis'); + //return false; + } + + $bittotal = 0; + $framecounter = 0; + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { + $framecounter += $bitratecount; + if ($bitratevalue != 'free') { + $bittotal += ($bitratevalue * $bitratecount); + } + } + if ($framecounter == 0) { + $this->error('Corrupt MP3 file: framecounter == zero'); + return false; + } + $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); + $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); + + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + + + // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently + $distinct_bitrates = 0; + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { + if ($bitrate_count > 0) { + $distinct_bitrates++; + } + } + if ($distinct_bitrates > 1) { + $info['mpeg']['audio']['bitrate_mode'] = 'vbr'; + } else { + $info['mpeg']['audio']['bitrate_mode'] = 'cbr'; + } + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; + + } + + break; // exit while() + } + } + + $SynchSeekOffset++; + if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) { + // end of file/data + + if (empty($info['mpeg']['audio'])) { + + $this->error('could not find valid MPEG synch before end of file'); + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); + } + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); + } + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) { + unset($info['mpeg']); + } + return false; + + } + break; + } + + } + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + if ($info['audio']['channels'] < 1) { + $this->error('Corrupt MP3 file: no channels'); + return false; + } + $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + return true; + } + + /** + * @return array + */ + public static function MPEGaudioVersionArray() { + static $MPEGaudioVersion = array('2.5', false, '2', '1'); + return $MPEGaudioVersion; + } + + /** + * @return array + */ + public static function MPEGaudioLayerArray() { + static $MPEGaudioLayer = array(false, 3, 2, 1); + return $MPEGaudioLayer; + } + + /** + * @return array + */ + public static function MPEGaudioBitrateArray() { + static $MPEGaudioBitrate; + if (empty($MPEGaudioBitrate)) { + $MPEGaudioBitrate = array ( + '1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000), + 2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000), + 3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000) + ), + + '2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000), + 2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000), + ) + ); + $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2]; + $MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2']; + } + return $MPEGaudioBitrate; + } + + /** + * @return array + */ + public static function MPEGaudioFrequencyArray() { + static $MPEGaudioFrequency; + if (empty($MPEGaudioFrequency)) { + $MPEGaudioFrequency = array ( + '1' => array(44100, 48000, 32000), + '2' => array(22050, 24000, 16000), + '2.5' => array(11025, 12000, 8000) + ); + } + return $MPEGaudioFrequency; + } + + /** + * @return array + */ + public static function MPEGaudioChannelModeArray() { + static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); + return $MPEGaudioChannelMode; + } + + /** + * @return array + */ + public static function MPEGaudioModeExtensionArray() { + static $MPEGaudioModeExtension; + if (empty($MPEGaudioModeExtension)) { + $MPEGaudioModeExtension = array ( + 1 => array('4-31', '8-31', '12-31', '16-31'), + 2 => array('4-31', '8-31', '12-31', '16-31'), + 3 => array('', 'IS', 'MS', 'IS+MS') + ); + } + return $MPEGaudioModeExtension; + } + + /** + * @return array + */ + public static function MPEGaudioEmphasisArray() { + static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); + return $MPEGaudioEmphasis; + } + + /** + * @param string $head4 + * @param bool $allowBitrate15 + * + * @return bool + */ + public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { + return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); + } + + /** + * @param array $rawarray + * @param bool $echoerrors + * @param bool $allowBitrate15 + * + * @return bool + */ + public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { + if (!isset($rawarray['synch']) || ($rawarray['synch'] & 0x0FFE) != 0x0FFE) { + return false; + } + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + static $MPEGaudioFrequencyLookup; + static $MPEGaudioChannelModeLookup; + static $MPEGaudioModeExtensionLookup; + static $MPEGaudioEmphasisLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); + } + + if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { + $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']]; + } else { + echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : ''); + return false; + } + if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) { + $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']]; + } else { + echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : ''); + return false; + } + if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) { + echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : ''); + if ($rawarray['bitrate'] == 15) { + // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0 + // let it go through here otherwise file will not be identified + if (!$allowBitrate15) { + return false; + } + } else { + return false; + } + } + if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) { + echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : ''); + return false; + } + if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) { + echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : ''); + return false; + } + if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) { + echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : ''); + return false; + } + if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) { + echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : ''); + return false; + } + // These are just either set or not set, you can't mess that up :) + // $rawarray['protection']; + // $rawarray['padding']; + // $rawarray['private']; + // $rawarray['copyright']; + // $rawarray['original']; + + return true; + } + + /** + * @param string $Header4Bytes + * + * @return array|false + */ + public static function MPEGaudioHeaderDecode($Header4Bytes) { + // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM + // A - Frame sync (all bits set) + // B - MPEG Audio version ID + // C - Layer description + // D - Protection bit + // E - Bitrate index + // F - Sampling rate frequency index + // G - Padding bit + // H - Private bit + // I - Channel Mode + // J - Mode extension (Only if Joint stereo) + // K - Copyright + // L - Original + // M - Emphasis + + if (strlen($Header4Bytes) != 4) { + return false; + } + + $MPEGrawHeader = array(); + $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4; + $MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB + $MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC + $MPEGrawHeader['protection'] = (ord($Header4Bytes[1]) & 0x01); // D + $MPEGrawHeader['bitrate'] = (ord($Header4Bytes[2]) & 0xF0) >> 4; // EEEE + $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes[2]) & 0x0C) >> 2; // FF + $MPEGrawHeader['padding'] = (ord($Header4Bytes[2]) & 0x02) >> 1; // G + $MPEGrawHeader['private'] = (ord($Header4Bytes[2]) & 0x01); // H + $MPEGrawHeader['channelmode'] = (ord($Header4Bytes[3]) & 0xC0) >> 6; // II + $MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; // JJ + $MPEGrawHeader['copyright'] = (ord($Header4Bytes[3]) & 0x08) >> 3; // K + $MPEGrawHeader['original'] = (ord($Header4Bytes[3]) & 0x04) >> 2; // L + $MPEGrawHeader['emphasis'] = (ord($Header4Bytes[3]) & 0x03); // MM + + return $MPEGrawHeader; + } + + /** + * @param int|string $bitrate + * @param string $version + * @param string $layer + * @param bool $padding + * @param int $samplerate + * + * @return int|false + */ + public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { + static $AudioFrameLengthCache = array(); + + if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { + $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false; + if ($bitrate != 'free') { + + if ($version == '1') { + + if ($layer == '1') { + + // For Layer I slot is 32 bits long + $FrameLengthCoefficient = 48; + $SlotLength = 4; + + } else { // Layer 2 / 3 + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 144; + $SlotLength = 1; + + } + + } else { // MPEG-2 / MPEG-2.5 + + if ($layer == '1') { + + // For Layer I slot is 32 bits long + $FrameLengthCoefficient = 24; + $SlotLength = 4; + + } elseif ($layer == '2') { + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 144; + $SlotLength = 1; + + } else { // layer 3 + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 72; + $SlotLength = 1; + + } + + } + + // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding + if ($samplerate > 0) { + $NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate; + $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I) + if ($padding) { + $NewFramelength += $SlotLength; + } + $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength; + } + } + } + return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; + } + + /** + * @param float|int $bit_rate + * + * @return int|float|string + */ + public static function ClosestStandardMP3Bitrate($bit_rate) { + static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); + static $bit_rate_table = array (0=>'-'); + $round_bit_rate = intval(round($bit_rate, -3)); + if (!isset($bit_rate_table[$round_bit_rate])) { + if ($round_bit_rate > max($standard_bit_rates)) { + $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate)); + } else { + $bit_rate_table[$round_bit_rate] = max($standard_bit_rates); + foreach ($standard_bit_rates as $standard_bit_rate) { + if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) { + break; + } + $bit_rate_table[$round_bit_rate] = $standard_bit_rate; + } + } + } + return $bit_rate_table[$round_bit_rate]; + } + + /** + * @param string $version + * @param string $channelmode + * + * @return int + */ + public static function XingVBRidOffset($version, $channelmode) { + static $XingVBRidOffsetCache = array(); + if (empty($XingVBRidOffsetCache)) { + $XingVBRidOffsetCache = array ( + '1' => array ('mono' => 0x15, // 4 + 17 = 21 + 'stereo' => 0x24, // 4 + 32 = 36 + 'joint stereo' => 0x24, + 'dual channel' => 0x24 + ), + + '2' => array ('mono' => 0x0D, // 4 + 9 = 13 + 'stereo' => 0x15, // 4 + 17 = 21 + 'joint stereo' => 0x15, + 'dual channel' => 0x15 + ), + + '2.5' => array ('mono' => 0x15, + 'stereo' => 0x15, + 'joint stereo' => 0x15, + 'dual channel' => 0x15 + ) + ); + } + return $XingVBRidOffsetCache[$version][$channelmode]; + } + + /** + * @param int $VBRmethodID + * + * @return string + */ + public static function LAMEvbrMethodLookup($VBRmethodID) { + static $LAMEvbrMethodLookup = array( + 0x00 => 'unknown', + 0x01 => 'cbr', + 0x02 => 'abr', + 0x03 => 'vbr-old / vbr-rh', + 0x04 => 'vbr-new / vbr-mtrh', + 0x05 => 'vbr-mt', + 0x06 => 'vbr (full vbr method 4)', + 0x08 => 'cbr (constant bitrate 2 pass)', + 0x09 => 'abr (2 pass)', + 0x0F => 'reserved' + ); + return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); + } + + /** + * @param int $StereoModeID + * + * @return string + */ + public static function LAMEmiscStereoModeLookup($StereoModeID) { + static $LAMEmiscStereoModeLookup = array( + 0 => 'mono', + 1 => 'stereo', + 2 => 'dual mono', + 3 => 'joint stereo', + 4 => 'forced stereo', + 5 => 'auto', + 6 => 'intensity stereo', + 7 => 'other' + ); + return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); + } + + /** + * @param int $SourceSampleFrequencyID + * + * @return string + */ + public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { + static $LAMEmiscSourceSampleFrequencyLookup = array( + 0 => '<= 32 kHz', + 1 => '44.1 kHz', + 2 => '48 kHz', + 3 => '> 48kHz' + ); + return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); + } + + /** + * @param int $SurroundInfoID + * + * @return string + */ + public static function LAMEsurroundInfoLookup($SurroundInfoID) { + static $LAMEsurroundInfoLookup = array( + 0 => 'no surround info', + 1 => 'DPL encoding', + 2 => 'DPL2 encoding', + 3 => 'Ambisonic encoding' + ); + return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); + } + + /** + * @param array $LAMEtag + * + * @return string + */ + public static function LAMEpresetUsedLookup($LAMEtag) { + + if ($LAMEtag['preset_used_id'] == 0) { + // no preset used (LAME >=3.93) + // no preset recorded (LAME <3.93) + return ''; + } + $LAMEpresetUsedLookup = array(); + + ///// THIS PART CANNOT BE STATIC . + for ($i = 8; $i <= 320; $i++) { + switch ($LAMEtag['vbr_method']) { + case 'cbr': + $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i; + break; + case 'abr': + default: // other VBR modes shouldn't be here(?) + $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i; + break; + } + } + + // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions() + + // named alt-presets + $LAMEpresetUsedLookup[1000] = '--r3mix'; + $LAMEpresetUsedLookup[1001] = '--alt-preset standard'; + $LAMEpresetUsedLookup[1002] = '--alt-preset extreme'; + $LAMEpresetUsedLookup[1003] = '--alt-preset insane'; + $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard'; + $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme'; + $LAMEpresetUsedLookup[1006] = '--alt-preset medium'; + $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium'; + + // LAME 3.94 additions/changes + $LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003 + $LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003 + + $LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[410] = '-V9'; + $LAMEpresetUsedLookup[420] = '-V8'; + $LAMEpresetUsedLookup[440] = '-V6'; + $LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003 + $LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[490] = '-V1'; + $LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003 + + return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org'); + } + +} diff --git a/config/getid3/module.audio.mpc.php b/config/getid3/module.audio.mpc.php new file mode 100644 index 000000000..f754f4b7a --- /dev/null +++ b/config/getid3/module.audio.mpc.php @@ -0,0 +1,549 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mpc.php // +// module for analyzing Musepack/MPEG+ Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} + +class getid3_mpc extends getid3_handler +{ + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + $info['mpc']['header'] = array(); + $thisfile_mpc_header = &$info['mpc']['header']; + + $info['fileformat'] = 'mpc'; + $info['audio']['dataformat'] = 'mpc'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only + $info['audio']['lossless'] = false; + + $this->fseek($info['avdataoffset']); + $MPCheaderData = $this->fread(4); + $info['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6) + if (preg_match('#^MPCK#', $info['mpc']['header']['preamble'])) { + + // this is SV8 + return $this->ParseMPCsv8(); + + } elseif (preg_match('#^MP\+#', $info['mpc']['header']['preamble'])) { + + // this is SV7 + return $this->ParseMPCsv7(); + + } elseif (preg_match('#^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]#s', $MPCheaderData)) { + + // this is SV4 - SV6, handle seperately + return $this->ParseMPCsv6(); + + } else { + + $this->error('Expecting "MP+" or "MPCK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($MPCheaderData, 0, 4)).'"'); + unset($info['fileformat']); + unset($info['mpc']); + return false; + + } + } + + /** + * @return bool + */ + public function ParseMPCsv8() { + // this is SV8 + // http://trac.musepack.net/trac/wiki/SV8Specification + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; + + $keyNameSize = 2; + $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" + + $offset = $this->ftell(); + while ($offset < $info['avdataend']) { + $thisPacket = array(); + $thisPacket['offset'] = $offset; + $packet_offset = 0; + + // Size is a variable-size field, could be 1-4 bytes (possibly more?) + // read enough data in and figure out the exact size later + $MPCheaderData = $this->fread($keyNameSize + $maxHandledPacketLength); + $packet_offset += $keyNameSize; + $thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize); + $thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']); + if ($thisPacket['key'] == $thisPacket['key_name']) { + $this->error('Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']); + return false; + } + $packetLength = 0; + $thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field + if ($thisPacket['packet_size'] === false) { + $this->error('Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize)); + return false; + } + $packet_offset += $packetLength; + $offset += $thisPacket['packet_size']; + + switch ($thisPacket['key']) { + case 'SH': // Stream Header + $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; + if ($moreBytesToRead > 0) { + $MPCheaderData .= $this->fread($moreBytesToRead); + } + $thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4)); + $packet_offset += 4; + $thisPacket['stream_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); + $packet_offset += 1; + + $packetLength = 0; + $thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); + $packet_offset += $packetLength; + + $packetLength = 0; + $thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); + $packet_offset += $packetLength; + + $otherUsefulData = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); + $packet_offset += 2; + $thisPacket['sample_frequency_raw'] = (($otherUsefulData & 0xE000) >> 13); + $thisPacket['max_bands_used'] = (($otherUsefulData & 0x1F00) >> 8); + $thisPacket['channels'] = (($otherUsefulData & 0x00F0) >> 4) + 1; + $thisPacket['ms_used'] = (bool) (($otherUsefulData & 0x0008) >> 3); + $thisPacket['audio_block_frames'] = (($otherUsefulData & 0x0007) >> 0); + $thisPacket['sample_frequency'] = $this->MPCfrequencyLookup($thisPacket['sample_frequency_raw']); + + $thisfile_mpc_header['mid_side_stereo'] = $thisPacket['ms_used']; + $thisfile_mpc_header['sample_rate'] = $thisPacket['sample_frequency']; + $thisfile_mpc_header['samples'] = $thisPacket['sample_count']; + $thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version']; + + $info['audio']['channels'] = $thisPacket['channels']; + $info['audio']['sample_rate'] = $thisPacket['sample_frequency']; + $info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency']; + $info['audio']['bitrate'] = getid3_lib::SafeDiv(($info['avdataend'] - $info['avdataoffset']) * 8, $info['playtime_seconds']); + break; + + case 'RG': // Replay Gain + $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; + if ($moreBytesToRead > 0) { + $MPCheaderData .= $this->fread($moreBytesToRead); + } + $thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); + $packet_offset += 1; + $thisPacket['replaygain_title_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); + $packet_offset += 2; + $thisPacket['replaygain_title_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); + $packet_offset += 2; + $thisPacket['replaygain_album_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); + $packet_offset += 2; + $thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2)); + $packet_offset += 2; + + if ($thisPacket['replaygain_title_gain']) { $info['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; } + if ($thisPacket['replaygain_title_peak']) { $info['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; } + if ($thisPacket['replaygain_album_gain']) { $info['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; } + if ($thisPacket['replaygain_album_peak']) { $info['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; } + break; + + case 'EI': // Encoder Info + $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; + if ($moreBytesToRead > 0) { + $MPCheaderData .= $this->fread($moreBytesToRead); + } + $profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); + $packet_offset += 1; + $quality_int = (($profile_pns & 0xF0) >> 4); + $quality_dec = (($profile_pns & 0x0E) >> 3); + $thisPacket['quality'] = (float) $quality_int + ($quality_dec / 8); + $thisPacket['pns_tool'] = (bool) (($profile_pns & 0x01) >> 0); + $thisPacket['version_major'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); + $packet_offset += 1; + $thisPacket['version_minor'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); + $packet_offset += 1; + $thisPacket['version_build'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); + $packet_offset += 1; + $thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build']; + + $info['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')'; + $thisfile_mpc_header['encoder_version'] = $info['audio']['encoder']; + //$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0 + $thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 + break; + + case 'SO': // Seek Table Offset + $packetLength = 0; + $thisPacket['seek_table_offset'] = $thisPacket['offset'] + $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength); + $packet_offset += $packetLength; + break; + + case 'ST': // Seek Table + case 'SE': // Stream End + case 'AP': // Audio Data + // nothing useful here, just skip this packet + $thisPacket = array(); + break; + + default: + $this->error('Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']); + return false; + } + if (!empty($thisPacket)) { + $info['mpc']['packets'][] = $thisPacket; + } + $this->fseek($offset); + } + $thisfile_mpc_header['size'] = $offset; + return true; + } + + /** + * @return bool + */ + public function ParseMPCsv7() { + // this is SV7 + // http://www.uni-jena.de/~pfk/mpp/sv8/header.html + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; + $offset = 0; + + $thisfile_mpc_header['size'] = 28; + $MPCheaderData = $info['mpc']['header']['preamble']; + $MPCheaderData .= $this->fread($thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble'])); + $offset = strlen('MP+'); + + $StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); + $offset += 1; + $thisfile_mpc_header['stream_version_major'] = ($StreamVersionByte & 0x0F) >> 0; + $thisfile_mpc_header['stream_version_minor'] = ($StreamVersionByte & 0xF0) >> 4; // should always be 0, subversions no longer exist in SV8 + $thisfile_mpc_header['frame_count'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); + $offset += 4; + + if ($thisfile_mpc_header['stream_version_major'] != 7) { + $this->error('Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')'); + return false; + } + + $FlagsDWORD1 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); + $offset += 4; + $thisfile_mpc_header['intensity_stereo'] = (bool) (($FlagsDWORD1 & 0x80000000) >> 31); + $thisfile_mpc_header['mid_side_stereo'] = (bool) (($FlagsDWORD1 & 0x40000000) >> 30); + $thisfile_mpc_header['max_subband'] = ($FlagsDWORD1 & 0x3F000000) >> 24; + $thisfile_mpc_header['raw']['profile'] = ($FlagsDWORD1 & 0x00F00000) >> 20; + $thisfile_mpc_header['begin_loud'] = (bool) (($FlagsDWORD1 & 0x00080000) >> 19); + $thisfile_mpc_header['end_loud'] = (bool) (($FlagsDWORD1 & 0x00040000) >> 18); + $thisfile_mpc_header['raw']['sample_rate'] = ($FlagsDWORD1 & 0x00030000) >> 16; + $thisfile_mpc_header['max_level'] = ($FlagsDWORD1 & 0x0000FFFF); + + $thisfile_mpc_header['raw']['title_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2)); + $offset += 2; + $thisfile_mpc_header['raw']['title_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true); + $offset += 2; + + $thisfile_mpc_header['raw']['album_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2)); + $offset += 2; + $thisfile_mpc_header['raw']['album_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true); + $offset += 2; + + $FlagsDWORD2 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); + $offset += 4; + $thisfile_mpc_header['true_gapless'] = (bool) (($FlagsDWORD2 & 0x80000000) >> 31); + $thisfile_mpc_header['last_frame_length'] = ($FlagsDWORD2 & 0x7FF00000) >> 20; + + + $thisfile_mpc_header['raw']['not_sure_what'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 3)); + $offset += 3; + $thisfile_mpc_header['raw']['encoder_version'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); + $offset += 1; + + $thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']); + $thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']); + if ($thisfile_mpc_header['sample_rate'] == 0) { + $this->error('Corrupt MPC file: frequency == zero'); + return false; + } + $info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; + $thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $info['audio']['channels']; + + $info['playtime_seconds'] = getid3_lib::SafeDiv($thisfile_mpc_header['samples'], $info['audio']['channels'] * $info['audio']['sample_rate']); + if ($info['playtime_seconds'] == 0) { + $this->error('Corrupt MPC file: playtime_seconds == zero'); + return false; + } + + // add size of file header to avdataoffset - calc bitrate correctly + MD5 data + $info['avdataoffset'] += $thisfile_mpc_header['size']; + + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + $thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak']; + $thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']); + if ($thisfile_mpc_header['raw']['title_gain'] < 0) { + $thisfile_mpc_header['title_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['title_gain']) / -100; + } else { + $thisfile_mpc_header['title_gain_db'] = (float) $thisfile_mpc_header['raw']['title_gain'] / 100; + } + + $thisfile_mpc_header['album_peak'] = $thisfile_mpc_header['raw']['album_peak']; + $thisfile_mpc_header['album_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['album_peak']); + if ($thisfile_mpc_header['raw']['album_gain'] < 0) { + $thisfile_mpc_header['album_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['album_gain']) / -100; + } else { + $thisfile_mpc_header['album_gain_db'] = (float) $thisfile_mpc_header['raw']['album_gain'] / 100;; + } + $thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']); + + $info['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db']; + $info['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db']; + + if ($thisfile_mpc_header['title_peak'] > 0) { + $info['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; + } elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) { + $info['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c + } + if ($thisfile_mpc_header['album_peak'] > 0) { + $info['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; + } + + //$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version']; + $info['audio']['encoder'] = $thisfile_mpc_header['encoder_version']; + $info['audio']['encoder_options'] = $thisfile_mpc_header['profile']; + $thisfile_mpc_header['quality'] = (float) ($thisfile_mpc_header['raw']['profile'] - 5); // values can range from 0 to 15, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0 + + return true; + } + + /** + * @return bool + */ + public function ParseMPCsv6() { + // this is SV4 - SV6 + + $info = &$this->getid3->info; + $thisfile_mpc_header = &$info['mpc']['header']; + $offset = 0; + + $thisfile_mpc_header['size'] = 8; + $this->fseek($info['avdataoffset']); + $MPCheaderData = $this->fread($thisfile_mpc_header['size']); + + // add size of file header to avdataoffset - calc bitrate correctly + MD5 data + $info['avdataoffset'] += $thisfile_mpc_header['size']; + + // Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :) + $HeaderDWORD = array(); + $HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4)); + $HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4)); + + + // DDDD DDDD CCCC CCCC BBBB BBBB AAAA AAAA + // aaaa aaaa abcd dddd dddd deee eeff ffff + // + // a = bitrate = anything + // b = IS = anything + // c = MS = anything + // d = streamversion = 0000000004 or 0000000005 or 0000000006 + // e = maxband = anything + // f = blocksize = 000001 for SV5+, anything(?) for SV4 + + $thisfile_mpc_header['target_bitrate'] = (($HeaderDWORD[0] & 0xFF800000) >> 23); + $thisfile_mpc_header['intensity_stereo'] = (bool) (($HeaderDWORD[0] & 0x00400000) >> 22); + $thisfile_mpc_header['mid_side_stereo'] = (bool) (($HeaderDWORD[0] & 0x00200000) >> 21); + $thisfile_mpc_header['stream_version_major'] = ($HeaderDWORD[0] & 0x001FF800) >> 11; + $thisfile_mpc_header['stream_version_minor'] = 0; // no sub-version numbers before SV7 + $thisfile_mpc_header['max_band'] = ($HeaderDWORD[0] & 0x000007C0) >> 6; // related to lowpass frequency, not sure how it translates exactly + $thisfile_mpc_header['block_size'] = ($HeaderDWORD[0] & 0x0000003F); + + switch ($thisfile_mpc_header['stream_version_major']) { + case 4: + $thisfile_mpc_header['frame_count'] = ($HeaderDWORD[1] >> 16); + break; + + case 5: + case 6: + $thisfile_mpc_header['frame_count'] = $HeaderDWORD[1]; + break; + + default: + $info['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead'; + unset($info['mpc']); + return false; + } + + if (($thisfile_mpc_header['stream_version_major'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) { + $this->warning('Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']); + } + + $thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7 + $info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; + $thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $info['audio']['channels']; + + if ($thisfile_mpc_header['target_bitrate'] == 0) { + $info['audio']['bitrate_mode'] = 'vbr'; + } else { + $info['audio']['bitrate_mode'] = 'cbr'; + } + + $info['mpc']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152; + $info['audio']['bitrate'] = $info['mpc']['bitrate']; + $info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major']; + + return true; + } + + /** + * @param int $profileid + * + * @return string + */ + public function MPCprofileNameLookup($profileid) { + static $MPCprofileNameLookup = array( + 0 => 'no profile', + 1 => 'Experimental', + 2 => 'unused', + 3 => 'unused', + 4 => 'unused', + 5 => 'below Telephone (q = 0.0)', + 6 => 'below Telephone (q = 1.0)', + 7 => 'Telephone (q = 2.0)', + 8 => 'Thumb (q = 3.0)', + 9 => 'Radio (q = 4.0)', + 10 => 'Standard (q = 5.0)', + 11 => 'Extreme (q = 6.0)', + 12 => 'Insane (q = 7.0)', + 13 => 'BrainDead (q = 8.0)', + 14 => 'above BrainDead (q = 9.0)', + 15 => 'above BrainDead (q = 10.0)' + ); + return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid'); + } + + /** + * @param int $frequencyid + * + * @return int|string + */ + public function MPCfrequencyLookup($frequencyid) { + static $MPCfrequencyLookup = array( + 0 => 44100, + 1 => 48000, + 2 => 37800, + 3 => 32000 + ); + return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid'); + } + + /** + * @param int $intvalue + * + * @return float|false + */ + public function MPCpeakDBLookup($intvalue) { + if ($intvalue > 0) { + return ((log10($intvalue) / log10(2)) - 15) * 6; + } + return false; + } + + /** + * @param int $encoderversion + * + * @return string + */ + public function MPCencoderVersionLookup($encoderversion) { + //Encoder version * 100 (106 = 1.06) + //EncoderVersion % 10 == 0 Release (1.0) + //EncoderVersion % 2 == 0 Beta (1.06) + //EncoderVersion % 2 == 1 Alpha (1.05a...z) + + if ($encoderversion == 0) { + // very old version, not known exactly which + return 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05'; + } + + if (($encoderversion % 10) == 0) { + + // release version + return number_format($encoderversion / 100, 2); + + } elseif (($encoderversion % 2) == 0) { + + // beta version + return number_format($encoderversion / 100, 2).' beta'; + + } + + // alpha version + return number_format($encoderversion / 100, 2).' alpha'; + } + + /** + * @param string $data + * @param int $packetLength + * @param int $maxHandledPacketLength + * + * @return int|false + */ + public function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) { + $packet_size = 0; + for ($packetLength = 1; $packetLength <= $maxHandledPacketLength; $packetLength++) { + // variable-length size field: + // bits, big-endian + // 0xxx xxxx - value 0 to 2^7-1 + // 1xxx xxxx 0xxx xxxx - value 0 to 2^14-1 + // 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^21-1 + // 1xxx xxxx 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^28-1 + // ... + $thisbyte = ord(substr($data, ($packetLength - 1), 1)); + // look through bytes until find a byte with MSB==0 + $packet_size = ($packet_size << 7); + $packet_size = ($packet_size | ($thisbyte & 0x7F)); + if (($thisbyte & 0x80) === 0) { + break; + } + if ($packetLength >= $maxHandledPacketLength) { + return false; + } + } + return $packet_size; + } + + /** + * @param string $packetKey + * + * @return string + */ + public function MPCsv8PacketName($packetKey) { + static $MPCsv8PacketName = array(); + if (empty($MPCsv8PacketName)) { + $MPCsv8PacketName = array( + 'AP' => 'Audio Packet', + 'CT' => 'Chapter Tag', + 'EI' => 'Encoder Info', + 'RG' => 'Replay Gain', + 'SE' => 'Stream End', + 'SH' => 'Stream Header', + 'SO' => 'Seek Table Offset', + 'ST' => 'Seek Table', + ); + } + return (isset($MPCsv8PacketName[$packetKey]) ? $MPCsv8PacketName[$packetKey] : $packetKey); + } +} diff --git a/config/getid3/module.audio.ogg.php b/config/getid3/module.audio.ogg.php new file mode 100644 index 000000000..ebd2b946c --- /dev/null +++ b/config/getid3/module.audio.ogg.php @@ -0,0 +1,925 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.ogg.php // +// module for analyzing Ogg Vorbis, OggFLAC and Speex files // +// dependencies: module.audio.flac.php // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); + +class getid3_ogg extends getid3_handler +{ + /** + * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html + * + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'ogg'; + + // Warn about illegal tags - only vorbiscomments are allowed + if (isset($info['id3v2'])) { + $this->warning('Illegal ID3v2 tag present.'); + } + if (isset($info['id3v1'])) { + $this->warning('Illegal ID3v1 tag present.'); + } + if (isset($info['ape'])) { + $this->warning('Illegal APE tag present.'); + } + + + // Page 1 - Stream Header + + $this->fseek($info['avdataoffset']); + + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + if ($this->ftell() >= $this->getid3->fread_buffer_size()) { + $this->error('Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)'); + unset($info['fileformat']); + unset($info['ogg']); + return false; + } + + $filedata = $this->fread($oggpageinfo['page_length']); + $filedataoffset = 0; + + if (substr($filedata, 0, 4) == 'fLaC') { + + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + } elseif (substr($filedata, 1, 6) == 'vorbis') { + + $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); + + } elseif (substr($filedata, 0, 8) == 'OpusHead') { + + if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) { + return false; + } + + } elseif (substr($filedata, 0, 8) == 'Speex ') { + + // http://www.speex.org/manual/node10.html + + $info['audio']['dataformat'] = 'speex'; + $info['mime_type'] = 'audio/speex'; + $info['audio']['bitrate_mode'] = 'abr'; + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' + $filedataoffset += 8; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); + $filedataoffset += 20; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + + $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); + $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; + $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; + $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; + $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); + + $info['audio']['sample_rate'] = $info['speex']['sample_rate']; + $info['audio']['channels'] = $info['speex']['channels']; + if ($info['speex']['vbr']) { + $info['audio']['bitrate_mode'] = 'vbr'; + } + + } elseif (substr($filedata, 0, 7) == "\x80".'theora') { + + // http://www.theora.org/doc/Theora.pdf (section 6.2) + + $info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora' + $filedataoffset += 7; + $info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + + $info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10; + $info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5; + $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3; + $info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0 + $info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']); + $info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']); + + $info['video']['dataformat'] = 'theora'; + $info['mime_type'] = 'video/ogg'; + //$info['audio']['bitrate_mode'] = 'abr'; + //$info['audio']['lossless'] = false; + $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x']; + $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y']; + if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) { + $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator']; + } + if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) { + $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator']; + } +$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'); + + + } elseif (substr($filedata, 0, 8) == "fishead\x00") { + + // Ogg Skeleton version 3.0 Format Specification + // http://xiph.org/ogg/doc/skeleton.html + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20)); + $filedataoffset += 20; + + $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor']; + $info['ogg']['skeleton']['fishead']['presentationtime'] = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']); + $info['ogg']['skeleton']['fishead']['basetime'] = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']); + $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc']; + + + $counter = 0; + do { + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; + $filedata = $this->fread($oggpageinfo['page_length']); + $this->fseek($oggpageinfo['page_end_offset']); + + if (substr($filedata, 0, 8) == "fisbone\x00") { + + $filedataoffset = 8; + $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3); + $filedataoffset += 3; + + } elseif (substr($filedata, 1, 6) == 'theora') { + + $info['video']['dataformat'] = 'theora1'; + $this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']'); + //break; + + } elseif (substr($filedata, 1, 6) == 'vorbis') { + + $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); + + } else { + $this->error('unexpected'); + //break; + } + //} while ($oggpageinfo['page_seqno'] == 0); + } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); + + $this->fseek($oggpageinfo['page_start_offset']); + + $this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'); + //return false; + + } elseif (substr($filedata, 0, 5) == "\x7F".'FLAC') { + // https://xiph.org/flac/ogg_mapping.html + + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + $info['ogg']['flac']['header']['version_major'] = ord(substr($filedata, 5, 1)); + $info['ogg']['flac']['header']['version_minor'] = ord(substr($filedata, 6, 1)); + $info['ogg']['flac']['header']['header_packets'] = getid3_lib::BigEndian2Int(substr($filedata, 7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams." + $info['ogg']['flac']['header']['magic'] = substr($filedata, 9, 4); + if ($info['ogg']['flac']['header']['magic'] != 'fLaC') { + $this->error('Ogg-FLAC expecting "fLaC", found "'.$info['ogg']['flac']['header']['magic'].'" ('.trim(getid3_lib::PrintHexBytes($info['ogg']['flac']['header']['magic'])).')'); + return false; + } + $info['ogg']['flac']['header']['STREAMINFO_bytes'] = getid3_lib::BigEndian2Int(substr($filedata, 13, 4)); + $info['flac']['STREAMINFO'] = getid3_flac::parseSTREAMINFOdata(substr($filedata, 17, 34)); + if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; + $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + $info['playtime_seconds'] = getid3_lib::SafeDiv($info['flac']['STREAMINFO']['samples_stream'], $info['flac']['STREAMINFO']['sample_rate']); + } + + } else { + + $this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"'); + unset($info['ogg']); + unset($info['mime_type']); + return false; + + } + + // Page 2 - Comment Header + $oggpageinfo = $this->ParseOggPageHeader(); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + switch ($info['audio']['dataformat']) { + case 'vorbis': + $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' + + $this->ParseVorbisComments(); + break; + + case 'flac': + $flac = new getid3_flac($this->getid3); + if (!$flac->parseMETAdata()) { + $this->error('Failed to parse FLAC headers'); + return false; + } + unset($flac); + break; + + case 'speex': + $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); + $this->ParseVorbisComments(); + break; + + case 'opus': + $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags' + if(substr($filedata, 0, 8) != 'OpusTags') { + $this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"'); + return false; + } + + $this->ParseVorbisComments(); + break; + + } + + // Last Page - Number of Samples + if (!getid3_lib::intValueSupported($info['avdataend'])) { + + $this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'); + + } else { + + $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0)); + $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size())); + if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { + $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO'))); + $info['avdataend'] = $this->ftell(); + $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); + $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; + if ($info['ogg']['samples'] == 0) { + $this->error('Corrupt Ogg file: eos.number of samples == zero'); + return false; + } + if (!empty($info['audio']['sample_rate'])) { + $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) * $info['audio']['sample_rate'] / $info['ogg']['samples']; + } + } + + } + + if (!empty($info['ogg']['bitrate_average'])) { + $info['audio']['bitrate'] = $info['ogg']['bitrate_average']; + } elseif (!empty($info['ogg']['bitrate_nominal'])) { + $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal']; + } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) { + $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2; + } + if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) { + if ($info['audio']['bitrate'] == 0) { + $this->error('Corrupt Ogg file: bitrate_audio == zero'); + return false; + } + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']); + } + + if (isset($info['ogg']['vendor'])) { + $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']); + + // Vorbis only + if ($info['audio']['dataformat'] == 'vorbis') { + + // Vorbis 1.0 starts with Xiph.Org + if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) { + + if ($info['audio']['bitrate_mode'] == 'abr') { + + // Set -b 128 on abr files + $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000); + + } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) { + // Set -q N on vbr files + $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']); + + } + } + + if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) { + $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps'; + } + } + } + + return true; + } + + /** + * @param string $filedata + * @param int $filedataoffset + * @param array $oggpageinfo + * + * @return bool + */ + public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { + $info = &$this->getid3->info; + $info['audio']['dataformat'] = 'vorbis'; + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' + $filedataoffset += 6; + $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['audio']['channels'] = $info['ogg']['numberofchannels']; + $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + if ($info['ogg']['samplerate'] == 0) { + $this->error('Corrupt Ogg file: sample rate == zero'); + return false; + } + $info['audio']['sample_rate'] = $info['ogg']['samplerate']; + $info['ogg']['samples'] = 0; // filled in later + $info['ogg']['bitrate_average'] = 0; // filled in later + $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); + $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); + $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet + + $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr + if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) { + unset($info['ogg']['bitrate_max']); + $info['audio']['bitrate_mode'] = 'abr'; + } + if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { + unset($info['ogg']['bitrate_nominal']); + } + if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) { + unset($info['ogg']['bitrate_min']); + $info['audio']['bitrate_mode'] = 'abr'; + } + return true; + } + + /** + * @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 + * + * @param string $filedata + * @param int $filedataoffset + * @param array $oggpageinfo + * + * @return bool + */ + public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { + $info = &$this->getid3->info; + $info['audio']['dataformat'] = 'opus'; + $info['mime_type'] = 'audio/ogg; codecs=opus'; + + /** @todo find a usable way to detect abr (vbr that is padded to be abr) */ + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead' + $filedataoffset += 8; + $info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + + if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) { + $this->error('Unknown opus version number (only accepting 1-15)'); + return false; + } + + $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + + if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) { + $this->error('Invalid channel count in opus header (must not be zero)'); + return false; + } + + $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + + $info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + + //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + //$filedataoffset += 2; + + //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + //$filedataoffset += 1; + + $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; + $info['opus']['sample_rate_input'] = $info['ogg']['pageheader']['opus']['input_sample_rate']; + $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; + + $info['audio']['channels'] = $info['opus']['out_channel_count']; + $info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input']; + $info['audio']['sample_rate'] = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html + return true; + } + + /** + * @return array|false + */ + public function ParseOggPageHeader() { + // http://xiph.org/ogg/vorbis/doc/framing.html + $oggheader = array(); + $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file + + $filedata = $this->fread($this->getid3->fread_buffer_size()); + $filedataoffset = 0; + while (substr($filedata, $filedataoffset++, 4) != 'OggS') { + if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { + // should be found before here + return false; + } + if (($filedataoffset + 28) > strlen($filedata)) { + if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) { + // get some more data, unless eof, in which case fail + return false; + } + } + } + $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' + + $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet + $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) + $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) + + $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['page_length'] = 0; + for ($i = 0; $i < $oggheader['page_segments']; $i++) { + $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['page_length'] += $oggheader['segment_table'][$i]; + } + $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; + $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; + $this->fseek($oggheader['header_end_offset']); + + return $oggheader; + } + + /** + * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 + * + * @return bool + */ + public function ParseVorbisComments() { + $info = &$this->getid3->info; + + $OriginalOffset = $this->ftell(); + $commentdata = null; + $commentdataoffset = 0; + $VorbisCommentPage = 1; + $CommentStartOffset = 0; + + switch ($info['audio']['dataformat']) { + case 'vorbis': + case 'speex': + case 'opus': + $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + $this->fseek($CommentStartOffset); + $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + + if ($info['audio']['dataformat'] == 'vorbis') { + $commentdataoffset += (strlen('vorbis') + 1); + } + else if ($info['audio']['dataformat'] == 'opus') { + $commentdataoffset += strlen('OpusTags'); + } + + break; + + case 'flac': + $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; + $this->fseek($CommentStartOffset); + $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); + break; + + default: + return false; + } + + $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + $commentdataoffset += 4; + + $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); + $commentdataoffset += $VendorSize; + + $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + $commentdataoffset += 4; + $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; + + $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); + $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; + for ($i = 0; $i < $CommentsCount; $i++) { + + if ($i >= 10000) { + // https://github.com/owncloud/music/issues/212#issuecomment-43082336 + $this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments'); + break; + } + + $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; + + if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { + if ($oggpageinfo = $this->ParseOggPageHeader()) { + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + $VorbisCommentPage++; + + // First, save what we haven't read yet + $AsYetUnusedData = substr($commentdata, $commentdataoffset); + + // Then take that data off the end + $commentdata = substr($commentdata, 0, $commentdataoffset); + + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct + $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + + // Finally, stick the unused data back on the end + $commentdata .= $AsYetUnusedData; + + //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); + } + + } + $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + + // replace avdataoffset with position just after the last vorbiscomment + $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; + + $commentdataoffset += 4; + while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { + if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { + $this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'); + break 2; + } + + $VorbisCommentPage++; + + if ($oggpageinfo = $this->ParseOggPageHeader()) { + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + // First, save what we haven't read yet + $AsYetUnusedData = substr($commentdata, $commentdataoffset); + + // Then take that data off the end + $commentdata = substr($commentdata, 0, $commentdataoffset); + + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct + $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + + // Finally, stick the unused data back on the end + $commentdata .= $AsYetUnusedData; + + //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { + $this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell()); + break; + } + $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); + if ($readlength <= 0) { + $this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell()); + break; + } + $commentdata .= $this->fread($readlength); + + //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; + } else { + $this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell()); + break; + } + } + $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; + $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); + $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; + + if (!$commentstring) { + + // no comment? + $this->warning('Blank Ogg comment ['.$i.']'); + + } elseif (strstr($commentstring, '=')) { + + $commentexploded = explode('=', $commentstring, 2); + $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); + $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); + + if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { + + // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE + // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard. + // http://flac.sourceforge.net/format.html#metadata_block_picture + $flac = new getid3_flac($this->getid3); + $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); + $flac->parsePICTURE(); + $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; + unset($flac); + + } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { + + $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); + $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); + /** @todo use 'coverartmime' where available */ + $imageinfo = getid3_lib::GetDataImageSize($data); + if ($imageinfo === false || !isset($imageinfo['mime'])) { + $this->warning('COVERART vorbiscomment tag contains invalid image'); + continue; + } + + $ogg = new self($this->getid3); + $ogg->setStringMode($data); + $info['ogg']['comments']['picture'][] = array( + 'image_mime' => $imageinfo['mime'], + 'datalength' => strlen($data), + 'picturetype' => 'cover art', + 'image_height' => $imageinfo['height'], + 'image_width' => $imageinfo['width'], + 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), + ); + unset($ogg); + + } else { + + $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; + + } + + } else { + + $this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring); + + } + unset($ThisFileInfo_ogg_comments_raw[$i]); + } + unset($ThisFileInfo_ogg_comments_raw); + + + // Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/ + if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { + foreach ($info['ogg']['comments'] as $index => $commentvalue) { + switch ($index) { + case 'rg_audiophile': + case 'replaygain_album_gain': + $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'rg_radio': + case 'replaygain_track_gain': + $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'replaygain_album_peak': + $info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'rg_peak': + case 'replaygain_track_peak': + $info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + case 'replaygain_reference_loudness': + $info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; + unset($info['ogg']['comments'][$index]); + break; + + default: + // do nothing + break; + } + } + } + + $this->fseek($OriginalOffset); + + return true; + } + + /** + * @param int $mode + * + * @return string|null + */ + public static function SpeexBandModeLookup($mode) { + static $SpeexBandModeLookup = array(); + if (empty($SpeexBandModeLookup)) { + $SpeexBandModeLookup[0] = 'narrow'; + $SpeexBandModeLookup[1] = 'wide'; + $SpeexBandModeLookup[2] = 'ultra-wide'; + } + return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); + } + + /** + * @param array $OggInfoArray + * @param int $SegmentNumber + * + * @return int + */ + public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { + $segmentlength = 0; + for ($i = 0; $i < $SegmentNumber; $i++) { + $segmentlength = 0; + foreach ($OggInfoArray['segment_table'] as $key => $value) { + $segmentlength += $value; + if ($value < 255) { + break; + } + } + } + return $segmentlength; + } + + /** + * @param int $nominal_bitrate + * + * @return float + */ + public static function get_quality_from_nominal_bitrate($nominal_bitrate) { + + // decrease precision + $nominal_bitrate = $nominal_bitrate / 1000; + + if ($nominal_bitrate < 128) { + // q-1 to q4 + $qval = ($nominal_bitrate - 64) / 16; + } elseif ($nominal_bitrate < 256) { + // q4 to q8 + $qval = $nominal_bitrate / 32; + } elseif ($nominal_bitrate < 320) { + // q8 to q9 + $qval = ($nominal_bitrate + 256) / 64; + } else { + // q9 to q10 + $qval = ($nominal_bitrate + 1300) / 180; + } + //return $qval; // 5.031324 + //return intval($qval); // 5 + return round($qval, 1); // 5 or 4.9 + } + + /** + * @param int $colorspace_id + * + * @return string|null + */ + public static function TheoraColorSpace($colorspace_id) { + // http://www.theora.org/doc/Theora.pdf (table 6.3) + static $TheoraColorSpaceLookup = array(); + if (empty($TheoraColorSpaceLookup)) { + $TheoraColorSpaceLookup[0] = 'Undefined'; + $TheoraColorSpaceLookup[1] = 'Rec. 470M'; + $TheoraColorSpaceLookup[2] = 'Rec. 470BG'; + $TheoraColorSpaceLookup[3] = 'Reserved'; + } + return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); + } + + /** + * @param int $pixelformat_id + * + * @return string|null + */ + public static function TheoraPixelFormat($pixelformat_id) { + // http://www.theora.org/doc/Theora.pdf (table 6.4) + static $TheoraPixelFormatLookup = array(); + if (empty($TheoraPixelFormatLookup)) { + $TheoraPixelFormatLookup[0] = '4:2:0'; + $TheoraPixelFormatLookup[1] = 'Reserved'; + $TheoraPixelFormatLookup[2] = '4:2:2'; + $TheoraPixelFormatLookup[3] = '4:4:4'; + } + return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null); + } + +} diff --git a/config/getid3/module.audio.wavpack.php b/config/getid3/module.audio.wavpack.php new file mode 100644 index 000000000..53602c33c --- /dev/null +++ b/config/getid3/module.audio.wavpack.php @@ -0,0 +1,424 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.wavpack.php // +// module for analyzing WavPack v4.0+ Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} +class getid3_wavpack extends getid3_handler +{ + /** + * Avoid scanning all frames (break after finding ID_RIFF_HEADER and ID_CONFIG_BLOCK, + * significantly faster for very large files but other data may be missed + * + * @var bool + */ + public $quick_parsing = false; + + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + $this->fseek($info['avdataoffset']); + + $found_blocks = array(); + while (true) { + + $wavpackheader = $this->fread(32); + + if ($this->ftell() >= $info['avdataend']) { + break; + } elseif ($this->feof()) { + break; + } elseif ( + isset($info['wavpack']['blockheader']['total_samples']) && + isset($info['wavpack']['blockheader']['block_samples']) && + ($info['wavpack']['blockheader']['total_samples'] > 0) && + ($info['wavpack']['blockheader']['block_samples'] > 0) && + (!isset($info['wavpack']['riff_trailer_size']) || ($info['wavpack']['riff_trailer_size'] <= 0)) && + ((isset($info['wavpack']['config_flags']['md5_checksum']) && ($info['wavpack']['config_flags']['md5_checksum'] === false)) || !empty($info['md5_data_source']))) { + break; + } + + $blockheader_offset = $this->ftell() - 32; + $blockheader_magic = substr($wavpackheader, 0, 4); + $blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4)); + + $magic = 'wvpk'; + if ($blockheader_magic != $magic) { + $this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$blockheader_offset.', found "'.getid3_lib::PrintHexBytes($blockheader_magic).'"'); + switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { + case 'wavpack': + case 'wvc': + break; + default: + unset($info['fileformat']); + unset($info['audio']); + unset($info['wavpack']); + break; + } + return false; + } + + if (empty($info['wavpack']['blockheader']['block_samples']) || + empty($info['wavpack']['blockheader']['total_samples']) || + ($info['wavpack']['blockheader']['block_samples'] <= 0) || + ($info['wavpack']['blockheader']['total_samples'] <= 0)) { + // Also, it is possible that the first block might not have + // any samples (block_samples == 0) and in this case you should skip blocks + // until you find one with samples because the other information (like + // total_samples) are not guaranteed to be correct until (block_samples > 0) + + // Finally, I have defined a format for files in which the length is not known + // (for example when raw files are created using pipes). In these cases + // total_samples will be -1 and you must seek to the final block to determine + // the total number of samples. + + + $info['audio']['dataformat'] = 'wavpack'; + $info['fileformat'] = 'wavpack'; + $info['audio']['lossless'] = true; + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['wavpack']['blockheader']['offset'] = $blockheader_offset; + $info['wavpack']['blockheader']['magic'] = $blockheader_magic; + $info['wavpack']['blockheader']['size'] = $blockheader_size; + + if ($info['wavpack']['blockheader']['size'] >= 0x100000) { + $this->error('Expecting WavPack block size less than "0x100000", found "'.$info['wavpack']['blockheader']['size'].'" at offset '.$info['wavpack']['blockheader']['offset']); + switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { + case 'wavpack': + case 'wvc': + break; + default: + unset($info['fileformat']); + unset($info['audio']); + unset($info['wavpack']); + break; + } + return false; + } + + $info['wavpack']['blockheader']['minor_version'] = ord($wavpackheader[8]); + $info['wavpack']['blockheader']['major_version'] = ord($wavpackheader[9]); + + if (($info['wavpack']['blockheader']['major_version'] != 4) || + (($info['wavpack']['blockheader']['minor_version'] < 4) && + ($info['wavpack']['blockheader']['minor_version'] > 16))) { + $this->error('Expecting WavPack version between "4.2" and "4.16", found version "'.$info['wavpack']['blockheader']['major_version'].'.'.$info['wavpack']['blockheader']['minor_version'].'" at offset '.$info['wavpack']['blockheader']['offset']); + switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') { + case 'wavpack': + case 'wvc': + break; + default: + unset($info['fileformat']); + unset($info['audio']); + unset($info['wavpack']); + break; + } + return false; + } + + $info['wavpack']['blockheader']['track_number'] = ord($wavpackheader[10]); // unused + $info['wavpack']['blockheader']['index_number'] = ord($wavpackheader[11]); // unused + $info['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4)); + $info['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4)); + $info['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4)); + $info['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4)); + $info['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4)); + + $info['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($info['wavpack']['blockheader']['flags_raw'] & 0x00000003); + $info['wavpack']['blockheader']['flags']['mono'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000004); + $info['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000008); + $info['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000010); + $info['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000020); + $info['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000040); + $info['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000080); + $info['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000100); + $info['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000200); + $info['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000400); + $info['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000800); + $info['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00001000); + + $info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid']; + } + + while (!$this->feof() && ($this->ftell() < ($blockheader_offset + $blockheader_size + 8))) { + + $metablock = array('offset'=>$this->ftell()); + $metablockheader = $this->fread(2); + if ($this->feof()) { + break; + } + $metablock['id'] = ord($metablockheader[0]); + $metablock['function_id'] = ($metablock['id'] & 0x3F); + $metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']); + $found_blocks[$metablock['function_name']] = (isset($found_blocks[$metablock['function_name']]) ? $found_blocks[$metablock['function_name']] : 0) + 1; // cannot use getid3_lib::safe_inc without warnings(?) + + // The 0x20 bit in the id of the meta subblocks (which is defined as + // ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that + // if a decoder encounters an id that it does not know about, it uses + // that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set + // then the decoder simply ignores the metadata, but if it is zero + // then the decoder should quit because it means that an understanding + // of the metadata is required to correctly decode the audio. + $metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20); + + $metablock['padded_data'] = (bool) ($metablock['id'] & 0x40); + $metablock['large_block'] = (bool) ($metablock['id'] & 0x80); + if ($metablock['large_block']) { + $metablockheader .= $this->fread(2); + } + $metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words + $metablock['data'] = null; + $metablock['comments'] = array(); + + if ($metablock['size'] > 0) { + + switch ($metablock['function_id']) { + case 0x21: // ID_RIFF_HEADER + case 0x22: // ID_RIFF_TRAILER + case 0x23: // ID_REPLAY_GAIN + case 0x24: // ID_CUESHEET + case 0x25: // ID_CONFIG_BLOCK + case 0x26: // ID_MD5_CHECKSUM + $metablock['data'] = $this->fread($metablock['size']); + + if ($metablock['padded_data']) { + // padded to the nearest even byte + $metablock['size']--; + $metablock['data'] = substr($metablock['data'], 0, -1); + } + break; + + case 0x00: // ID_DUMMY + case 0x01: // ID_ENCODER_INFO + case 0x02: // ID_DECORR_TERMS + case 0x03: // ID_DECORR_WEIGHTS + case 0x04: // ID_DECORR_SAMPLES + case 0x05: // ID_ENTROPY_VARS + case 0x06: // ID_HYBRID_PROFILE + case 0x07: // ID_SHAPING_WEIGHTS + case 0x08: // ID_FLOAT_INFO + case 0x09: // ID_INT32_INFO + case 0x0A: // ID_WV_BITSTREAM + case 0x0B: // ID_WVC_BITSTREAM + case 0x0C: // ID_WVX_BITSTREAM + case 0x0D: // ID_CHANNEL_INFO + $this->fseek($metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size']); + break; + + default: + $this->warning('Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset']); + $this->fseek($metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size']); + break; + } + + switch ($metablock['function_id']) { + case 0x21: // ID_RIFF_HEADER + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + $original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4)); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->ParseRIFFdata($metablock['data']); + $metablock['riff'] = $getid3_temp->info['riff']; + $info['audio']['sample_rate'] = $getid3_temp->info['riff']['raw']['fmt ']['nSamplesPerSec']; + unset($getid3_riff, $getid3_temp); + + $metablock['riff']['original_filesize'] = $original_wav_filesize; + $info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size']; + $info['playtime_seconds'] = getid3_lib::SafeDiv($info['wavpack']['blockheader']['total_samples'], $info['audio']['sample_rate']); + + // Safe RIFF header in case there's a RIFF footer later + $metablockRIFFheader = $metablock['data']; + if ($this->quick_parsing && !empty($found_blocks['RIFF header']) && !empty($found_blocks['Config Block'])) { + $this->warning('WavPack quick-parsing enabled (faster at parsing large fules, but may miss some data)'); + break 2; + } + break; + + + case 0x22: // ID_RIFF_TRAILER + $metablockRIFFfooter = isset($metablockRIFFheader) ? $metablockRIFFheader : ''.$metablock['data']; + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + + $startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); + $getid3_temp->info['avdataend'] = $info['avdataend']; + //$getid3_temp->info['fileformat'] = 'riff'; + $getid3_riff = new getid3_riff($getid3_temp); + $metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']); + + if (!empty($metablock['riff']['INFO'])) { + getid3_riff::parseComments($metablock['riff']['INFO'], $metablock['comments']); + $info['tags']['riff'] = $metablock['comments']; + } + unset($getid3_temp, $getid3_riff); + break; + + + case 0x23: // ID_REPLAY_GAIN + $this->warning('WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']); + break; + + + case 0x24: // ID_CUESHEET + $this->warning('WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']); + break; + + + case 0x25: // ID_CONFIG_BLOCK + $metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3)); + + $metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x000001); // "adobe" mode for 32-bit floats + $metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000002); // fast mode + $metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000004); // double fast + $metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x000008); // high quality mode + $metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x000010); // double high (not used yet) + $metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x000020); // bitrate is kbps, not bits / sample + $metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x000040); // automatic noise shaping + $metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x000080); // shaping mode specified + $metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x000100); // joint-stereo mode specified + $metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x000200); // copy file-time from source + $metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x000400); // create executable + $metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x000800); // create correction file + $metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x001000); // maximize bybrid compression + $metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x002000); // psychoacoustic quality mode + $metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x004000); // raw mode (not implemented yet) + $metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x008000); // calc noise in hybrid mode + $metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x010000); // obsolete (for information) + $metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x020000); // extra processing mode + $metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x040000); // no wvx stream w/ floats & big ints + $metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x080000); // compute & store MD5 signature + $metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress % + + $info['wavpack']['config_flags'] = $metablock['flags']; + + + $info['audio']['encoder_options'] = ''; + if ($info['wavpack']['blockheader']['flags']['hybrid']) { + $info['audio']['encoder_options'] .= ' -b???'; + } + $info['audio']['encoder_options'] .= ($metablock['flags']['adobe_mode'] ? ' -a' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['optimize_wvc'] ? ' -cc' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['create_exe'] ? ' -e' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['fast_flag'] ? ' -f' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['joint_override'] ? ' -j?' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['high_flag'] ? ' -h' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['md5_checksum'] ? ' -m' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['calc_noise'] ? ' -n' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['shape_override'] ? ' -s?' : ''); + $info['audio']['encoder_options'] .= ($metablock['flags']['extra_mode'] ? ' -x?' : ''); + if (!empty($info['audio']['encoder_options'])) { + $info['audio']['encoder_options'] = trim($info['audio']['encoder_options']); + } elseif (isset($info['audio']['encoder_options'])) { + unset($info['audio']['encoder_options']); + } + if ($this->quick_parsing && !empty($found_blocks['RIFF header']) && !empty($found_blocks['Config Block'])) { + $this->warning('WavPack quick-parsing enabled (faster at parsing large fules, but may miss some data)'); + break 2; + } + break; + + + case 0x26: // ID_MD5_CHECKSUM + if (strlen($metablock['data']) == 16) { + $info['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false)); + } else { + $this->warning('Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes'); + } + break; + + + case 0x00: // ID_DUMMY + case 0x01: // ID_ENCODER_INFO + case 0x02: // ID_DECORR_TERMS + case 0x03: // ID_DECORR_WEIGHTS + case 0x04: // ID_DECORR_SAMPLES + case 0x05: // ID_ENTROPY_VARS + case 0x06: // ID_HYBRID_PROFILE + case 0x07: // ID_SHAPING_WEIGHTS + case 0x08: // ID_FLOAT_INFO + case 0x09: // ID_INT32_INFO + case 0x0A: // ID_WV_BITSTREAM + case 0x0B: // ID_WVC_BITSTREAM + case 0x0C: // ID_WVX_BITSTREAM + case 0x0D: // ID_CHANNEL_INFO + unset($metablock); + break; + } + + } + if (!empty($metablock)) { + $info['wavpack']['metablocks'][] = $metablock; + } + + } + + } + + $info['audio']['encoder'] = 'WavPack v'.$info['wavpack']['blockheader']['major_version'].'.'.str_pad($info['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT); + $info['audio']['bits_per_sample'] = $info['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8; + $info['audio']['channels'] = ($info['wavpack']['blockheader']['flags']['mono'] ? 1 : 2); + + if (!empty($info['playtime_seconds'])) { + + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + } else { + + $info['audio']['dataformat'] = 'wvc'; + + } + return true; + } + + /** + * @param int $id + * + * @return string + */ + public function WavPackMetablockNameLookup(&$id) { + static $WavPackMetablockNameLookup = array( + 0x00 => 'Dummy', + 0x01 => 'Encoder Info', + 0x02 => 'Decorrelation Terms', + 0x03 => 'Decorrelation Weights', + 0x04 => 'Decorrelation Samples', + 0x05 => 'Entropy Variables', + 0x06 => 'Hybrid Profile', + 0x07 => 'Shaping Weights', + 0x08 => 'Float Info', + 0x09 => 'Int32 Info', + 0x0A => 'WV Bitstream', + 0x0B => 'WVC Bitstream', + 0x0C => 'WVX Bitstream', + 0x0D => 'Channel Info', + 0x21 => 'RIFF header', + 0x22 => 'RIFF trailer', + 0x23 => 'Replay Gain', + 0x24 => 'Cuesheet', + 0x25 => 'Config Block', + 0x26 => 'MD5 Checksum', + ); + return (isset($WavPackMetablockNameLookup[$id]) ? $WavPackMetablockNameLookup[$id] : ''); + } + +} diff --git a/config/getid3/module.tag.apetag.php b/config/getid3/module.tag.apetag.php new file mode 100644 index 000000000..1305cfb50 --- /dev/null +++ b/config/getid3/module.tag.apetag.php @@ -0,0 +1,454 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.tag.apetag.php // +// module for analyzing APE tags // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} + +class getid3_apetag extends getid3_handler +{ + /** + * true: return full data for all attachments; + * false: return no data for all attachments; + * integer: return data for attachments <= than this; + * string: save as file to this directory. + * + * @var int|bool|string + */ + public $inline_attachments = true; + + public $overrideendoffset = 0; + + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + if (!getid3_lib::intValueSupported($info['filesize'])) { + $this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); + return false; + } + + $id3v1tagsize = 128; + $apetagheadersize = 32; + $lyrics3tagsize = 10; + + if ($this->overrideendoffset == 0) { + + $this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); + $APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize); + + //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) { + if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') { + + // APE tag found before ID3v1 + $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize; + + //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) { + } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') { + + // APE tag found, no ID3v1 + $info['ape']['tag_offset_end'] = $info['filesize']; + + } + + } else { + + $this->fseek($this->overrideendoffset - $apetagheadersize); + if ($this->fread(8) == 'APETAGEX') { + $info['ape']['tag_offset_end'] = $this->overrideendoffset; + } + + } + if (!isset($info['ape']['tag_offset_end'])) { + + // APE tag not found + unset($info['ape']); + return false; + + } + + // shortcut + $thisfile_ape = &$info['ape']; + + $this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize); + $APEfooterData = $this->fread(32); + if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) { + $this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']); + return false; + } + + if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { + $this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize); + $thisfile_ape['tag_offset_start'] = $this->ftell(); + $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); + } else { + $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize']; + $this->fseek($thisfile_ape['tag_offset_start']); + $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']); + } + $info['avdataend'] = $thisfile_ape['tag_offset_start']; + + if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) { + $this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data'); + unset($info['id3v1']); + foreach ($info['warning'] as $key => $value) { + if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { + unset($info['warning'][$key]); + sort($info['warning']); + break; + } + } + } + + $offset = 0; + if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { + if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) { + $offset += $apetagheadersize; + } else { + $this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']); + return false; + } + } + + // shortcut + $info['replay_gain'] = array(); + $thisfile_replaygain = &$info['replay_gain']; + + for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) { + $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); + $offset += 4; + $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); + $offset += 4; + if (strstr(substr($APEtagData, $offset), "\x00") === false) { + $this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset)); + return false; + } + $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset; + $item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength)); + + // shortcut + $thisfile_ape['items'][$item_key] = array(); + $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key]; + + $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset; + + $offset += ($ItemKeyLength + 1); // skip 0x00 terminator + $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size); + $offset += $value_size; + + $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags); + switch ($thisfile_ape_items_current['flags']['item_contents_raw']) { + case 0: // UTF-8 + case 2: // Locator (URL, filename, etc), UTF-8 encoded + $thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']); + break; + + case 1: // binary data + default: + break; + } + + switch (strtolower($item_key)) { + // http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain + case 'replaygain_track_gain': + if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + } else { + $this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'replaygain_track_peak': + if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + if ($thisfile_replaygain['track']['peak'] <= 0) { + $this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'); + } + } else { + $this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'replaygain_album_gain': + if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + } else { + $this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'replaygain_album_peak': + if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) { + $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + if ($thisfile_replaygain['album']['peak'] <= 0) { + $this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'); + } + } else { + $this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'mp3gain_undo': + if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); + $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); + $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); + } else { + $this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'mp3gain_minmax': + if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); + $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); + } else { + $this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'mp3gain_album_minmax': + if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); + $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); + } else { + $this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'); + } + break; + + case 'tracknumber': + if (is_array($thisfile_ape_items_current['data'])) { + foreach ($thisfile_ape_items_current['data'] as $comment) { + $thisfile_ape['comments']['track_number'][] = $comment; + } + } + break; + + case 'cover art (artist)': + case 'cover art (back)': + case 'cover art (band logo)': + case 'cover art (band)': + case 'cover art (colored fish)': + case 'cover art (composer)': + case 'cover art (conductor)': + case 'cover art (front)': + case 'cover art (icon)': + case 'cover art (illustration)': + case 'cover art (lead)': + case 'cover art (leaflet)': + case 'cover art (lyricist)': + case 'cover art (media)': + case 'cover art (movie scene)': + case 'cover art (other icon)': + case 'cover art (other)': + case 'cover art (performance)': + case 'cover art (publisher logo)': + case 'cover art (recording)': + case 'cover art (studio)': + // list of possible cover arts from https://github.com/mono/taglib-sharp/blob/taglib-sharp-2.0.3.2/src/TagLib/Ape/Tag.cs + if (is_array($thisfile_ape_items_current['data'])) { + $this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8'); + $thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']); + } + list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2); + $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00"); + $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']); + + do { + $thisfile_ape_items_current['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo); + if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) { + $this->warning('APEtag "'.$item_key.'" contains invalid image data'); + break; + } + $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + + if ($this->inline_attachments === false) { + // skip entirely + unset($thisfile_ape_items_current['data']); + break; + } + if ($this->inline_attachments === true) { + // great + } elseif (is_int($this->inline_attachments)) { + if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) { + // too big, skip + $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)'); + unset($thisfile_ape_items_current['data']); + break; + } + } elseif (is_string($this->inline_attachments)) { + $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($this->inline_attachments) || !getID3::is_writable($this->inline_attachments)) { + // cannot write, skip + $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'); + unset($thisfile_ape_items_current['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->inline_attachments)) { + $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset']; + if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) { + file_put_contents($destination_filename, $thisfile_ape_items_current['data']); + } else { + $this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'); + } + $thisfile_ape_items_current['data_filename'] = $destination_filename; + unset($thisfile_ape_items_current['data']); + } else { + if (!isset($info['ape']['comments']['picture'])) { + $info['ape']['comments']['picture'] = array(); + } + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($thisfile_ape_items_current[$picture_key])) { + $comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key]; + } + } + $info['ape']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); + } + } while (false); // @phpstan-ignore-line + break; + + default: + if (is_array($thisfile_ape_items_current['data'])) { + foreach ($thisfile_ape_items_current['data'] as $comment) { + $thisfile_ape['comments'][strtolower($item_key)][] = $comment; + } + } + break; + } + + } + if (empty($thisfile_replaygain)) { + unset($info['replay_gain']); + } + return true; + } + + /** + * @param string $APEheaderFooterData + * + * @return array|false + */ + public function parseAPEheaderFooter($APEheaderFooterData) { + // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html + + // shortcut + $headerfooterinfo = array(); + $headerfooterinfo['raw'] = array(); + $headerfooterinfo_raw = &$headerfooterinfo['raw']; + + $headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8); + if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') { + return false; + } + $headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4)); + $headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4)); + $headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4)); + $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4)); + $headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8); + + $headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000; + if ($headerfooterinfo['tag_version'] >= 2) { + $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']); + } + return $headerfooterinfo; + } + + /** + * @param int $rawflagint + * + * @return array + */ + public function parseAPEtagFlags($rawflagint) { + // "Note: APE Tags 1.0 do not use any of the APE Tag flags. + // All are set to zero on creation and ignored on reading." + // http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags + $flags = array(); + $flags['header'] = (bool) ($rawflagint & 0x80000000); + $flags['footer'] = (bool) ($rawflagint & 0x40000000); + $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000); + $flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1; + $flags['read_only'] = (bool) ($rawflagint & 0x00000001); + + $flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']); + + return $flags; + } + + /** + * @param int $contenttypeid + * + * @return string + */ + public function APEcontentTypeFlagLookup($contenttypeid) { + static $APEcontentTypeFlagLookup = array( + 0 => 'utf-8', + 1 => 'binary', + 2 => 'external', + 3 => 'reserved' + ); + return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid'); + } + + /** + * @param string $itemkey + * + * @return bool + */ + public function APEtagItemIsUTF8Lookup($itemkey) { + static $APEtagItemIsUTF8Lookup = array( + 'title', + 'subtitle', + 'artist', + 'album', + 'debut album', + 'publisher', + 'conductor', + 'track', + 'composer', + 'comment', + 'copyright', + 'publicationright', + 'file', + 'year', + 'record date', + 'record location', + 'genre', + 'media', + 'related', + 'isrc', + 'abstract', + 'language', + 'bibliography' + ); + return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup); + } + +} diff --git a/config/getid3/module.tag.id3v1.php b/config/getid3/module.tag.id3v1.php new file mode 100644 index 000000000..442aefe35 --- /dev/null +++ b/config/getid3/module.tag.id3v1.php @@ -0,0 +1,480 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.tag.id3v1.php // +// module for analyzing ID3v1 tags // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} + +class getid3_id3v1 extends getid3_handler +{ + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + if (!getid3_lib::intValueSupported($info['filesize'])) { + $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); + return false; + } + + if($info['filesize'] < 256) { + $this->fseek(-128, SEEK_END); + $preid3v1 = ''; + $id3v1tag = $this->fread(128); + } else { + $this->fseek(-256, SEEK_END); + $preid3v1 = $this->fread(128); + $id3v1tag = $this->fread(128); + } + + + if (substr($id3v1tag, 0, 3) == 'TAG') { + + $info['avdataend'] = $info['filesize'] - 128; + + $ParsedID3v1 = array(); + $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); + $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); + $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30)); + $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4)); + $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them + $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1)); + + // If second-last byte of comment field is null and last byte of comment field is non-null + // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number + if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) { + $ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29, 1)); + $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); + } + $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); + + $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']); + if (!empty($ParsedID3v1['genre'])) { + unset($ParsedID3v1['genreid']); + } + if (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown')) { + unset($ParsedID3v1['genre']); + } + + foreach ($ParsedID3v1 as $key => $value) { + $ParsedID3v1['comments'][$key][0] = $value; + } + $ID3v1encoding = $this->getid3->encoding_id3v1; + if ($this->getid3->encoding_id3v1_autodetect) { + // ID3v1 encoding detection hack START + // ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets + // Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess + foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years) + foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) { + if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) { + $ID3v1encoding = $id3v1_bad_encoding; + $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key); + break 3; + } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) { + $ID3v1encoding = $id3v1_bad_encoding; + $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key); + break 3; + } + } + } + } + } + // ID3v1 encoding detection hack END + } + + // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces + $GoodFormatID3v1tag = $this->GenerateID3v1Tag( + $ParsedID3v1['title'], + $ParsedID3v1['artist'], + $ParsedID3v1['album'], + $ParsedID3v1['year'], + (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), + $ParsedID3v1['comment'], + (!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : '')); + $ParsedID3v1['padding_valid'] = true; + if ($id3v1tag !== $GoodFormatID3v1tag) { + $ParsedID3v1['padding_valid'] = false; + $this->warning('Some ID3v1 fields do not use NULL characters for padding'); + } + + $ParsedID3v1['tag_offset_end'] = $info['filesize']; + $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128; + + $info['id3v1'] = $ParsedID3v1; + $info['id3v1']['encoding'] = $ID3v1encoding; + } + + if (substr($preid3v1, 0, 3) == 'TAG') { + // The way iTunes handles tags is, well, brain-damaged. + // It completely ignores v1 if ID3v2 is present. + // This goes as far as adding a new v1 tag *even if there already is one* + + // A suspected double-ID3v1 tag has been detected, but it could be that + // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag + if (substr($preid3v1, 96, 8) == 'APETAGEX') { + // an APE tag footer was found before the last ID3v1, assume false "TAG" synch + } elseif (substr($preid3v1, 119, 6) == 'LYRICS') { + // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch + } else { + // APE and Lyrics3 footers not found - assume double ID3v1 + $this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes'); + $info['avdataend'] -= 128; + } + } + + return true; + } + + /** + * @param string $str + * + * @return string + */ + public static function cutfield($str) { + return trim(substr($str, 0, strcspn($str, "\x00"))); + } + + /** + * @param bool $allowSCMPXextended + * + * @return string[] + */ + public static function ArrayOfGenres($allowSCMPXextended=false) { + static $GenreLookup = array( + 0 => 'Blues', + 1 => 'Classic Rock', + 2 => 'Country', + 3 => 'Dance', + 4 => 'Disco', + 5 => 'Funk', + 6 => 'Grunge', + 7 => 'Hip-Hop', + 8 => 'Jazz', + 9 => 'Metal', + 10 => 'New Age', + 11 => 'Oldies', + 12 => 'Other', + 13 => 'Pop', + 14 => 'R&B', + 15 => 'Rap', + 16 => 'Reggae', + 17 => 'Rock', + 18 => 'Techno', + 19 => 'Industrial', + 20 => 'Alternative', + 21 => 'Ska', + 22 => 'Death Metal', + 23 => 'Pranks', + 24 => 'Soundtrack', + 25 => 'Euro-Techno', + 26 => 'Ambient', + 27 => 'Trip-Hop', + 28 => 'Vocal', + 29 => 'Jazz+Funk', + 30 => 'Fusion', + 31 => 'Trance', + 32 => 'Classical', + 33 => 'Instrumental', + 34 => 'Acid', + 35 => 'House', + 36 => 'Game', + 37 => 'Sound Clip', + 38 => 'Gospel', + 39 => 'Noise', + 40 => 'Alt. Rock', + 41 => 'Bass', + 42 => 'Soul', + 43 => 'Punk', + 44 => 'Space', + 45 => 'Meditative', + 46 => 'Instrumental Pop', + 47 => 'Instrumental Rock', + 48 => 'Ethnic', + 49 => 'Gothic', + 50 => 'Darkwave', + 51 => 'Techno-Industrial', + 52 => 'Electronic', + 53 => 'Pop-Folk', + 54 => 'Eurodance', + 55 => 'Dream', + 56 => 'Southern Rock', + 57 => 'Comedy', + 58 => 'Cult', + 59 => 'Gangsta Rap', + 60 => 'Top 40', + 61 => 'Christian Rap', + 62 => 'Pop/Funk', + 63 => 'Jungle', + 64 => 'Native American', + 65 => 'Cabaret', + 66 => 'New Wave', + 67 => 'Psychedelic', + 68 => 'Rave', + 69 => 'Showtunes', + 70 => 'Trailer', + 71 => 'Lo-Fi', + 72 => 'Tribal', + 73 => 'Acid Punk', + 74 => 'Acid Jazz', + 75 => 'Polka', + 76 => 'Retro', + 77 => 'Musical', + 78 => 'Rock & Roll', + 79 => 'Hard Rock', + 80 => 'Folk', + 81 => 'Folk/Rock', + 82 => 'National Folk', + 83 => 'Swing', + 84 => 'Fast-Fusion', + 85 => 'Bebob', + 86 => 'Latin', + 87 => 'Revival', + 88 => 'Celtic', + 89 => 'Bluegrass', + 90 => 'Avantgarde', + 91 => 'Gothic Rock', + 92 => 'Progressive Rock', + 93 => 'Psychedelic Rock', + 94 => 'Symphonic Rock', + 95 => 'Slow Rock', + 96 => 'Big Band', + 97 => 'Chorus', + 98 => 'Easy Listening', + 99 => 'Acoustic', + 100 => 'Humour', + 101 => 'Speech', + 102 => 'Chanson', + 103 => 'Opera', + 104 => 'Chamber Music', + 105 => 'Sonata', + 106 => 'Symphony', + 107 => 'Booty Bass', + 108 => 'Primus', + 109 => 'Porn Groove', + 110 => 'Satire', + 111 => 'Slow Jam', + 112 => 'Club', + 113 => 'Tango', + 114 => 'Samba', + 115 => 'Folklore', + 116 => 'Ballad', + 117 => 'Power Ballad', + 118 => 'Rhythmic Soul', + 119 => 'Freestyle', + 120 => 'Duet', + 121 => 'Punk Rock', + 122 => 'Drum Solo', + 123 => 'A Cappella', + 124 => 'Euro-House', + 125 => 'Dance Hall', + 126 => 'Goa', + 127 => 'Drum & Bass', + 128 => 'Club-House', + 129 => 'Hardcore', + 130 => 'Terror', + 131 => 'Indie', + 132 => 'BritPop', + 133 => 'Negerpunk', + 134 => 'Polsk Punk', + 135 => 'Beat', + 136 => 'Christian Gangsta Rap', + 137 => 'Heavy Metal', + 138 => 'Black Metal', + 139 => 'Crossover', + 140 => 'Contemporary Christian', + 141 => 'Christian Rock', + 142 => 'Merengue', + 143 => 'Salsa', + 144 => 'Thrash Metal', + 145 => 'Anime', + 146 => 'JPop', + 147 => 'Synthpop', + 148 => 'Abstract', + 149 => 'Art Rock', + 150 => 'Baroque', + 151 => 'Bhangra', + 152 => 'Big Beat', + 153 => 'Breakbeat', + 154 => 'Chillout', + 155 => 'Downtempo', + 156 => 'Dub', + 157 => 'EBM', + 158 => 'Eclectic', + 159 => 'Electro', + 160 => 'Electroclash', + 161 => 'Emo', + 162 => 'Experimental', + 163 => 'Garage', + 164 => 'Global', + 165 => 'IDM', + 166 => 'Illbient', + 167 => 'Industro-Goth', + 168 => 'Jam Band', + 169 => 'Krautrock', + 170 => 'Leftfield', + 171 => 'Lounge', + 172 => 'Math Rock', + 173 => 'New Romantic', + 174 => 'Nu-Breakz', + 175 => 'Post-Punk', + 176 => 'Post-Rock', + 177 => 'Psytrance', + 178 => 'Shoegaze', + 179 => 'Space Rock', + 180 => 'Trop Rock', + 181 => 'World Music', + 182 => 'Neoclassical', + 183 => 'Audiobook', + 184 => 'Audio Theatre', + 185 => 'Neue Deutsche Welle', + 186 => 'Podcast', + 187 => 'Indie-Rock', + 188 => 'G-Funk', + 189 => 'Dubstep', + 190 => 'Garage Rock', + 191 => 'Psybient', + + 255 => 'Unknown', + + 'CR' => 'Cover', + 'RX' => 'Remix' + ); + + static $GenreLookupSCMPX = array(); + if ($allowSCMPXextended && empty($GenreLookupSCMPX)) { + $GenreLookupSCMPX = $GenreLookup; + // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended + // Extended ID3v1 genres invented by SCMPX + // Note that 255 "Japanese Anime" conflicts with standard "Unknown" + $GenreLookupSCMPX[240] = 'Sacred'; + $GenreLookupSCMPX[241] = 'Northern Europe'; + $GenreLookupSCMPX[242] = 'Irish & Scottish'; + $GenreLookupSCMPX[243] = 'Scotland'; + $GenreLookupSCMPX[244] = 'Ethnic Europe'; + $GenreLookupSCMPX[245] = 'Enka'; + $GenreLookupSCMPX[246] = 'Children\'s Song'; + $GenreLookupSCMPX[247] = 'Japanese Sky'; + $GenreLookupSCMPX[248] = 'Japanese Heavy Rock'; + $GenreLookupSCMPX[249] = 'Japanese Doom Rock'; + $GenreLookupSCMPX[250] = 'Japanese J-POP'; + $GenreLookupSCMPX[251] = 'Japanese Seiyu'; + $GenreLookupSCMPX[252] = 'Japanese Ambient Techno'; + $GenreLookupSCMPX[253] = 'Japanese Moemoe'; + $GenreLookupSCMPX[254] = 'Japanese Tokusatsu'; + //$GenreLookupSCMPX[255] = 'Japanese Anime'; + } + + return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); + } + + /** + * @param string $genreid + * @param bool $allowSCMPXextended + * + * @return string|false + */ + public static function LookupGenreName($genreid, $allowSCMPXextended=true) { + switch ($genreid) { + case 'RX': + case 'CR': + break; + default: + if (!is_numeric($genreid)) { + return false; + } + $genreid = intval($genreid); // to handle 3 or '3' or '03' + break; + } + $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); + return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); + } + + /** + * @param string $genre + * @param bool $allowSCMPXextended + * + * @return string|false + */ + public static function LookupGenreID($genre, $allowSCMPXextended=false) { + $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); + $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); + foreach ($GenreLookup as $key => $value) { + if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { + return $key; + } + } + return false; + } + + /** + * @param string $OriginalGenre + * + * @return string|false + */ + public static function StandardiseID3v1GenreName($OriginalGenre) { + if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { + return self::LookupGenreName($GenreID); + } + return $OriginalGenre; + } + + /** + * @param string $title + * @param string $artist + * @param string $album + * @param string $year + * @param int $genreid + * @param string $comment + * @param int|string $track + * + * @return string + */ + public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { + $ID3v1Tag = 'TAG'; + $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT); + if (!empty($track) && ($track > 0) && ($track <= 255)) { + $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= "\x00"; + if (gettype($track) == 'string') { + $track = (int) $track; + } + $ID3v1Tag .= chr($track); + } else { + $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + } + if (($genreid < 0) || ($genreid > 147)) { + $genreid = 255; // 'unknown' genre + } + switch (gettype($genreid)) { + case 'string': + case 'integer': + $ID3v1Tag .= chr(intval($genreid)); + break; + default: + $ID3v1Tag .= chr(255); // 'unknown' genre + break; + } + + return $ID3v1Tag; + } + +} diff --git a/config/getid3/module.tag.id3v2.php b/config/getid3/module.tag.id3v2.php new file mode 100644 index 000000000..ec448be87 --- /dev/null +++ b/config/getid3/module.tag.id3v2.php @@ -0,0 +1,3894 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// module.tag.id3v2.php // +// module for analyzing ID3v2 tags // +// dependencies: module.tag.id3v1.php // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + +class getid3_id3v2 extends getid3_handler +{ + public $StartingOffset = 0; + + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + // Overall tag structure: + // +-----------------------------+ + // | Header (10 bytes) | + // +-----------------------------+ + // | Extended Header | + // | (variable length, OPTIONAL) | + // +-----------------------------+ + // | Frames (variable length) | + // +-----------------------------+ + // | Padding | + // | (variable length, OPTIONAL) | + // +-----------------------------+ + // | Footer (10 bytes, OPTIONAL) | + // +-----------------------------+ + + // Header + // ID3v2/file identifier "ID3" + // ID3v2 version $04 00 + // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x) + // ID3v2 size 4 * %0xxxxxxx + + + // shortcuts + $info['id3v2']['header'] = true; + $thisfile_id3v2 = &$info['id3v2']; + $thisfile_id3v2['flags'] = array(); + $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; + + + $this->fseek($this->StartingOffset); + $header = $this->fread(10); + if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { + + $thisfile_id3v2['majorversion'] = ord($header[3]); + $thisfile_id3v2['minorversion'] = ord($header[4]); + + // shortcut + $id3v2_majorversion = &$thisfile_id3v2['majorversion']; + + } else { + + unset($info['id3v2']); + return false; + + } + + if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) + + $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']); + return false; + + } + + $id3_flags = ord($header[5]); + switch ($id3v2_majorversion) { + case 2: + // %ab000000 in v2.2 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression + break; + + case 3: + // %abc00000 in v2.3 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header + $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator + break; + + case 4: + // %abcd0000 in v2.4 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header + $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator + $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present + break; + } + + $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + + $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset; + $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; + + + + // create 'encoding' key - used by getid3::HandleAllTags() + // in ID3v2 every field can have it's own encoding type + // so force everything to UTF-8 so it can be handled consistantly + $thisfile_id3v2['encoding'] = 'UTF-8'; + + + // Frames + + // All ID3v2 frames consists of one frame header followed by one or more + // fields containing the actual information. The header is always 10 + // bytes and laid out as follows: + // + // Frame ID $xx xx xx xx (four characters) + // Size 4 * %0xxxxxxx + // Flags $xx xx + + $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header + if (!empty($thisfile_id3v2['exthead']['length'])) { + $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4); + } + if (!empty($thisfile_id3v2_flags['isfooter'])) { + $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio + } + if ($sizeofframes > 0) { + + $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable + + // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) + if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { + $framedata = $this->DeUnsynchronise($framedata); + } + // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead + // of on tag level, making it easier to skip frames, increasing the streamability + // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that + // there exists an unsynchronised frame, while the new unsynchronisation flag in + // the frame header [S:4.1.2] indicates unsynchronisation. + + + //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) + $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header + + + // Extended Header + if (!empty($thisfile_id3v2_flags['exthead'])) { + $extended_header_offset = 0; + + if ($id3v2_majorversion == 3) { + + // v2.3 definition: + //Extended header size $xx xx xx xx // 32-bit integer + //Extended Flags $xx xx + // %x0000000 %00000000 // v2.3 + // x - CRC data present + //Size of padding $xx xx xx xx + + $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0); + $extended_header_offset += 4; + + $thisfile_id3v2['exthead']['flag_bytes'] = 2; + $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); + $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; + + $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000); + + $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); + $extended_header_offset += 4; + + if ($thisfile_id3v2['exthead']['flags']['crc']) { + $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); + $extended_header_offset += 4; + } + $extended_header_offset += $thisfile_id3v2['exthead']['padding_size']; + + } elseif ($id3v2_majorversion == 4) { + + // v2.4 definition: + //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer + //Number of flag bytes $01 + //Extended Flags $xx + // %0bcd0000 // v2.4 + // b - Tag is an update + // Flag data length $00 + // c - CRC data present + // Flag data length $05 + // Total frame CRC 5 * %0xxxxxxx + // d - Tag restrictions + // Flag data length $01 + + $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true); + $extended_header_offset += 4; + + $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1 + $extended_header_offset += 1; + + $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); + $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; + + $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40); + $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20); + $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10); + + if ($thisfile_id3v2['exthead']['flags']['update']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0 + $extended_header_offset += 1; + } + + if ($thisfile_id3v2['exthead']['flags']['crc']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5 + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false); + $extended_header_offset += $ext_header_chunk_length; + } + + if ($thisfile_id3v2['exthead']['flags']['restrictions']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1 + $extended_header_offset += 1; + + // %ppqrrstt + $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions + + $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']); + } + + if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) { + $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')'); + } + } + + $framedataoffset += $extended_header_offset; + $framedata = substr($framedata, $extended_header_offset); + } // end extended header + + + while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse + if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) { + // insufficient room left in ID3v2 header for actual data - must be padding + $thisfile_id3v2['padding']['start'] = $framedataoffset; + $thisfile_id3v2['padding']['length'] = strlen($framedata); + $thisfile_id3v2['padding']['valid'] = true; + for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { + if ($framedata[$i] != "\x00") { + $thisfile_id3v2['padding']['valid'] = false; + $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; + $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); + break; + } + } + break; // skip rest of ID3v2 header + } + $frame_header = null; + $frame_name = null; + $frame_size = null; + $frame_flags = null; + if ($id3v2_majorversion == 2) { + // Frame ID $xx xx xx (three characters) + // Size $xx xx xx (24-bit integer) + // Flags $xx xx + + $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header + $framedata = substr($framedata, 6); // and leave the rest in $framedata + $frame_name = substr($frame_header, 0, 3); + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0); + $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs + + } elseif ($id3v2_majorversion > 2) { + + // Frame ID $xx xx xx xx (four characters) + // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+) + // Flags $xx xx + + $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header + $framedata = substr($framedata, 10); // and leave the rest in $framedata + + $frame_name = substr($frame_header, 0, 4); + if ($id3v2_majorversion == 3) { + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer + } else { // ID3v2.4+ + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value) + } + + if ($frame_size < (strlen($framedata) + 4)) { + $nextFrameID = substr($framedata, $frame_size, 4); + if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) { + // next frame is OK + } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { + // MP3ext known broken frames - "ok" for the purposes of this test + } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { + $this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'); + $id3v2_majorversion = 3; + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer + } + } + + + $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2)); + } + + if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) { + // padding encountered + + $thisfile_id3v2['padding']['start'] = $framedataoffset; + $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata); + $thisfile_id3v2['padding']['valid'] = true; + + $len = strlen($framedata); + for ($i = 0; $i < $len; $i++) { + if ($framedata[$i] != "\x00") { + $thisfile_id3v2['padding']['valid'] = false; + $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; + $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'); + break; + } + } + break; // skip rest of ID3v2 header + } + + if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) { + $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.'); + $frame_name = $iTunesBrokenFrameNameFixed; + } + if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { + + $parsedFrame = array(); + $parsedFrame['frame_name'] = $frame_name; + $parsedFrame['frame_flags_raw'] = $frame_flags; + $parsedFrame['data'] = substr($framedata, 0, $frame_size); + $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size); + $parsedFrame['dataoffset'] = $framedataoffset; + + $this->ParseID3v2Frame($parsedFrame); + $thisfile_id3v2[$frame_name][] = $parsedFrame; + + $framedata = substr($framedata, $frame_size); + + } else { // invalid frame length or FrameID + + if ($frame_size <= strlen($framedata)) { + + if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { + + // next frame is valid, just skip the current frame + $framedata = substr($framedata, $frame_size); + $this->warning('Next ID3v2 frame is valid, skipping current frame.'); + + } else { + + // next frame is invalid too, abort processing + //unset($framedata); + $framedata = null; + $this->error('Next ID3v2 frame is also invalid, aborting processing.'); + + } + + } elseif ($frame_size == strlen($framedata)) { + + // this is the last frame, just skip + $this->warning('This was the last ID3v2 frame.'); + + } else { + + // next frame is invalid too, abort processing + //unset($framedata); + $framedata = null; + $this->warning('Invalid ID3v2 frame size, aborting.'); + + } + if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { + + switch ($frame_name) { + case "\x00\x00".'MP': + case "\x00".'MP3': + case ' MP3': + case 'MP3e': + case "\x00".'MP': + case ' MP': + case 'MP3': + $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'); + break; + + default: + $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'); + break; + } + + } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { + + $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).'); + + } else { + + $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'); + + } + + } + $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion)); + + } + + } + + + // Footer + + // The footer is a copy of the header, but with a different identifier. + // ID3v2 identifier "3DI" + // ID3v2 version $04 00 + // ID3v2 flags %abcd0000 + // ID3v2 size 4 * %0xxxxxxx + + if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { + $footer = $this->fread(10); + if (substr($footer, 0, 3) == '3DI') { + $thisfile_id3v2['footer'] = true; + $thisfile_id3v2['majorversion_footer'] = ord($footer[3]); + $thisfile_id3v2['minorversion_footer'] = ord($footer[4]); + } + if ($thisfile_id3v2['majorversion_footer'] <= 4) { + $id3_flags = ord($footer[5]); + $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80); + $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40); + $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20); + $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10); + + $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1); + } + } // end footer + + if (isset($thisfile_id3v2['comments']['genre'])) { + $genres = array(); + foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { + foreach ($this->ParseID3v2GenreString($value) as $genre) { + $genres[] = $genre; + } + } + $thisfile_id3v2['comments']['genre'] = array_unique($genres); + unset($key, $value, $genres, $genre); + } + + if (isset($thisfile_id3v2['comments']['track_number'])) { + foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) { + if (strstr($value, '/')) { + list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]); + } + } + } + + if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) { + $thisfile_id3v2['comments']['year'] = array($matches[1]); + } + + + if (!empty($thisfile_id3v2['TXXX'])) { + // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames + foreach ($thisfile_id3v2['TXXX'] as $txxx_array) { + switch ($txxx_array['description']) { + case 'replaygain_track_gain': + if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + case 'replaygain_track_peak': + if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']); + } + break; + case 'replaygain_album_gain': + if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + } + } + } + + + // Set avdataoffset + $info['avdataoffset'] = $thisfile_id3v2['headerlength']; + if (isset($thisfile_id3v2['footer'])) { + $info['avdataoffset'] += 10; + } + + return true; + } + + /** + * @param string $genrestring + * + * @return array + */ + public function ParseID3v2GenreString($genrestring) { + // Parse genres into arrays of genreName and genreID + // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' + // ID3v2.4.x: '21' $00 'Eurodisco' $00 + $clean_genres = array(); + + // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags + if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) { + // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here: + // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name + if (strpos($genrestring, '/') !== false) { + $LegitimateSlashedGenreList = array( // https://github.com/JamesHeinrich/getID3/issues/223 + 'Pop/Funk', // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard + 'Cut-up/DJ', // Discogs - https://www.discogs.com/style/cut-up/dj + 'RnB/Swing', // Discogs - https://www.discogs.com/style/rnb/swing + 'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul + ); + $genrestring = str_replace('/', "\x00", $genrestring); + foreach ($LegitimateSlashedGenreList as $SlashedGenre) { + $genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring); + } + } + + // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal" + if (strpos($genrestring, ';') !== false) { + $genrestring = str_replace(';', "\x00", $genrestring); + } + } + + + if (strpos($genrestring, "\x00") === false) { + $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); + } + + $genre_elements = explode("\x00", $genrestring); + foreach ($genre_elements as $element) { + $element = trim($element); + if ($element) { + if (preg_match('#^[0-9]{1,3}$#', $element)) { + $clean_genres[] = getid3_id3v1::LookupGenreName($element); + } else { + $clean_genres[] = str_replace('((', '(', $element); + } + } + } + return $clean_genres; + } + + /** + * @param array $parsedFrame + * + * @return bool + */ + public function ParseID3v2Frame(&$parsedFrame) { + + // shortcuts + $info = &$this->getid3->info; + $id3v2_majorversion = $info['id3v2']['majorversion']; + + $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); + if (empty($parsedFrame['framenamelong'])) { + unset($parsedFrame['framenamelong']); + } + $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']); + if (empty($parsedFrame['framenameshort'])) { + unset($parsedFrame['framenameshort']); + } + + if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard + if ($id3v2_majorversion == 3) { + // Frame Header Flags + // %abc00000 %ijk00000 + $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation + $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation + $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only + $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression + $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption + $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity + + } elseif ($id3v2_majorversion == 4) { + // Frame Header Flags + // %0abc0000 %0h00kmnp + $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation + $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation + $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only + $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity + $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression + $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption + $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation + $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator + + // Frame-level de-unsynchronisation - ID3v2.4 + if ($parsedFrame['flags']['Unsynchronisation']) { + $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); + } + + if ($parsedFrame['flags']['DataLengthIndicator']) { + $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); + $parsedFrame['data'] = substr($parsedFrame['data'], 4); + } + } + + // Frame-level de-compression + if ($parsedFrame['flags']['compression']) { + $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); + if (!function_exists('gzuncompress')) { + $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'); + } else { + if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { + //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { + $parsedFrame['data'] = $decompresseddata; + unset($decompresseddata); + } else { + $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'); + } + } + } + } + + if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { + if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { + $this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data'); + } + } + + if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { + + $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; + switch ($parsedFrame['frame_name']) { + case 'WCOM': + $warning .= ' (this is known to happen with files tagged by RioPort)'; + break; + + default: + break; + } + $this->warning($warning); + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier + // There may be more than one 'UFID' frame in a tag, + // but only one with the same 'Owner identifier'. + //
+ // Owner identifier $00 + // Identifier + $exploded = explode("\x00", $parsedFrame['data'], 2); + $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : ''); + $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : ''); + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame + // There may be more than one 'TXXX' frame in each tag, + // but only one with the same description. + //
+ // Text encoding $xx + // Description $00 (00) + // Value + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description'])); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); + if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); + } else { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); + } + } + //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain + + + } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame + // There may only be one text information frame of its kind in an tag. + //
+ // Text encoding $xx + // Information + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + } + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with / + // This of course breaks when an artist name contains slash character, e.g. "AC/DC" + // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense + // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user + switch ($parsedFrame['encoding']) { + case 'UTF-16': + case 'UTF-16BE': + case 'UTF-16LE': + $wordsize = 2; + break; + case 'ISO-8859-1': + case 'UTF-8': + default: + $wordsize = 1; + break; + } + $Txxx_elements = array(); + $Txxx_elements_start_offset = 0; + for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) { + if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) { + $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); + $Txxx_elements_start_offset = $i + $wordsize; + } + } + $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); + foreach ($Txxx_elements as $Txxx_element) { + $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element); + if (!empty($string)) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; + } + } + unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset); + } + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame + // There may be more than one 'WXXX' frame in each tag, + // but only one with the same description + //
+ // Text encoding $xx + // Description $00 (00) + // URL + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); // according to the frame text encoding + $parsedFrame['url'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1 + $parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames + // There may only be one URL link frame of its kind in a tag, + // except when stated otherwise in the frame description + //
+ // URL + + $parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1 + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only) + // http://id3.org/id3v2.3.0#sec4.4 + // There may only be one 'IPL' frame in each tag + //
+ // Text encoding $xx + // People list strings + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); + $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); + + // https://www.getid3.org/phpBB3/viewtopic.php?t=1369 + // "this tag typically contains null terminated strings, which are associated in pairs" + // "there are users that use the tag incorrectly" + $IPLS_parts = array(); + if (strpos($parsedFrame['data_raw'], "\x00") !== false) { + $IPLS_parts_unsorted = array(); + if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { + // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding + $thisILPS = ''; + for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { + $twobytes = substr($parsedFrame['data_raw'], $i, 2); + if ($twobytes === "\x00\x00") { + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + $thisILPS = ''; + } else { + $thisILPS .= $twobytes; + } + } + if (strlen($thisILPS) > 2) { // 2-byte BOM + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + } + } else { + // ISO-8859-1 or UTF-8 or other single-byte-null character set + $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); + } + if (count($IPLS_parts_unsorted) == 1) { + // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson" + foreach ($IPLS_parts_unsorted as $key => $value) { + $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value); + $position = ''; + foreach ($IPLS_parts_sorted as $person) { + $IPLS_parts[] = array('position'=>$position, 'person'=>$person); + } + } + } elseif ((count($IPLS_parts_unsorted) % 2) == 0) { + $position = ''; + $person = ''; + foreach ($IPLS_parts_unsorted as $key => $value) { + if (($key % 2) == 0) { + $position = $value; + } else { + $person = $value; + $IPLS_parts[] = array('position'=>$position, 'person'=>$person); + $position = ''; + $person = ''; + } + } + } else { + foreach ($IPLS_parts_unsorted as $key => $value) { + $IPLS_parts[] = array($value); + } + } + + } else { + $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']); + } + $parsedFrame['data'] = $IPLS_parts; + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier + // There may only be one 'MCDI' frame in each tag + //
+ // CD TOC + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes + // There may only be one 'ETCO' frame in each tag + //
+ // Time stamp format $xx + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Followed by a list of key events in the following format: + // Type of event $xx + // Time stamp $xx (xx ...) + // The 'Time stamp' is set to zero if directly at the beginning of the sound + // or after the previous event. All events MUST be sorted in chronological order. + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + while ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1); + $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']); + $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table + // There may only be one 'MLLT' frame in each tag + //
+ // MPEG frames between reference $xx xx + // Bytes between reference $xx xx xx + // Milliseconds between reference $xx xx xx + // Bits for bytes deviation $xx + // Bits for milliseconds dev. $xx + // Then for every reference the following data is included; + // Deviation in bytes %xxx.... + // Deviation in milliseconds %xxx.... + + $frame_offset = 0; + $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2)); + $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3)); + $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3)); + $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); + $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); + $parsedFrame['data'] = substr($parsedFrame['data'], 10); + $deviationbitstream = ''; + while ($frame_offset < strlen($parsedFrame['data'])) { + $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + } + $reference_counter = 0; + while (strlen($deviationbitstream) > 0) { + $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation'])); + $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation'])); + $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']); + $reference_counter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes + // There may only be one 'SYTC' frame in each tag + //
+ // Time stamp format $xx + // Tempo data + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $timestamp_counter = 0; + while ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ($parsedFrame[$timestamp_counter]['tempo'] == 255) { + $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1)); + } + $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $timestamp_counter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription + // There may be more than one 'Unsynchronised lyrics/text transcription' frame + // in each tag, but only one with the same language and content descriptor. + //
+ // Text encoding $xx + // Language $xx xx xx + // Content descriptor $00 (00) + // Lyrics/text + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) { // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315 + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + } else { + $this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text + // There may be more than one 'SYLT' frame in each tag, + // but only one with the same language and content descriptor. + //
+ // Text encoding $xx + // Language $xx xx xx + // Time stamp format $xx + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Content type $xx + // Content descriptor $00 (00) + // Terminated text to be synced (typically a syllable) + // Sync identifier (terminator to above string) $00 (00) + // Time stamp $xx (xx ...) + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + + $timestampindex = 0; + $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); + while (strlen($frame_remainingdata)) { + $frame_offset = 0; + $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator); + if ($frame_terminatorpos === false) { + $frame_remainingdata = ''; + } else { + if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); + + $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator)); + if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) { + // timestamp probably omitted for first data item + } else { + $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); + $frame_remainingdata = substr($frame_remainingdata, 4); + } + $timestampindex++; + } + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments + // There may be more than one comment frame in each tag, + // but only one with the same language and content descriptor. + //
+ // Text encoding $xx + // Language $xx xx xx + // Short content descrip. $00 (00) + // The actual text + + if (strlen($parsedFrame['data']) < 5) { + + $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']); + + } else { + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['data'] = $frame_text; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); + if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } else { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + } + + } + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) + // There may be more than one 'RVA2' frame in each tag, + // but only one with the same identification string + //
+ // Identification $00 + // The 'identification' string is used to identify the situation and/or + // device where this adjustment should apply. The following is then + // repeated for every channel: + // Type of channel $xx + // Volume adjustment $xx xx + // Bits representing peak $xx + // Peak volume $xx (xx ...) + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); + $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); + if (ord($frame_idstring) === 0) { + $frame_idstring = ''; + } + $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + $parsedFrame['description'] = $frame_idstring; + $RVA2channelcounter = 0; + while (strlen($frame_remainingdata) >= 5) { + $frame_offset = 0; + $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); + $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid; + $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); + $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed + $frame_offset += 2; + $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); + if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) { + $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value'); + break; + } + $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); + $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); + $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); + $RVA2channelcounter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) + // There may only be one 'RVA' frame in each tag + //
+ // ID3v2.2 => Increment/decrement %000000ba + // ID3v2.3 => Increment/decrement %00fedcba + // Bits used for volume descr. $xx + // Relative volume change, right $xx xx (xx ...) // a + // Relative volume change, left $xx xx (xx ...) // b + // Peak volume right $xx xx (xx ...) + // Peak volume left $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, right back $xx xx (xx ...) // c + // Relative volume change, left back $xx xx (xx ...) // d + // Peak volume right back $xx xx (xx ...) + // Peak volume left back $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, center $xx xx (xx ...) // e + // Peak volume center $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, bass $xx xx (xx ...) // f + // Peak volume bass $xx xx (xx ...) + + $frame_offset = 0; + $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1); + $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1); + $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8); + $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['right'] === false) { + $parsedFrame['volumechange']['right'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['left'] === false) { + $parsedFrame['volumechange']['left'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + if ($id3v2_majorversion == 3) { + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1); + $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1); + $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['rightrear'] === false) { + $parsedFrame['volumechange']['rightrear'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['leftrear'] === false) { + $parsedFrame['volumechange']['leftrear'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1); + $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['center'] === false) { + $parsedFrame['volumechange']['center'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1); + $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['bass'] === false) { + $parsedFrame['volumechange']['bass'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) + // There may be more than one 'EQU2' frame in each tag, + // but only one with the same identification string + //
+ // Interpolation method $xx + // $00 Band + // $01 Linear + // Identification $00 + // The following is then repeated for every adjustment point + // Frequency $xx xx + // Volume adjustment $xx xx + + $frame_offset = 0; + $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_idstring) === 0) { + $frame_idstring = ''; + } + $parsedFrame['description'] = $frame_idstring; + $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + while (strlen($frame_remainingdata)) { + $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2; + $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true); + $frame_remainingdata = substr($frame_remainingdata, 4); + } + $parsedFrame['interpolationmethod'] = $frame_interpolationmethod; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only) + // There may only be one 'EQUA' frame in each tag + //
+ // Adjustment bits $xx + // This is followed by 2 bytes + ('adjustment bits' rounded up to the + // nearest byte) for every equalisation band in the following format, + // giving a frequency range of 0 - 32767Hz: + // Increment/decrement %x (MSB of the Frequency) + // Frequency (lower 15 bits) + // Adjustment $xx (xx ...) + + $frame_offset = 0; + $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1); + $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8); + + $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset); + while (strlen($frame_remainingdata) > 0) { + $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2)); + $frame_incdec = (bool) substr($frame_frequencystr, 0, 1); + $frame_frequency = bindec(substr($frame_frequencystr, 1, 15)); + $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec; + $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes)); + if ($parsedFrame[$frame_frequency]['incdec'] === false) { + $parsedFrame[$frame_frequency]['adjustment'] *= -1; + } + $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb + // There may only be one 'RVRB' frame in each tag. + //
+ // Reverb left (ms) $xx xx + // Reverb right (ms) $xx xx + // Reverb bounces, left $xx + // Reverb bounces, right $xx + // Reverb feedback, left to left $xx + // Reverb feedback, left to right $xx + // Reverb feedback, right to right $xx + // Reverb feedback, right to left $xx + // Premix left to right $xx + // Premix right to left $xx + + $frame_offset = 0; + $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture + // There may be several pictures attached to one file, + // each in their individual 'APIC' frame, but only one + // with the same content descriptor + //
+ // Text encoding $xx + // ID3v2.3+ => MIME type $00 + // ID3v2.2 => Image format $xx xx xx + // Picture type $xx + // Description $00 (00) + // Picture data + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + + $frame_imagetype = null; + $frame_mimetype = null; + if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { + $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); + if (strtolower($frame_imagetype) == 'ima') { + // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted + // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype))); + if ($frame_imagetype == 'JPEG') { + $frame_imagetype = 'JPG'; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + } else { + $frame_offset += 3; + } + } + if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + } + + $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + if ($frame_offset >= $parsedFrame['datalength']) { + $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset)); + } else { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + if ($id3v2_majorversion == 2) { + $parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null; + } else { + $parsedFrame['mime'] = isset($frame_mimetype) ? $frame_mimetype : null; + } + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); + $parsedFrame['datalength'] = strlen($parsedFrame['data']); + + $parsedFrame['image_mime'] = ''; + $imageinfo = array(); + if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) { + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } + } + } + + do { + if ($this->getid3->option_save_attachments === false) { + // skip entirely + unset($parsedFrame['data']); + break; + } + $dir = ''; + if ($this->getid3->option_save_attachments === true) { + // great +/* + } elseif (is_int($this->getid3->option_save_attachments)) { + if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { + // too big, skip + $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'); + unset($parsedFrame['data']); + break; + } +*/ + } elseif (is_string($this->getid3->option_save_attachments)) { + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !getID3::is_writable($dir)) { + // cannot write, skip + $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'); + unset($parsedFrame['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->getid3->option_save_attachments)) { + $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; + if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) { + file_put_contents($destination_filename, $parsedFrame['data']); + } else { + $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'); + } + $parsedFrame['data_filename'] = $destination_filename; + unset($parsedFrame['data']); + } else { + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + if (!isset($info['id3v2']['comments']['picture'])) { + $info['id3v2']['comments']['picture'] = array(); + } + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($parsedFrame[$picture_key])) { + $comments_picture_data[$picture_key] = $parsedFrame[$picture_key]; + } + } + $info['id3v2']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); + } + } + } while (false); // @phpstan-ignore-line + } + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object + // There may be more than one 'GEOB' frame in each tag, + // but only one with the same content descriptor + //
+ // Text encoding $xx + // MIME type $00 + // Filename $00 (00) + // Content description $00 (00) + // Encapsulated object + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_filename) === 0) { + $frame_filename = ''; + } + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); + + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); + + $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['filename'] = $frame_filename; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter + // There may only be one 'PCNT' frame in each tag. + // When the counter reaches all one's, one byte is inserted in + // front of the counter thus making the counter eight bits bigger + //
+ // Counter $xx xx xx xx (xx ...) + + $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter + // There may be more than one 'POPM' frame in each tag, + // but only one with the same email address + //
+ // Email to user $00 + // Rating $xx + // Counter $xx xx xx xx (xx ...) + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_emailaddress) === 0) { + $frame_emailaddress = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + $parsedFrame['email'] = $frame_emailaddress; + $parsedFrame['rating'] = $frame_rating; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size + // There may only be one 'RBUF' frame in each tag + //
+ // Buffer size $xx xx xx + // Embedded info flag %0000000x + // Offset to next tag $xx xx xx xx + + $frame_offset = 0; + $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3)); + $frame_offset += 3; + + $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1); + $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only) + // There may be more than one 'CRM' frame in a tag, + // but only one with the same 'owner identifier' + //
+ // Owner identifier $00 (00) + // Content/explanation $00 (00) + // Encrypted datablock + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption + // There may be more than one 'AENC' frames in a tag, + // but only one with the same 'Owner identifier' + //
+ // Owner identifier $00 + // Preview start $xx xx + // Preview length $xx xx + // Encryption info + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information + // There may be more than one 'LINK' frame in a tag, + // but only one with the same contents + //
+ // ID3v2.3+ => Frame identifier $xx xx xx xx + // ID3v2.2 => Frame identifier $xx xx xx + // URL $00 + // ID and additional data + + $frame_offset = 0; + if ($id3v2_majorversion == 2) { + $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + } else { + $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + } + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_url) === 0) { + $frame_url = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $parsedFrame['url'] = $frame_url; + + $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) + // There may only be one 'POSS' frame in each tag + // + // Time stamp format $xx + // Position $xx (xx ...) + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only) + // There may be more than one 'Terms of use' frame in a tag, + // but only one with the same 'Language' + //
+ // Text encoding $xx + // Language $xx xx xx + // The actual text + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only) + // There may only be one 'OWNE' frame in a tag + //
+ // Text encoding $xx + // Price paid $00 + // Date of purch. + // Seller + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); + $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); + $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); + + $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); + if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) { + $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); + } + $frame_offset += 8; + + $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only) + // There may be more than one 'commercial frame' in a tag, + // but no two may be identical + //
+ // Text encoding $xx + // Price string $00 + // Valid until + // Contact URL $00 + // Received as $xx + // Name of seller $00 (00) + // Description $00 (00) + // Picture MIME type $00 + // Seller logo + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + $frame_textencoding_terminator = "\x00"; + } + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $frame_rawpricearray = explode('/', $frame_pricestring); + foreach ($frame_rawpricearray as $key => $val) { + $frame_currencyid = substr($val, 0, 3); + $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid); + $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3); + } + + $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); + $frame_offset += 8; + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_sellername) === 0) { + $frame_sellername = ''; + } + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); + + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['pricevaliduntil'] = $frame_datestring; + $parsedFrame['contacturl'] = $frame_contacturl; + $parsedFrame['receivedasid'] = $frame_receivedasid; + $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid); + $parsedFrame['sellername'] = $frame_sellername; + $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['logo'] = $frame_sellerlogo; + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only) + // There may be several 'ENCR' frames in a tag, + // but only one containing the same symbol + // and only one containing the same owner identifier + //
+ // Owner identifier $00 + // Method symbol $xx + // Encryption data + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only) + + // There may be several 'GRID' frames in a tag, + // but only one containing the same symbol + // and only one containing the same owner identifier + //
+ // Owner identifier $00 + // Group symbol $xx + // Group dependent data + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only) + // The tag may contain more than one 'PRIV' frame + // but only with different contents + //
+ // Owner identifier $00 + // The private data + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only) + // There may be more than one 'signature frame' in a tag, + // but no two may be identical + //
+ // Group symbol $xx + // Signature + + $frame_offset = 0; + $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only) + // There may only be one 'seek frame' in a tag + //
+ // Minimum offset to next tag $xx xx xx xx + + $frame_offset = 0; + $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only) + // There may only be one 'audio seek point index' frame in a tag + //
+ // Indexed data start (S) $xx xx xx xx + // Indexed data length (L) $xx xx xx xx + // Number of index points (N) $xx xx + // Bits per index point (b) $xx + // Then for every index point the following data is included: + // Fraction at index (Fi) $xx (xx) + + $frame_offset = 0; + $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); + for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) { + $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); + $frame_offset += $frame_bytesperpoint; + } + unset($parsedFrame['data']); + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html + // There may only be one 'RGAD' frame in a tag + //
+ // Peak Amplitude $xx $xx $xx $xx + // Radio Replay Gain Adjustment %aaabbbcd %dddddddd + // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd + // a - name code + // b - originator code + // c - sign bit + // d - replay gain adjustment + + $frame_offset = 0; + $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + foreach (array('track','album') as $rgad_entry_type) { + $rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['raw'][$rgad_entry_type]['name'] = ($rg_adjustment_word & 0xE000) >> 13; + $parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10; + $parsedFrame['raw'][$rgad_entry_type]['signbit'] = ($rg_adjustment_word & 0x0200) >> 9; + $parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100); + } + $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); + $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); + $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); + $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']); + $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); + $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); + + $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; + $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; + $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; + $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; + $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; + + unset($parsedFrame['data']); + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only) + // http://id3.org/id3v2-chapters-1.0 + // (10 bytes) + // Element ID $00 + // Start time $xx xx xx xx + // End time $xx xx xx xx + // Start offset $xx xx xx xx + // End offset $xx xx xx xx + // + + $frame_offset = 0; + @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); + $frame_offset += strlen($parsedFrame['element_id']."\x00"); + $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { + // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." + $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + } + $frame_offset += 4; + if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { + // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." + $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + } + $frame_offset += 4; + + if ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['subframes'] = array(); + while ($frame_offset < strlen($parsedFrame['data'])) { + // + $subframe = array(); + $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { + $this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)'); + break; + } + $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); + $frame_offset += $subframe['size']; + + $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); + $subframe['text'] = substr($subframe_rawdata, 1); + $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text'])); + switch (substr($encoding_converted_text, 0, 2)) { + case "\xFF\xFE": + case "\xFE\xFF": + switch (strtoupper($info['id3v2']['encoding'])) { + case 'ISO-8859-1': + case 'UTF-8': + $encoding_converted_text = substr($encoding_converted_text, 2); + // remove unwanted byte-order-marks + break; + default: + // ignore + break; + } + break; + default: + // do not remove BOM + break; + } + + switch ($subframe['name']) { + case 'TIT2': + $parsedFrame['chapter_name'] = $encoding_converted_text; + $parsedFrame['subframes'][] = $subframe; + break; + case 'TIT3': + $parsedFrame['chapter_description'] = $encoding_converted_text; + $parsedFrame['subframes'][] = $subframe; + break; + case 'WXXX': + @list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2); + $parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url']; + $parsedFrame['subframes'][] = $subframe; + break; + case 'APIC': + if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) { + list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches; + $subframe['image_mime'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime)); + $subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype); + $subframe['description'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description)); + if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) { + // the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16) + // the above regex assumes one byte, if it's actually two then strip the second one here + $subframe_apic_picturedata = substr($subframe_apic_picturedata, 1); + } + $subframe['data'] = $subframe_apic_picturedata; + unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata); + unset($subframe['text'], $parsedFrame['text']); + $parsedFrame['subframes'][] = $subframe; + $parsedFrame['picture_present'] = true; + } else { + $this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format'); + } + break; + default: + $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)'); + break; + } + } + unset($subframe_rawdata, $subframe, $encoding_converted_text); + unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC + } + + $id3v2_chapter_entry = array(); + foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) { + if (isset($parsedFrame[$id3v2_chapter_key])) { + $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key]; + } + } + if (!isset($info['id3v2']['chapters'])) { + $info['id3v2']['chapters'] = array(); + } + $info['id3v2']['chapters'][] = $id3v2_chapter_entry; + unset($id3v2_chapter_entry, $id3v2_chapter_key); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only) + // http://id3.org/id3v2-chapters-1.0 + // (10 bytes) + // Element ID $00 + // CTOC flags %xx + // Entry count $xx + // Child Element ID $00 /* zero or more child CHAP or CTOC entries */ + // + + $frame_offset = 0; + @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); + $frame_offset += strlen($parsedFrame['element_id']."\x00"); + $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1)); + $frame_offset += 1; + $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1)); + $frame_offset += 1; + + $terminator_position = null; + for ($i = 0; $i < $parsedFrame['entry_count']; $i++) { + $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset); + $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset); + $frame_offset = $terminator_position + 1; + } + + $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01); + $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03); + + unset($ctoc_flags_raw, $terminator_position); + + if ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['subframes'] = array(); + while ($frame_offset < strlen($parsedFrame['data'])) { + // + $subframe = array(); + $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { + $this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)'); + break; + } + $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); + $frame_offset += $subframe['size']; + + $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); + $subframe['text'] = substr($subframe_rawdata, 1); + $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; + switch (substr($encoding_converted_text, 0, 2)) { + case "\xFF\xFE": + case "\xFE\xFF": + switch (strtoupper($info['id3v2']['encoding'])) { + case 'ISO-8859-1': + case 'UTF-8': + $encoding_converted_text = substr($encoding_converted_text, 2); + // remove unwanted byte-order-marks + break; + default: + // ignore + break; + } + break; + default: + // do not remove BOM + break; + } + + if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { + if ($subframe['name'] == 'TIT2') { + $parsedFrame['toc_name'] = $encoding_converted_text; + } elseif ($subframe['name'] == 'TIT3') { + $parsedFrame['toc_description'] = $encoding_converted_text; + } + $parsedFrame['subframes'][] = $subframe; + } else { + $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'); + } + } + unset($subframe_rawdata, $subframe, $encoding_converted_text); + } + + } + + return true; + } + + /** + * @param string $data + * + * @return string + */ + public function DeUnsynchronise($data) { + return str_replace("\xFF\x00", "\xFF", $data); + } + + /** + * @param int $index + * + * @return string + */ + public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { + static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( + 0x00 => 'No more than 128 frames and 1 MB total tag size', + 0x01 => 'No more than 64 frames and 128 KB total tag size', + 0x02 => 'No more than 32 frames and 40 KB total tag size', + 0x03 => 'No more than 32 frames and 4 KB total tag size', + ); + return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public function LookupExtendedHeaderRestrictionsTextEncodings($index) { + static $LookupExtendedHeaderRestrictionsTextEncodings = array( + 0x00 => 'No restrictions', + 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', + ); + return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public function LookupExtendedHeaderRestrictionsTextFieldSize($index) { + static $LookupExtendedHeaderRestrictionsTextFieldSize = array( + 0x00 => 'No restrictions', + 0x01 => 'No string is longer than 1024 characters', + 0x02 => 'No string is longer than 128 characters', + 0x03 => 'No string is longer than 30 characters', + ); + return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public function LookupExtendedHeaderRestrictionsImageEncoding($index) { + static $LookupExtendedHeaderRestrictionsImageEncoding = array( + 0x00 => 'No restrictions', + 0x01 => 'Images are encoded only with PNG or JPEG', + ); + return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public function LookupExtendedHeaderRestrictionsImageSizeSize($index) { + static $LookupExtendedHeaderRestrictionsImageSizeSize = array( + 0x00 => 'No restrictions', + 0x01 => 'All images are 256x256 pixels or smaller', + 0x02 => 'All images are 64x64 pixels or smaller', + 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise', + ); + return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); + } + + /** + * @param string $currencyid + * + * @return string + */ + public function LookupCurrencyUnits($currencyid) { + + $begin = __LINE__; + + /** This is not a comment! + + + AED Dirhams + AFA Afghanis + ALL Leke + AMD Drams + ANG Guilders + AOA Kwanza + ARS Pesos + ATS Schillings + AUD Dollars + AWG Guilders + AZM Manats + BAM Convertible Marka + BBD Dollars + BDT Taka + BEF Francs + BGL Leva + BHD Dinars + BIF Francs + BMD Dollars + BND Dollars + BOB Bolivianos + BRL Brazil Real + BSD Dollars + BTN Ngultrum + BWP Pulas + BYR Rubles + BZD Dollars + CAD Dollars + CDF Congolese Francs + CHF Francs + CLP Pesos + CNY Yuan Renminbi + COP Pesos + CRC Colones + CUP Pesos + CVE Escudos + CYP Pounds + CZK Koruny + DEM Deutsche Marks + DJF Francs + DKK Kroner + DOP Pesos + DZD Algeria Dinars + EEK Krooni + EGP Pounds + ERN Nakfa + ESP Pesetas + ETB Birr + EUR Euro + FIM Markkaa + FJD Dollars + FKP Pounds + FRF Francs + GBP Pounds + GEL Lari + GGP Pounds + GHC Cedis + GIP Pounds + GMD Dalasi + GNF Francs + GRD Drachmae + GTQ Quetzales + GYD Dollars + HKD Dollars + HNL Lempiras + HRK Kuna + HTG Gourdes + HUF Forints + IDR Rupiahs + IEP Pounds + ILS New Shekels + IMP Pounds + INR Rupees + IQD Dinars + IRR Rials + ISK Kronur + ITL Lire + JEP Pounds + JMD Dollars + JOD Dinars + JPY Yen + KES Shillings + KGS Soms + KHR Riels + KMF Francs + KPW Won + KWD Dinars + KYD Dollars + KZT Tenge + LAK Kips + LBP Pounds + LKR Rupees + LRD Dollars + LSL Maloti + LTL Litai + LUF Francs + LVL Lati + LYD Dinars + MAD Dirhams + MDL Lei + MGF Malagasy Francs + MKD Denars + MMK Kyats + MNT Tugriks + MOP Patacas + MRO Ouguiyas + MTL Liri + MUR Rupees + MVR Rufiyaa + MWK Kwachas + MXN Pesos + MYR Ringgits + MZM Meticais + NAD Dollars + NGN Nairas + NIO Gold Cordobas + NLG Guilders + NOK Krone + NPR Nepal Rupees + NZD Dollars + OMR Rials + PAB Balboa + PEN Nuevos Soles + PGK Kina + PHP Pesos + PKR Rupees + PLN Zlotych + PTE Escudos + PYG Guarani + QAR Rials + ROL Lei + RUR Rubles + RWF Rwanda Francs + SAR Riyals + SBD Dollars + SCR Rupees + SDD Dinars + SEK Kronor + SGD Dollars + SHP Pounds + SIT Tolars + SKK Koruny + SLL Leones + SOS Shillings + SPL Luigini + SRG Guilders + STD Dobras + SVC Colones + SYP Pounds + SZL Emalangeni + THB Baht + TJR Rubles + TMM Manats + TND Dinars + TOP Pa'anga + TRL Liras (old) + TRY Liras + TTD Dollars + TVD Tuvalu Dollars + TWD New Dollars + TZS Shillings + UAH Hryvnia + UGX Shillings + USD Dollars + UYU Pesos + UZS Sums + VAL Lire + VEB Bolivares + VND Dong + VUV Vatu + WST Tala + XAF Francs + XAG Ounces + XAU Ounces + XCD Dollars + XDR Special Drawing Rights + XPD Ounces + XPF Francs + XPT Ounces + YER Rials + YUM New Dinars + ZAR Rand + ZMK Kwacha + ZWD Zimbabwe Dollars + + */ + + return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units'); + } + + /** + * @param string $currencyid + * + * @return string + */ + public function LookupCurrencyCountry($currencyid) { + + $begin = __LINE__; + + /** This is not a comment! + + AED United Arab Emirates + AFA Afghanistan + ALL Albania + AMD Armenia + ANG Netherlands Antilles + AOA Angola + ARS Argentina + ATS Austria + AUD Australia + AWG Aruba + AZM Azerbaijan + BAM Bosnia and Herzegovina + BBD Barbados + BDT Bangladesh + BEF Belgium + BGL Bulgaria + BHD Bahrain + BIF Burundi + BMD Bermuda + BND Brunei Darussalam + BOB Bolivia + BRL Brazil + BSD Bahamas + BTN Bhutan + BWP Botswana + BYR Belarus + BZD Belize + CAD Canada + CDF Congo/Kinshasa + CHF Switzerland + CLP Chile + CNY China + COP Colombia + CRC Costa Rica + CUP Cuba + CVE Cape Verde + CYP Cyprus + CZK Czech Republic + DEM Germany + DJF Djibouti + DKK Denmark + DOP Dominican Republic + DZD Algeria + EEK Estonia + EGP Egypt + ERN Eritrea + ESP Spain + ETB Ethiopia + EUR Euro Member Countries + FIM Finland + FJD Fiji + FKP Falkland Islands (Malvinas) + FRF France + GBP United Kingdom + GEL Georgia + GGP Guernsey + GHC Ghana + GIP Gibraltar + GMD Gambia + GNF Guinea + GRD Greece + GTQ Guatemala + GYD Guyana + HKD Hong Kong + HNL Honduras + HRK Croatia + HTG Haiti + HUF Hungary + IDR Indonesia + IEP Ireland (Eire) + ILS Israel + IMP Isle of Man + INR India + IQD Iraq + IRR Iran + ISK Iceland + ITL Italy + JEP Jersey + JMD Jamaica + JOD Jordan + JPY Japan + KES Kenya + KGS Kyrgyzstan + KHR Cambodia + KMF Comoros + KPW Korea + KWD Kuwait + KYD Cayman Islands + KZT Kazakstan + LAK Laos + LBP Lebanon + LKR Sri Lanka + LRD Liberia + LSL Lesotho + LTL Lithuania + LUF Luxembourg + LVL Latvia + LYD Libya + MAD Morocco + MDL Moldova + MGF Madagascar + MKD Macedonia + MMK Myanmar (Burma) + MNT Mongolia + MOP Macau + MRO Mauritania + MTL Malta + MUR Mauritius + MVR Maldives (Maldive Islands) + MWK Malawi + MXN Mexico + MYR Malaysia + MZM Mozambique + NAD Namibia + NGN Nigeria + NIO Nicaragua + NLG Netherlands (Holland) + NOK Norway + NPR Nepal + NZD New Zealand + OMR Oman + PAB Panama + PEN Peru + PGK Papua New Guinea + PHP Philippines + PKR Pakistan + PLN Poland + PTE Portugal + PYG Paraguay + QAR Qatar + ROL Romania + RUR Russia + RWF Rwanda + SAR Saudi Arabia + SBD Solomon Islands + SCR Seychelles + SDD Sudan + SEK Sweden + SGD Singapore + SHP Saint Helena + SIT Slovenia + SKK Slovakia + SLL Sierra Leone + SOS Somalia + SPL Seborga + SRG Suriname + STD São Tome and Principe + SVC El Salvador + SYP Syria + SZL Swaziland + THB Thailand + TJR Tajikistan + TMM Turkmenistan + TND Tunisia + TOP Tonga + TRL Turkey + TRY Turkey + TTD Trinidad and Tobago + TVD Tuvalu + TWD Taiwan + TZS Tanzania + UAH Ukraine + UGX Uganda + USD United States of America + UYU Uruguay + UZS Uzbekistan + VAL Vatican City + VEB Venezuela + VND Viet Nam + VUV Vanuatu + WST Samoa + XAF Communauté Financière Africaine + XAG Silver + XAU Gold + XCD East Caribbean + XDR International Monetary Fund + XPD Palladium + XPF Comptoirs Français du Pacifique + XPT Platinum + YER Yemen + YUM Yugoslavia + ZAR South Africa + ZMK Zambia + ZWD Zimbabwe + + */ + + return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country'); + } + + /** + * @param string $languagecode + * @param bool $casesensitive + * + * @return string + */ + public static function LanguageLookup($languagecode, $casesensitive=false) { + + if (!$casesensitive) { + $languagecode = strtolower($languagecode); + } + + // http://www.id3.org/id3v2.4.0-structure.txt + // [4. ID3v2 frame overview] + // The three byte language field, present in several frames, is used to + // describe the language of the frame's content, according to ISO-639-2 + // [ISO-639-2]. The language should be represented in lower case. If the + // language is not known the string "XXX" should be used. + + + // ISO 639-2 - http://www.id3.org/iso639-2.html + + $begin = __LINE__; + + /** This is not a comment! + + XXX unknown + xxx unknown + aar Afar + abk Abkhazian + ace Achinese + ach Acoli + ada Adangme + afa Afro-Asiatic (Other) + afh Afrihili + afr Afrikaans + aka Akan + akk Akkadian + alb Albanian + ale Aleut + alg Algonquian Languages + amh Amharic + ang English, Old (ca. 450-1100) + apa Apache Languages + ara Arabic + arc Aramaic + arm Armenian + arn Araucanian + arp Arapaho + art Artificial (Other) + arw Arawak + asm Assamese + ath Athapascan Languages + ava Avaric + ave Avestan + awa Awadhi + aym Aymara + aze Azerbaijani + bad Banda + bai Bamileke Languages + bak Bashkir + bal Baluchi + bam Bambara + ban Balinese + baq Basque + bas Basa + bat Baltic (Other) + bej Beja + bel Byelorussian + bem Bemba + ben Bengali + ber Berber (Other) + bho Bhojpuri + bih Bihari + bik Bikol + bin Bini + bis Bislama + bla Siksika + bnt Bantu (Other) + bod Tibetan + bra Braj + bre Breton + bua Buriat + bug Buginese + bul Bulgarian + bur Burmese + cad Caddo + cai Central American Indian (Other) + car Carib + cat Catalan + cau Caucasian (Other) + ceb Cebuano + cel Celtic (Other) + ces Czech + cha Chamorro + chb Chibcha + che Chechen + chg Chagatai + chi Chinese + chm Mari + chn Chinook jargon + cho Choctaw + chr Cherokee + chu Church Slavic + chv Chuvash + chy Cheyenne + cop Coptic + cor Cornish + cos Corsican + cpe Creoles and Pidgins, English-based (Other) + cpf Creoles and Pidgins, French-based (Other) + cpp Creoles and Pidgins, Portuguese-based (Other) + cre Cree + crp Creoles and Pidgins (Other) + cus Cushitic (Other) + cym Welsh + cze Czech + dak Dakota + dan Danish + del Delaware + deu German + din Dinka + div Divehi + doi Dogri + dra Dravidian (Other) + dua Duala + dum Dutch, Middle (ca. 1050-1350) + dut Dutch + dyu Dyula + dzo Dzongkha + efi Efik + egy Egyptian (Ancient) + eka Ekajuk + ell Greek, Modern (1453-) + elx Elamite + eng English + enm English, Middle (ca. 1100-1500) + epo Esperanto + esk Eskimo (Other) + esl Spanish + est Estonian + eus Basque + ewe Ewe + ewo Ewondo + fan Fang + fao Faroese + fas Persian + fat Fanti + fij Fijian + fin Finnish + fiu Finno-Ugrian (Other) + fon Fon + fra French + fre French + frm French, Middle (ca. 1400-1600) + fro French, Old (842- ca. 1400) + fry Frisian + ful Fulah + gaa Ga + gae Gaelic (Scots) + gai Irish + gay Gayo + gdh Gaelic (Scots) + gem Germanic (Other) + geo Georgian + ger German + gez Geez + gil Gilbertese + glg Gallegan + gmh German, Middle High (ca. 1050-1500) + goh German, Old High (ca. 750-1050) + gon Gondi + got Gothic + grb Grebo + grc Greek, Ancient (to 1453) + gre Greek, Modern (1453-) + grn Guarani + guj Gujarati + hai Haida + hau Hausa + haw Hawaiian + heb Hebrew + her Herero + hil Hiligaynon + him Himachali + hin Hindi + hmo Hiri Motu + hun Hungarian + hup Hupa + hye Armenian + iba Iban + ibo Igbo + ice Icelandic + ijo Ijo + iku Inuktitut + ilo Iloko + ina Interlingua (International Auxiliary language Association) + inc Indic (Other) + ind Indonesian + ine Indo-European (Other) + ine Interlingue + ipk Inupiak + ira Iranian (Other) + iri Irish + iro Iroquoian uages + isl Icelandic + ita Italian + jav Javanese + jaw Javanese + jpn Japanese + jpr Judeo-Persian + jrb Judeo-Arabic + kaa Kara-Kalpak + kab Kabyle + kac Kachin + kal Greenlandic + kam Kamba + kan Kannada + kar Karen + kas Kashmiri + kat Georgian + kau Kanuri + kaw Kawi + kaz Kazakh + kha Khasi + khi Khoisan (Other) + khm Khmer + kho Khotanese + kik Kikuyu + kin Kinyarwanda + kir Kirghiz + kok Konkani + kom Komi + kon Kongo + kor Korean + kpe Kpelle + kro Kru + kru Kurukh + kua Kuanyama + kum Kumyk + kur Kurdish + kus Kusaie + kut Kutenai + lad Ladino + lah Lahnda + lam Lamba + lao Lao + lat Latin + lav Latvian + lez Lezghian + lin Lingala + lit Lithuanian + lol Mongo + loz Lozi + ltz Letzeburgesch + lub Luba-Katanga + lug Ganda + lui Luiseno + lun Lunda + luo Luo (Kenya and Tanzania) + mac Macedonian + mad Madurese + mag Magahi + mah Marshall + mai Maithili + mak Macedonian + mak Makasar + mal Malayalam + man Mandingo + mao Maori + map Austronesian (Other) + mar Marathi + mas Masai + max Manx + may Malay + men Mende + mga Irish, Middle (900 - 1200) + mic Micmac + min Minangkabau + mis Miscellaneous (Other) + mkh Mon-Kmer (Other) + mlg Malagasy + mlt Maltese + mni Manipuri + mno Manobo Languages + moh Mohawk + mol Moldavian + mon Mongolian + mos Mossi + mri Maori + msa Malay + mul Multiple Languages + mun Munda Languages + mus Creek + mwr Marwari + mya Burmese + myn Mayan Languages + nah Aztec + nai North American Indian (Other) + nau Nauru + nav Navajo + nbl Ndebele, South + nde Ndebele, North + ndo Ndongo + nep Nepali + new Newari + nic Niger-Kordofanian (Other) + niu Niuean + nla Dutch + nno Norwegian (Nynorsk) + non Norse, Old + nor Norwegian + nso Sotho, Northern + nub Nubian Languages + nya Nyanja + nym Nyamwezi + nyn Nyankole + nyo Nyoro + nzi Nzima + oci Langue d'Oc (post 1500) + oji Ojibwa + ori Oriya + orm Oromo + osa Osage + oss Ossetic + ota Turkish, Ottoman (1500 - 1928) + oto Otomian Languages + paa Papuan-Australian (Other) + pag Pangasinan + pal Pahlavi + pam Pampanga + pan Panjabi + pap Papiamento + pau Palauan + peo Persian, Old (ca 600 - 400 B.C.) + per Persian + phn Phoenician + pli Pali + pol Polish + pon Ponape + por Portuguese + pra Prakrit uages + pro Provencal, Old (to 1500) + pus Pushto + que Quechua + raj Rajasthani + rar Rarotongan + roa Romance (Other) + roh Rhaeto-Romance + rom Romany + ron Romanian + rum Romanian + run Rundi + rus Russian + sad Sandawe + sag Sango + sah Yakut + sai South American Indian (Other) + sal Salishan Languages + sam Samaritan Aramaic + san Sanskrit + sco Scots + scr Serbo-Croatian + sel Selkup + sem Semitic (Other) + sga Irish, Old (to 900) + shn Shan + sid Sidamo + sin Singhalese + sio Siouan Languages + sit Sino-Tibetan (Other) + sla Slavic (Other) + slk Slovak + slo Slovak + slv Slovenian + smi Sami Languages + smo Samoan + sna Shona + snd Sindhi + sog Sogdian + som Somali + son Songhai + sot Sotho, Southern + spa Spanish + sqi Albanian + srd Sardinian + srr Serer + ssa Nilo-Saharan (Other) + ssw Siswant + ssw Swazi + suk Sukuma + sun Sudanese + sus Susu + sux Sumerian + sve Swedish + swa Swahili + swe Swedish + syr Syriac + tah Tahitian + tam Tamil + tat Tatar + tel Telugu + tem Timne + ter Tereno + tgk Tajik + tgl Tagalog + tha Thai + tib Tibetan + tig Tigre + tir Tigrinya + tiv Tivi + tli Tlingit + tmh Tamashek + tog Tonga (Nyasa) + ton Tonga (Tonga Islands) + tru Truk + tsi Tsimshian + tsn Tswana + tso Tsonga + tuk Turkmen + tum Tumbuka + tur Turkish + tut Altaic (Other) + twi Twi + tyv Tuvinian + uga Ugaritic + uig Uighur + ukr Ukrainian + umb Umbundu + und Undetermined + urd Urdu + uzb Uzbek + vai Vai + ven Venda + vie Vietnamese + vol Volapük + vot Votic + wak Wakashan Languages + wal Walamo + war Waray + was Washo + wel Welsh + wen Sorbian Languages + wol Wolof + xho Xhosa + yao Yao + yap Yap + yid Yiddish + yor Yoruba + zap Zapotec + zen Zenaga + zha Zhuang + zho Chinese + zul Zulu + zun Zuni + + */ + + return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode'); + } + + /** + * @param int $index + * + * @return string + */ + public static function ETCOEventLookup($index) { + if (($index >= 0x17) && ($index <= 0xDF)) { + return 'reserved for future use'; + } + if (($index >= 0xE0) && ($index <= 0xEF)) { + return 'not predefined synch 0-F'; + } + if (($index >= 0xF0) && ($index <= 0xFC)) { + return 'reserved for future use'; + } + + static $EventLookup = array( + 0x00 => 'padding (has no meaning)', + 0x01 => 'end of initial silence', + 0x02 => 'intro start', + 0x03 => 'main part start', + 0x04 => 'outro start', + 0x05 => 'outro end', + 0x06 => 'verse start', + 0x07 => 'refrain start', + 0x08 => 'interlude start', + 0x09 => 'theme start', + 0x0A => 'variation start', + 0x0B => 'key change', + 0x0C => 'time change', + 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)', + 0x0E => 'sustained noise', + 0x0F => 'sustained noise end', + 0x10 => 'intro end', + 0x11 => 'main part end', + 0x12 => 'verse end', + 0x13 => 'refrain end', + 0x14 => 'theme end', + 0x15 => 'profanity', + 0x16 => 'profanity end', + 0xFD => 'audio end (start of silence)', + 0xFE => 'audio file ends', + 0xFF => 'one more byte of events follows' + ); + + return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public static function SYTLContentTypeLookup($index) { + static $SYTLContentTypeLookup = array( + 0x00 => 'other', + 0x01 => 'lyrics', + 0x02 => 'text transcription', + 0x03 => 'movement/part name', // (e.g. 'Adagio') + 0x04 => 'events', // (e.g. 'Don Quijote enters the stage') + 0x05 => 'chord', // (e.g. 'Bb F Fsus') + 0x06 => 'trivia/\'pop up\' information', + 0x07 => 'URLs to webpages', + 0x08 => 'URLs to images' + ); + + return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); + } + + /** + * @param int $index + * @param bool $returnarray + * + * @return array|string + */ + public static function APICPictureTypeLookup($index, $returnarray=false) { + static $APICPictureTypeLookup = array( + 0x00 => 'Other', + 0x01 => '32x32 pixels \'file icon\' (PNG only)', + 0x02 => 'Other file icon', + 0x03 => 'Cover (front)', + 0x04 => 'Cover (back)', + 0x05 => 'Leaflet page', + 0x06 => 'Media (e.g. label side of CD)', + 0x07 => 'Lead artist/lead performer/soloist', + 0x08 => 'Artist/performer', + 0x09 => 'Conductor', + 0x0A => 'Band/Orchestra', + 0x0B => 'Composer', + 0x0C => 'Lyricist/text writer', + 0x0D => 'Recording Location', + 0x0E => 'During recording', + 0x0F => 'During performance', + 0x10 => 'Movie/video screen capture', + 0x11 => 'A bright coloured fish', + 0x12 => 'Illustration', + 0x13 => 'Band/artist logotype', + 0x14 => 'Publisher/Studio logotype' + ); + if ($returnarray) { + return $APICPictureTypeLookup; + } + return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public static function COMRReceivedAsLookup($index) { + static $COMRReceivedAsLookup = array( + 0x00 => 'Other', + 0x01 => 'Standard CD album with other songs', + 0x02 => 'Compressed audio on CD', + 0x03 => 'File over the Internet', + 0x04 => 'Stream over the Internet', + 0x05 => 'As note sheets', + 0x06 => 'As note sheets in a book with other sheets', + 0x07 => 'Music on other media', + 0x08 => 'Non-musical merchandise' + ); + + return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); + } + + /** + * @param int $index + * + * @return string + */ + public static function RVA2ChannelTypeLookup($index) { + static $RVA2ChannelTypeLookup = array( + 0x00 => 'Other', + 0x01 => 'Master volume', + 0x02 => 'Front right', + 0x03 => 'Front left', + 0x04 => 'Back right', + 0x05 => 'Back left', + 0x06 => 'Front centre', + 0x07 => 'Back centre', + 0x08 => 'Subwoofer' + ); + + return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); + } + + /** + * @param string $framename + * + * @return string + */ + public static function FrameNameLongLookup($framename) { + + $begin = __LINE__; + + /** This is not a comment! + + AENC Audio encryption + APIC Attached picture + ASPI Audio seek point index + BUF Recommended buffer size + CNT Play counter + COM Comments + COMM Comments + COMR Commercial frame + CRA Audio encryption + CRM Encrypted meta frame + ENCR Encryption method registration + EQU Equalisation + EQU2 Equalisation (2) + EQUA Equalisation + ETC Event timing codes + ETCO Event timing codes + GEO General encapsulated object + GEOB General encapsulated object + GRID Group identification registration + IPL Involved people list + IPLS Involved people list + LINK Linked information + LNK Linked information + MCDI Music CD identifier + MCI Music CD Identifier + MLL MPEG location lookup table + MLLT MPEG location lookup table + OWNE Ownership frame + PCNT Play counter + PIC Attached picture + POP Popularimeter + POPM Popularimeter + POSS Position synchronisation frame + PRIV Private frame + RBUF Recommended buffer size + REV Reverb + RVA Relative volume adjustment + RVA2 Relative volume adjustment (2) + RVAD Relative volume adjustment + RVRB Reverb + SEEK Seek frame + SIGN Signature frame + SLT Synchronised lyric/text + STC Synced tempo codes + SYLT Synchronised lyric/text + SYTC Synchronised tempo codes + TAL Album/Movie/Show title + TALB Album/Movie/Show title + TBP BPM (Beats Per Minute) + TBPM BPM (beats per minute) + TCM Composer + TCMP Part of a compilation + TCO Content type + TCOM Composer + TCON Content type + TCOP Copyright message + TCP Part of a compilation + TCR Copyright message + TDA Date + TDAT Date + TDEN Encoding time + TDLY Playlist delay + TDOR Original release time + TDRC Recording time + TDRL Release time + TDTG Tagging time + TDY Playlist delay + TEN Encoded by + TENC Encoded by + TEXT Lyricist/Text writer + TFLT File type + TFT File type + TIM Time + TIME Time + TIPL Involved people list + TIT1 Content group description + TIT2 Title/songname/content description + TIT3 Subtitle/Description refinement + TKE Initial key + TKEY Initial key + TLA Language(s) + TLAN Language(s) + TLE Length + TLEN Length + TMCL Musician credits list + TMED Media type + TMOO Mood + TMT Media type + TOA Original artist(s)/performer(s) + TOAL Original album/movie/show title + TOF Original filename + TOFN Original filename + TOL Original Lyricist(s)/text writer(s) + TOLY Original lyricist(s)/text writer(s) + TOPE Original artist(s)/performer(s) + TOR Original release year + TORY Original release year + TOT Original album/Movie/Show title + TOWN File owner/licensee + TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group + TP2 Band/Orchestra/Accompaniment + TP3 Conductor/Performer refinement + TP4 Interpreted, remixed, or otherwise modified by + TPA Part of a set + TPB Publisher + TPE1 Lead performer(s)/Soloist(s) + TPE2 Band/orchestra/accompaniment + TPE3 Conductor/performer refinement + TPE4 Interpreted, remixed, or otherwise modified by + TPOS Part of a set + TPRO Produced notice + TPUB Publisher + TRC ISRC (International Standard Recording Code) + TRCK Track number/Position in set + TRD Recording dates + TRDA Recording dates + TRK Track number/Position in set + TRSN Internet radio station name + TRSO Internet radio station owner + TS2 Album-Artist sort order + TSA Album sort order + TSC Composer sort order + TSI Size + TSIZ Size + TSO2 Album-Artist sort order + TSOA Album sort order + TSOC Composer sort order + TSOP Performer sort order + TSOT Title sort order + TSP Performer sort order + TSRC ISRC (international standard recording code) + TSS Software/hardware and settings used for encoding + TSSE Software/Hardware and settings used for encoding + TSST Set subtitle + TST Title sort order + TT1 Content group description + TT2 Title/Songname/Content description + TT3 Subtitle/Description refinement + TXT Lyricist/text writer + TXX User defined text information frame + TXXX User defined text information frame + TYE Year + TYER Year + UFI Unique file identifier + UFID Unique file identifier + ULT Unsynchronised lyric/text transcription + USER Terms of use + USLT Unsynchronised lyric/text transcription + WAF Official audio file webpage + WAR Official artist/performer webpage + WAS Official audio source webpage + WCM Commercial information + WCOM Commercial information + WCOP Copyright/Legal information + WCP Copyright/Legal information + WOAF Official audio file webpage + WOAR Official artist/performer webpage + WOAS Official audio source webpage + WORS Official Internet radio station homepage + WPAY Payment + WPB Publishers official webpage + WPUB Publishers official webpage + WXX User defined URL link frame + WXXX User defined URL link frame + TFEA Featured Artist + TSTU Recording Studio + rgad Replay Gain Adjustment + + */ + + return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long'); + + // Last three: + // from Helium2 [www.helium2.com] + // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html + } + + /** + * @param string $framename + * + * @return string + */ + public static function FrameNameShortLookup($framename) { + + $begin = __LINE__; + + /** This is not a comment! + + AENC audio_encryption + APIC attached_picture + ASPI audio_seek_point_index + BUF recommended_buffer_size + CNT play_counter + COM comment + COMM comment + COMR commercial_frame + CRA audio_encryption + CRM encrypted_meta_frame + ENCR encryption_method_registration + EQU equalisation + EQU2 equalisation + EQUA equalisation + ETC event_timing_codes + ETCO event_timing_codes + GEO general_encapsulated_object + GEOB general_encapsulated_object + GRID group_identification_registration + IPL involved_people_list + IPLS involved_people_list + LINK linked_information + LNK linked_information + MCDI music_cd_identifier + MCI music_cd_identifier + MLL mpeg_location_lookup_table + MLLT mpeg_location_lookup_table + OWNE ownership_frame + PCNT play_counter + PIC attached_picture + POP popularimeter + POPM popularimeter + POSS position_synchronisation_frame + PRIV private_frame + RBUF recommended_buffer_size + REV reverb + RVA relative_volume_adjustment + RVA2 relative_volume_adjustment + RVAD relative_volume_adjustment + RVRB reverb + SEEK seek_frame + SIGN signature_frame + SLT synchronised_lyric + STC synced_tempo_codes + SYLT synchronised_lyric + SYTC synchronised_tempo_codes + TAL album + TALB album + TBP bpm + TBPM bpm + TCM composer + TCMP part_of_a_compilation + TCO genre + TCOM composer + TCON genre + TCOP copyright_message + TCP part_of_a_compilation + TCR copyright_message + TDA date + TDAT date + TDEN encoding_time + TDLY playlist_delay + TDOR original_release_time + TDRC recording_time + TDRL release_time + TDTG tagging_time + TDY playlist_delay + TEN encoded_by + TENC encoded_by + TEXT lyricist + TFLT file_type + TFT file_type + TIM time + TIME time + TIPL involved_people_list + TIT1 content_group_description + TIT2 title + TIT3 subtitle + TKE initial_key + TKEY initial_key + TLA language + TLAN language + TLE length + TLEN length + TMCL musician_credits_list + TMED media_type + TMOO mood + TMT media_type + TOA original_artist + TOAL original_album + TOF original_filename + TOFN original_filename + TOL original_lyricist + TOLY original_lyricist + TOPE original_artist + TOR original_year + TORY original_year + TOT original_album + TOWN file_owner + TP1 artist + TP2 band + TP3 conductor + TP4 remixer + TPA part_of_a_set + TPB publisher + TPE1 artist + TPE2 band + TPE3 conductor + TPE4 remixer + TPOS part_of_a_set + TPRO produced_notice + TPUB publisher + TRC isrc + TRCK track_number + TRD recording_dates + TRDA recording_dates + TRK track_number + TRSN internet_radio_station_name + TRSO internet_radio_station_owner + TS2 album_artist_sort_order + TSA album_sort_order + TSC composer_sort_order + TSI size + TSIZ size + TSO2 album_artist_sort_order + TSOA album_sort_order + TSOC composer_sort_order + TSOP performer_sort_order + TSOT title_sort_order + TSP performer_sort_order + TSRC isrc + TSS encoder_settings + TSSE encoder_settings + TSST set_subtitle + TST title_sort_order + TT1 content_group_description + TT2 title + TT3 subtitle + TXT lyricist + TXX text + TXXX text + TYE year + TYER year + UFI unique_file_identifier + UFID unique_file_identifier + ULT unsynchronised_lyric + USER terms_of_use + USLT unsynchronised_lyric + WAF url_file + WAR url_artist + WAS url_source + WCM commercial_information + WCOM commercial_information + WCOP copyright + WCP copyright + WOAF url_file + WOAR url_artist + WOAS url_source + WORS url_station + WPAY url_payment + WPB url_publisher + WPUB url_publisher + WXX url_user + WXXX url_user + TFEA featured_artist + TSTU recording_studio + rgad replay_gain_adjustment + + */ + + return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); + } + + /** + * @param string $encoding + * + * @return string + */ + public static function TextEncodingTerminatorLookup($encoding) { + // http://www.id3.org/id3v2.4.0-structure.txt + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + static $TextEncodingTerminatorLookup = array( + 0 => "\x00", // $00 ISO-8859-1. Terminated with $00. + 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => "\x00\x00" + ); + return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00"); + } + + /** + * @param int $encoding + * + * @return string + */ + public static function TextEncodingNameLookup($encoding) { + // http://www.id3.org/id3v2.4.0-structure.txt + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + static $TextEncodingNameLookup = array( + 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00. + 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => 'UTF-16BE' + ); + return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); + } + + /** + * @param string $string + * @param string $terminator + * + * @return string + */ + public static function RemoveStringTerminator($string, $terminator) { + // Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present. + // https://github.com/JamesHeinrich/getID3/issues/121 + // https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227 + if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) { + $string = substr($string, 0, -strlen($terminator)); + } + return $string; + } + + /** + * @param string $string + * + * @return string + */ + public static function MakeUTF16emptyStringEmpty($string) { + if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) { + // if string only contains a BOM or terminator then make it actually an empty string + $string = ''; + } + return $string; + } + + /** + * @param string $framename + * @param int $id3v2majorversion + * + * @return bool|int + */ + public static function IsValidID3v2FrameName($framename, $id3v2majorversion) { + switch ($id3v2majorversion) { + case 2: + return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); + + case 3: + case 4: + return preg_match('#[A-Z][A-Z0-9]{3}#', $framename); + } + return false; + } + + /** + * @param string $numberstring + * @param bool $allowdecimal + * @param bool $allownegative + * + * @return bool + */ + public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + $pattern = '#^'; + $pattern .= ($allownegative ? '\\-?' : ''); + $pattern .= '[0-9]+'; + $pattern .= ($allowdecimal ? '(\\.[0-9]+)?' : ''); + $pattern .= '$#'; + return preg_match($pattern, $numberstring); + } + + /** + * @param string $datestamp + * + * @return bool + */ + public static function IsValidDateStampString($datestamp) { + if (!preg_match('#^[12][0-9]{3}[01][0-9][0123][0-9]$#', $datestamp)) { + return false; + } + $year = substr($datestamp, 0, 4); + $month = substr($datestamp, 4, 2); + $day = substr($datestamp, 6, 2); + if (($year == 0) || ($month == 0) || ($day == 0)) { + return false; + } + if ($month > 12) { + return false; + } + if ($day > 31) { + return false; + } + if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) { + return false; + } + if (($day > 29) && ($month == 2)) { + return false; + } + return true; + } + + /** + * @param int $majorversion + * + * @return int + */ + public static function ID3v2HeaderLength($majorversion) { + return (($majorversion == 2) ? 6 : 10); + } + + /** + * @param string $frame_name + * + * @return string|false + */ + public static function ID3v22iTunesBrokenFrameName($frame_name) { + // iTunes (multiple versions) has been known to write ID3v2.3 style frames + // but use ID3v2.2 frame names, right-padded using either [space] or [null] + // to make them fit in the 4-byte frame name space of the ID3v2.3 frame. + // This function will detect and translate the corrupt frame name into ID3v2.3 standard. + static $ID3v22_iTunes_BrokenFrames = array( + 'BUF' => 'RBUF', // Recommended buffer size + 'CNT' => 'PCNT', // Play counter + 'COM' => 'COMM', // Comments + 'CRA' => 'AENC', // Audio encryption + 'EQU' => 'EQUA', // Equalisation + 'ETC' => 'ETCO', // Event timing codes + 'GEO' => 'GEOB', // General encapsulated object + 'IPL' => 'IPLS', // Involved people list + 'LNK' => 'LINK', // Linked information + 'MCI' => 'MCDI', // Music CD identifier + 'MLL' => 'MLLT', // MPEG location lookup table + 'PIC' => 'APIC', // Attached picture + 'POP' => 'POPM', // Popularimeter + 'REV' => 'RVRB', // Reverb + 'RVA' => 'RVAD', // Relative volume adjustment + 'SLT' => 'SYLT', // Synchronised lyric/text + 'STC' => 'SYTC', // Synchronised tempo codes + 'TAL' => 'TALB', // Album/Movie/Show title + 'TBP' => 'TBPM', // BPM (beats per minute) + 'TCM' => 'TCOM', // Composer + 'TCO' => 'TCON', // Content type + 'TCP' => 'TCMP', // Part of a compilation + 'TCR' => 'TCOP', // Copyright message + 'TDA' => 'TDAT', // Date + 'TDY' => 'TDLY', // Playlist delay + 'TEN' => 'TENC', // Encoded by + 'TFT' => 'TFLT', // File type + 'TIM' => 'TIME', // Time + 'TKE' => 'TKEY', // Initial key + 'TLA' => 'TLAN', // Language(s) + 'TLE' => 'TLEN', // Length + 'TMT' => 'TMED', // Media type + 'TOA' => 'TOPE', // Original artist(s)/performer(s) + 'TOF' => 'TOFN', // Original filename + 'TOL' => 'TOLY', // Original lyricist(s)/text writer(s) + 'TOR' => 'TORY', // Original release year + 'TOT' => 'TOAL', // Original album/movie/show title + 'TP1' => 'TPE1', // Lead performer(s)/Soloist(s) + 'TP2' => 'TPE2', // Band/orchestra/accompaniment + 'TP3' => 'TPE3', // Conductor/performer refinement + 'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by + 'TPA' => 'TPOS', // Part of a set + 'TPB' => 'TPUB', // Publisher + 'TRC' => 'TSRC', // ISRC (international standard recording code) + 'TRD' => 'TRDA', // Recording dates + 'TRK' => 'TRCK', // Track number/Position in set + 'TS2' => 'TSO2', // Album-Artist sort order + 'TSA' => 'TSOA', // Album sort order + 'TSC' => 'TSOC', // Composer sort order + 'TSI' => 'TSIZ', // Size + 'TSP' => 'TSOP', // Performer sort order + 'TSS' => 'TSSE', // Software/Hardware and settings used for encoding + 'TST' => 'TSOT', // Title sort order + 'TT1' => 'TIT1', // Content group description + 'TT2' => 'TIT2', // Title/songname/content description + 'TT3' => 'TIT3', // Subtitle/Description refinement + 'TXT' => 'TEXT', // Lyricist/Text writer + 'TXX' => 'TXXX', // User defined text information frame + 'TYE' => 'TYER', // Year + 'UFI' => 'UFID', // Unique file identifier + 'ULT' => 'USLT', // Unsynchronised lyric/text transcription + 'WAF' => 'WOAF', // Official audio file webpage + 'WAR' => 'WOAR', // Official artist/performer webpage + 'WAS' => 'WOAS', // Official audio source webpage + 'WCM' => 'WCOM', // Commercial information + 'WCP' => 'WCOP', // Copyright/Legal information + 'WPB' => 'WPUB', // Publishers official webpage + 'WXX' => 'WXXX', // User defined URL link frame + ); + if (strlen($frame_name) == 4) { + if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) { + if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) { + return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)]; + } + } + } + return false; + } + +} + diff --git a/config/getid3/module.tag.lyrics3.php b/config/getid3/module.tag.lyrics3.php new file mode 100644 index 000000000..d37bb7a81 --- /dev/null +++ b/config/getid3/module.tag.lyrics3.php @@ -0,0 +1,329 @@ + // +// available at https://github.com/JamesHeinrich/getID3 // +// or https://www.getid3.org // +// or http://getid3.sourceforge.net // +// see readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// module.tag.lyrics3.php // +// module for analyzing Lyrics3 tags // +// dependencies: module.tag.apetag.php (optional) // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers + exit; +} +class getid3_lyrics3 extends getid3_handler +{ + /** + * @return bool + */ + public function Analyze() { + $info = &$this->getid3->info; + + // http://www.volweb.cz/str/tags.htm + + if (!getid3_lib::intValueSupported($info['filesize'])) { + $this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); + return false; + } + + $this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] + $lyrics3offset = null; + $lyrics3version = null; + $lyrics3size = null; + $lyrics3_id3v1 = $this->fread(128 + 9 + 6); + $lyrics3lsz = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size + $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 + $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 + + if ($lyrics3end == 'LYRICSEND') { + // Lyrics3v1, ID3v1, no APE + + $lyrics3size = 5100; + $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; + $lyrics3version = 1; + + } elseif ($lyrics3end == 'LYRICS200') { + // Lyrics3v2, ID3v1, no APE + + // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); + $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; + $lyrics3version = 2; + + } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) { + // Lyrics3v1, no ID3v1, no APE + + $lyrics3size = 5100; + $lyrics3offset = $info['filesize'] - $lyrics3size; + $lyrics3version = 1; + $lyrics3offset = $info['filesize'] - $lyrics3size; + + } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) { + + // Lyrics3v2, no ID3v1, no APE + + $lyrics3size = (int) strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3offset = $info['filesize'] - $lyrics3size; + $lyrics3version = 2; + + } else { + + if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) { + + $this->fseek($info['ape']['tag_offset_start'] - 15); + $lyrics3lsz = $this->fread(6); + $lyrics3end = $this->fread(9); + + if ($lyrics3end == 'LYRICSEND') { + // Lyrics3v1, APE, maybe ID3v1 + + $lyrics3size = 5100; + $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; + $info['avdataend'] = $lyrics3offset; + $lyrics3version = 1; + $this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability'); + + } elseif ($lyrics3end == 'LYRICS200') { + // Lyrics3v2, APE, maybe ID3v1 + + $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; + $lyrics3version = 2; + $this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability'); + + } + + } + + } + + if (isset($lyrics3offset) && isset($lyrics3version) && isset($lyrics3size)) { + $info['avdataend'] = $lyrics3offset; + $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); + + if (!isset($info['ape'])) { + if (isset($info['lyrics3']['tag_offset_start'])) { + $GETID3_ERRORARRAY = &$info['warning']; + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp); + $getid3_apetag = new getid3_apetag($getid3_temp); + $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start']; + $getid3_apetag->Analyze(); + if (!empty($getid3_temp->info['ape'])) { + $info['ape'] = $getid3_temp->info['ape']; + } + if (!empty($getid3_temp->info['replay_gain'])) { + $info['replay_gain'] = $getid3_temp->info['replay_gain']; + } + unset($getid3_temp, $getid3_apetag); + } else { + $this->warning('Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)'); + } + } + + } + + return true; + } + + /** + * @param int $endoffset + * @param int $version + * @param int $length + * + * @return bool + */ + public function getLyrics3Data($endoffset, $version, $length) { + // http://www.volweb.cz/str/tags.htm + + $info = &$this->getid3->info; + + if (!getid3_lib::intValueSupported($endoffset)) { + $this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); + return false; + } + + $this->fseek($endoffset); + if ($length <= 0) { + return false; + } + $rawdata = $this->fread($length); + + $ParsedLyrics3 = array(); + + $ParsedLyrics3['raw']['lyrics3version'] = $version; + $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; + $ParsedLyrics3['tag_offset_start'] = $endoffset; + $ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1; + + if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') { + if (strpos($rawdata, 'LYRICSBEGIN') !== false) { + + $this->warning('"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version); + $info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN'); + $rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN')); + $length = strlen($rawdata); + $ParsedLyrics3['tag_offset_start'] = $info['avdataend']; + $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; + + } else { + + $this->error('"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'); + return false; + + } + + } + + switch ($version) { + + case 1: + if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') { + $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9)); + $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); + } else { + $this->error('"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'); + return false; + } + break; + + case 2: + if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') { + $ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ + $rawdata = $ParsedLyrics3['raw']['unparsed']; + while (strlen($rawdata) > 0) { + $fieldname = substr($rawdata, 0, 3); + $fieldsize = (int) substr($rawdata, 3, 5); + $ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize); + $rawdata = substr($rawdata, 3 + 5 + $fieldsize); + } + + if (isset($ParsedLyrics3['raw']['IND'])) { + $i = 0; + $flagnames = array('lyrics', 'timestamps', 'inhibitrandom'); + foreach ($flagnames as $flagname) { + if (strlen($ParsedLyrics3['raw']['IND']) > $i++) { + $ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1)); + } + } + } + + $fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author'); + foreach ($fieldnametranslation as $key => $value) { + if (isset($ParsedLyrics3['raw'][$key])) { + $ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]); + } + } + + if (isset($ParsedLyrics3['raw']['IMG'])) { + $imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']); + foreach ($imagestrings as $key => $imagestring) { + if (strpos($imagestring, '||') !== false) { + $imagearray = explode('||', $imagestring); + $ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : ''); + $ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : ''); + $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : ''); + } + } + } + if (isset($ParsedLyrics3['raw']['LYR'])) { + $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); + } + } else { + $this->error('"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'); + return false; + } + break; + + default: + $this->error('Cannot process Lyrics3 version '.$version.' (only v1 and v2)'); + return false; + } + + + if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) { + $this->warning('ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'); + unset($info['id3v1']); + foreach ($info['warning'] as $key => $value) { + if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { + unset($info['warning'][$key]); + sort($info['warning']); + break; + } + } + } + + $info['lyrics3'] = $ParsedLyrics3; + + return true; + } + + /** + * @param string $rawtimestamp + * + * @return int|false + */ + public function Lyrics3Timestamp2Seconds($rawtimestamp) { + if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) { + return (int) (((int) $regs[1] * 60) + (int) $regs[2]); + } + return false; + } + + /** + * @param array $Lyrics3data + * + * @return bool + */ + public function Lyrics3LyricsTimestampParse(&$Lyrics3data) { + $lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']); + $notimestamplyricsarray = array(); + foreach ($lyricsarray as $key => $lyricline) { + $regs = array(); + unset($thislinetimestamps); + while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) { + $thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]); + $lyricline = str_replace($regs[0], '', $lyricline); + } + $notimestamplyricsarray[$key] = $lyricline; + if (isset($thislinetimestamps) && is_array($thislinetimestamps)) { + sort($thislinetimestamps); + foreach ($thislinetimestamps as $timestampkey => $timestamp) { + if (isset($Lyrics3data['synchedlyrics'][$timestamp])) { + // timestamps only have a 1-second resolution, it's possible that multiple lines + // could have the same timestamp, if so, append + $Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline; + } else { + $Lyrics3data['synchedlyrics'][$timestamp] = $lyricline; + } + } + } + } + $Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray); + if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) { + ksort($Lyrics3data['synchedlyrics']); + } + return true; + } + + /** + * @param string $char + * + * @return bool|null + */ + public function IntString2Bool($char) { + if ($char == '1') { + return true; + } elseif ($char == '0') { + return false; + } + return null; + } +} diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 000000000..bc2bc7c67 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/config/mail/Exception.php b/config/mail/Exception.php new file mode 100644 index 000000000..52eaf9515 --- /dev/null +++ b/config/mail/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; + } +} diff --git a/config/mail/PHPMailer.php b/config/mail/PHPMailer.php new file mode 100644 index 000000000..e14cdbf57 --- /dev/null +++ b/config/mail/PHPMailer.php @@ -0,0 +1,5126 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = ''; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = ''; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. + * + * @var string + */ + public $AuthType = ''; + + /** + * An implementation of the PHPMailer OAuthTokenProvider interface. + * + * @var OAuthTokenProvider + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available text strings for the current language. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.7.1'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if ((int)ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address + * @param string $name An optional username associated with the address + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + imap_errors(); + foreach ($list as $address) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + defined('MB_CASE_UPPER') && + preg_match('/^=\?.*\?=$/s', $address->personal) + ) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $address->personal = str_replace('_', '=20', $address->personal); + //Decode the name + $address->personal = mb_decode_mimeheader($address->personal); + mb_internal_encoding($origCharset); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + //If this name is encoded, decode it + if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $name = str_replace('_', '=20', $name); + //Decode the name + $name = mb_decode_mimeheader($name); + mb_internal_encoding($origCharset); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim((string)$address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error($this->lang('buggy_php'), E_USER_WARNING); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->{$address_kind} + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = is_file($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + if ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! + * + * @return bool Returns true if the requested language was loaded, false otherwise. + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + + //Validate $langcode + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P', 'mod'); +} +} else + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
'); +?> \ No newline at end of file diff --git a/dashboard/account/banPerson.php b/dashboard/account/banPerson.php new file mode 100644 index 000000000..d5c184aa7 --- /dev/null +++ b/dashboard/account/banPerson.php @@ -0,0 +1,144 @@ +title($dl->getLocalizedString("banUserPlace")); +$dl->printFooter('../'); +if(!$gs->checkPermission($_SESSION["accountID"], "dashboardModTools")) { + die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+

'.$dl->getLocalizedString("noPermission").'

+
+ +
+
', 'mod')); +} +if(isset($_POST["person"]) && isset($_POST["personType"]) && isset($_POST["banType"])) { + if(!Captcha::validateCaptcha()) { + die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'mod')); + } + $person = trim(ExploitPatch::rucharclean($_POST["person"])); + $personType = ExploitPatch::number($_POST["personType"]); + $reason = trim(ExploitPatch::rucharclean($_POST["reason"])); + $banType = ExploitPatch::number($_POST["banType"]); + $expires = !empty($_POST['expires']) ? (new DateTime($_POST['expires']))->getTimestamp() : 2147483647; + $alsoBanIP = $_POST["alsoBanIP"] == 'on' && $personType != 2; + $check = $gs->getBan($person, $personType, $banType); + $triedToBanYourself = $getIP = false; + switch($personType) { + case 0: + if($_SESSION['accountID'] == $person) $triedToBanYourself = true; + elseif($alsoBanIP) { + $getIP = $db->prepare('SELECT IP FROM users WHERE extID = :person'); + $getIP->execute([':person' => $person]); + $getIP = $getIP->fetchColumn(); + } + break; + case 1: + if($gs->getUserID($_SESSION['accountID'], $gs->getAccountName($_SESSION['accountID'])) == $person) $triedToBanYourself = true; + elseif($alsoBanIP) { + $getIP = $db->prepare('SELECT IP FROM users WHERE userID = :person'); + $getIP->execute([':person' => $person]); + $getIP = $getIP->fetchColumn(); + } + break; + case 2: + if($gs->IPForBan($gs->getIP()) == $gs->IPForBan($person)) $triedToBanYourself = true; + break; + } + if($triedToBanYourself) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("banYourself").'

+ +
+
', 'mod')); + if($check) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("userIsBanned").'

+ +
+
', 'mod')); + if(time() >= $expires) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noBanInPast").'

+ +
+
', 'mod')); + $gs->banPerson($_SESSION['accountID'], $person, $reason, $banType, $personType, $expires); + if($getIP) $gs->banPerson($_SESSION['accountID'], $getIP, $reason, $banType, 2, $expires); + $banTypes = ['playerTop', 'creatorTop', 'levelUploading', 'commentBan', 'account']; + $dl->printSong('
+

'.$dl->getLocalizedString("banUserPlace").'

+
+

'.sprintf($dl->getLocalizedString("banSuccess"), $person, $dl->getLocalizedString($banTypes[$banType]), date("d.m.Y G:i", $expires)).'

+ +
+
', 'mod'); +} else { +$dl->printSong('
+

'.$dl->getLocalizedString("banUserPlace").'

+

'.$dl->getLocalizedString("banDesc").'

+
+
+ +
+
+
+
+ +

'.$dl->getLocalizedString('alsoBanIP').'

+
+ '.Captcha::displayCaptcha(true).' + +
+
+', 'mod'); +} +?> \ No newline at end of file diff --git a/dashboard/account/changePassword.php b/dashboard/account/changePassword.php new file mode 100644 index 000000000..282fa26fd --- /dev/null +++ b/dashboard/account/changePassword.php @@ -0,0 +1,140 @@ +title($dl->getLocalizedString("changePassTitle")); +$dl->printFooter('../'); +if(isset($_SESSION["accountID"]) AND $_SESSION["accountID"] != 0){ +if($_POST["oldpassword"] != "" AND $_POST["newpassword"] != "" AND $_POST["newpassword"] == $_POST["newpassconfirm"]) { + if(!Captcha::validateCaptcha()) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'account'); + die(); + } + $getAccountData = $db->prepare("SELECT * FROM accounts WHERE accountID = :accountID"); + $getAccountData->execute([':accountID' => $_SESSION["accountID"]]); + $getAccountData = $getAccountData->fetch(); + $userName = $gs->getAccountName($_SESSION["accountID"]); + $oldpass = ExploitPatch::remove($_POST["oldpassword"]); + $newpass = ExploitPatch::remove($_POST["newpassword"]); + if($oldpass == $newpass){ + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("samePass").'

+ +
+
', 'account'); + die(); + } + $pass = GeneratePass::isValidUsrname($userName, $oldpass); + $salt = ""; +if($pass == 1) { + $passhash = password_hash($newpass, PASSWORD_DEFAULT); + $gjp2 = GeneratePass::GJP2hash($newpass); + $auth = $gs->randomString(8); + $query = $db->prepare("UPDATE accounts SET password=:password, gjp2 = :gjp, salt=:salt, auth=:auth WHERE userName=:userName"); + $query->execute([':password' => $passhash, ':userName' => $userName, ':salt' => $salt, ':gjp' => $gjp2, ':auth' => $auth]); + $gs->sendLogsAccountChangeWebhook($_SESSION["accountID"], $_SESSION["accountID"], $getAccountData); + $_SESSION["accountID"] = 0; + setcookie('auth', 'no', 2147483647, '/'); + $dl->printSong('
+

'.$dl->getLocalizedString("changePassTitle").'

+
+

'.$dl->getLocalizedString("changedPass").'

+ +
+
', 'account'); + //decrypting save + $query = $db->prepare("SELECT accountID FROM accounts WHERE userName=:userName"); + $query->execute([':userName' => $userName]); + $accountID = $query->fetchColumn(); + $saveData = file_get_contents("../".$dbPath."data/accounts/$accountID"); + if(file_exists("../".$dbPath."data/accounts/keys/$accountID")){ + $protected_key_encoded = file_get_contents("../".$dbPath."data/accounts/keys/$accountID"); + if($protected_key_encoded != ""){ + $protected_key = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded); + $user_key = $protected_key->unlockKey($oldpass); + try { + $saveData = Crypto::decrypt($saveData, $user_key); + } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) { + exit("Unable to update save data encryption"); + } + file_put_contents("../".$dbPath."data/accounts/$accountID",$saveData); + file_put_contents("../".$dbPath."data/accounts/keys/$accountID",""); + } + } +}else{ + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("wrongPass").'

+ +
+
', 'account'); +} + +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("changePassTitle").'

+
+

'.$dl->getLocalizedString("changePassDesc").'

+
+
+ '.$dl->getLocalizedString("passDontMatch").' +
+ '.Captcha::displayCaptcha(true).' + +
+
', 'account'); +}} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account'); +} +?> diff --git a/dashboard/account/changeUsername.php b/dashboard/account/changeUsername.php new file mode 100644 index 000000000..ef47efbd1 --- /dev/null +++ b/dashboard/account/changeUsername.php @@ -0,0 +1,127 @@ +title($dl->getLocalizedString("changeNickTitle")); +$dl->printFooter('../'); +if(!isset($_SESSION["accountID"]) || $_SESSION["accountID"] == 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account')); +if($_POST["oldnickname"] != "" AND $_POST["newnickname"] != "" AND $_POST["password"] != "") { + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'account')); + } + $userName = $gs->getAccountName($_SESSION["accountID"]); + $accID = $_SESSION["accountID"]; + $getAccountData = $db->prepare("SELECT * FROM accounts WHERE accountID = :accountID"); + $getAccountData->execute([':accountID' => $accID]); + $getAccountData = $getAccountData->fetch(); + $oldnick = ExploitPatch::charclean($_POST["oldnickname"]); + $newnick = str_replace(' ', '', ExploitPatch::charclean($_POST["newnickname"])); + if($oldnick != $userName) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("wrongNick").'

+ +
+
', 'account')); + } + if($userName == $newnick || $oldnick == $newnick) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("sameNick").'

+ +
+
', 'account')); + } + $pass = $_POST["password"]; + $pass = GeneratePass::isValidUsrname($userName, $pass); + $salt = ""; + if($pass == 1) { + $query = $db->prepare("SELECT count(*) FROM accounts WHERE userName LIKE :userName"); + $query->execute([':userName' => $newnick]); + $count = $query->fetchColumn(); + if($count > 0) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("alreadyUsedNick").'

+ +
+
', 'account')); + } + $auth = $gs->randomString(8); + $query = $db->prepare("UPDATE accounts SET userName = :userName, salt = :salt, auth = :auth WHERE accountID = :accountid"); + $query->execute([':userName' => $newnick, ':salt' => $salt, ':accountid' => $accID, ':auth' => $auth]); + $gs->sendLogsAccountChangeWebhook($accID, $accID, $getAccountData); + if($automaticCron) Cron::fixUsernames($accID, false); + $_SESSION["accountID"] = 0; + setcookie('auth', 'no', 2147483647, '/'); + $dl->printSong('
+

'.$dl->getLocalizedString("changeNickTitle").'

+
+

'.$dl->getLocalizedString("changedNick").'

+ +
+
', 'account'); + } else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("wrongPass").'

+ +
+
', 'account'); + } +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("changeNickTitle").'

+
+

'.$dl->getLocalizedString("changeNickDesc").'

+
+
+
+ '.Captcha::displayCaptcha(true).' + +
+
', 'account'); +} +?> \ No newline at end of file diff --git a/dashboard/account/forceChange.php b/dashboard/account/forceChange.php new file mode 100644 index 000000000..c18937794 --- /dev/null +++ b/dashboard/account/forceChange.php @@ -0,0 +1,142 @@ +printFooter('../'); +$acc = $_SESSION["accountID"]; +if(!$gs->checkPermission($acc, 'dashboardForceChangePassNick')) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+

'.$dl->getLocalizedString("noPermission").'

+
+ +
+
', 'mod')); +if($_POST["type"] == 0) { + $type = 'Password'; + $inputtype = ''; +}else { + $type = 'Nick'; + $inputtype = ''; +} +$dl->title($dl->getLocalizedString("force".$type)); +if(!empty($_POST["userID"]) AND !empty($_POST[$type])) { + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'mod')); + } + if(!empty($_POST["Nick"])) { + $newnick = str_replace(' ', '', ExploitPatch::charclean($_POST["Nick"])); + if(!is_numeric($_POST["userID"])) $accID = $gs->getAccountIDFromName($_POST["userID"]); + else $accID = ExploitPatch::number($_POST["userID"]); + $salt = ''; + $query = $db->prepare("SELECT count(*) FROM accounts WHERE userName LIKE :userName"); + $query->execute([':userName' => $newnick]); + $count = $query->fetchColumn(); + if($count > 0) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("alreadyUsedNick").'

+ +
+
', 'mod')); + } + $getAccountData = $db->prepare("SELECT * FROM accounts WHERE accountID = :accountID"); + $getAccountData->execute([':accountID' => $accID]); + $getAccountData = $getAccountData->fetch(); + $auth = $gs->randomString(8); + $query = $db->prepare("UPDATE accounts SET userName = :userName, salt = :salt, auth = :auth WHERE accountID = :accountid"); + $query->execute([':userName' => $newnick, ':salt' => $salt, ':accountid' => $accID, ':auth' => $auth]); + $gs->sendLogsAccountChangeWebhook($accID, $acc, $getAccountData); + $discord = $gs->hasDiscord($accID); + if($discord) $gs->changeDiscordUsername($discord, $newnick); + if($automaticCron) Cron::fixUsernames($_SESSION['accountID'], false); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, timestamp, account) VALUES ('26',:userID, :type, :timestamp,:account)"); + $query->execute([':userID' => $accID, ':timestamp' => time(), ':type' => $type, ':account' => $acc]); + exit($dl->printSong('
+

'.$dl->getLocalizedString("changeNickTitle").'

+
+

'.sprintf($dl->getLocalizedString("forceChangedNick"), $newnick).'

+ +
+
', 'mod')); + } elseif($type == 'Password') { + $newpass = $_POST["Password"]; + if(is_numeric($_POST["userID"])) { + $userName = $gs->getAccountName($_POST["userID"]); + $accID = $_POST["userID"]; + } + else { + $userName = ExploitPatch::remove($_POST["userID"]); + $accID = $gs->getAccountIDFromName($_POST["userID"]); + } + $salt = ''; + $passhash = password_hash($newpass, PASSWORD_DEFAULT); + $gjp2 = GeneratePass::GJP2hash($newpass); + $auth = $gs->randomString(8); + $getAccountData = $db->prepare("SELECT * FROM accounts WHERE accountID = :accountID"); + $getAccountData->execute([':accountID' => $accID]); + $getAccountData = $getAccountData->fetch(); + $query = $db->prepare("UPDATE accounts SET password=:password, gjp2 = :gjp, salt=:salt, auth=:auth WHERE userName=:userName"); + $query->execute([':password' => $passhash, ':userName' => $userName, ':salt' => $salt, ':gjp' => $gjp2, ':auth' => $auth]); + $gs->sendLogsAccountChangeWebhook($accID, $acc, $getAccountData); + $accountID = $gs->getAccountIDFromName($userName); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, timestamp, account) VALUES ('26',:userID, :type, :timestamp,:account)"); + $query->execute([':userID' => $accountID, ':timestamp' => time(), ':type' => $type, ':account' => $acc]); + $saveData = file_get_contents("../".$dbPath."data/accounts/$accountID"); + $dl->printSong('
+

'.$dl->getLocalizedString("changePassTitle").'

+
+

'.sprintf($dl->getLocalizedString("forceChangedPass"), $userName).'

+ +
+
', 'mod'); + } +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("force".$type).'

+

'.$dl->getLocalizedString("force".$type.'Desc').'

+
+ + +
+
+ '.$inputtype.' +
+
+ '.Captcha::displayCaptcha(true).' + +
+
', 'mod'); +} +?> \ No newline at end of file diff --git a/dashboard/account/mods.php b/dashboard/account/mods.php new file mode 100644 index 000000000..00c6331b6 --- /dev/null +++ b/dashboard/account/mods.php @@ -0,0 +1,41 @@ +checkPermission($_SESSION["accountID"], 'dashboardAddMod')) { + $priority = $gs->getMaxValuePermission($_SESSION["accountID"], 'priority'); + $role = ExploitPatch::numbercolon($_GET["role"]); + $check = $db->prepare('SELECT priority FROM roles WHERE roleID = :role'); + $check->execute([':role' => $role]); + $check = $check->fetchColumn(); + $mod = ExploitPatch::number($_GET["acc"]); + $mod2 = $gs->getAccountName($mod); + if($_SESSION['accountID'] == $mod) die('-1'); + $query = $db->prepare("SELECT * FROM roleassign WHERE accountID = :mod"); + $query->execute([':mod' => $mod]); + $res = $query->fetch(); + if($role != "-1") { + if($check >= $priority) die("-1"); + $change = $db->prepare("UPDATE roleassign SET roleID = :r WHERE assignID = :i"); + $change = $change->execute([':r' => $role, ':i' => $id]); + } else { + $change = $db->prepare("DELETE FROM roleassign WHERE assignID = :i"); + $change = $change->execute([':i' => $id]); + } + if($change) echo "1"; else die("-1"); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3) VALUES ('24', :value, :timestamp, :account, :value2, :value3)"); + $query->execute([':value' => $mod2, ':timestamp' => time(), ':account' => $_SESSION["accountID"], ':value2' => $mod, ':value3' => $role]); + $gs->sendLogsModChangeWebhook($res['accountID'], $_SESSION['accountID'], $res['assignID'], $res); + } else { + $pck = $db->prepare("SELECT * FROM roleassign WHERE assignID = :id"); + $pck->execute([':id' => $id]); + $map = $pck->fetch(); + echo $map["assignID"].' | '.$map["roleID"].' | '.$map["accountID"]." | ".$gs->getAccountName($map["accountID"]); + } +} +?> \ No newline at end of file diff --git a/dashboard/account/unlisted.php b/dashboard/account/unlisted.php index f04e75d2d..cc4b71d89 100644 --- a/dashboard/account/unlisted.php +++ b/dashboard/account/unlisted.php @@ -5,7 +5,7 @@ $dl = new dashboardLib(); require "../../incl/lib/mainLib.php"; $gs = new mainLib(); -include "../../incl/lib/connection.php"; +require "../../incl/lib/connection.php"; if(!isset($_SESSION["accountID"]) OR $_SESSION["accountID"] == 0){ header("Location: ../login/login.php"); exit(); diff --git a/dashboard/api/addSong.php b/dashboard/api/addSong.php new file mode 100644 index 000000000..29a1a4d2c --- /dev/null +++ b/dashboard/api/addSong.php @@ -0,0 +1,57 @@ +getAccountIDFromName('ObeyGDBot'); +if(!$reuploadID) $reuploadID = 0; +if(!$download) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 1, 'message' => 'Please specify song download link.'])); +$songReupload = $gs->songReupload($download, $author, $name, $reuploadID); +if($songReupload < 0) { + $error = mb_substr($songReupload, 0, 2); + switch($error) { + case '-2': + $errorNumber = 2; + $errorMessage = 'Your download link is not link.'; + break; + case '-3': + $errorNumber = 3; + $errorMessage = 'This song was already reuploaded.'; + break; + case '-4': + $errorNumber = 4; + $errorMessage = 'Your download link is not link to an audio.'; + break; + default: + $errorNumber = 0; + $errorMessage = 'Unexpected error.'; + break; + } + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => $errorNumber, 'message' => $errorMessage])); +} else { + $songInfo = $gs->getSongInfo($songReupload); + $song = [ + 'ID' => $songInfo['ID'], + 'author' => $songInfo['authorName'], + 'name' => $songInfo['name'], + 'size' => $songInfo['size'], + 'download' => urldecode($songInfo['download']), + 'reuploader' => [ + 'accountID' => $reuploadID, + 'userID' => $gs->getUserID($reuploadID, 'ObeyGDBot'), + 'username' => 'ObeyGDBot' + ], + 'newgrounds' => false, + 'customSong' => true + ]; + exit(json_encode(['dashboard' => true, 'success' => true, 'song' => $song])); +} +?> \ No newline at end of file diff --git a/dashboard/api/getGMD.php b/dashboard/api/getGMD.php new file mode 100644 index 000000000..fe595bda0 --- /dev/null +++ b/dashboard/api/getGMD.php @@ -0,0 +1,32 @@ + false, 'error' => 0, 'message' => 'Invalid level ID.'])); +$level = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); +$level->execute([':levelID' => $levelID]); +$level = $level->fetch(); +if(!$level) exit(json_encode(['success' => false, 'error' => 1, 'message' => 'Level was not found!'])); +$isPlayerAnAdmin = false; +if($unlistedLevelsForAdmins) { + $checkAdmin = $db->prepare('SELECT isAdmin FROM accounts WHERE accountID = :accountID'); + $checkAdmin->execute([':accountID' => $accountID]); + $checkAdmin = $checkAdmin->fetchColumn(); + if($checkAdmin) $isPlayerAnAdmin = true; +} +if($level["unlisted2"] == 1) if(!($level["extID"] == $accountID || $gs->isFriends($accountID, $level["extID"])) && !$isPlayerAnAdmin) exit(json_encode(['success' => false, 'error' => 1, 'message' => 'Level was not found!'])); +$GMDFile = $gs->getGMDFile($levelID); +if(!$GMDFile) exit(json_encode(['success' => false, 'error' => 2, 'message' => 'Level data was not found!'])); +exit(json_encode(['success' => true, 'levelName' => $level['levelName'], 'GMD' => base64_encode($GMDFile)])); +?> \ No newline at end of file diff --git a/dashboard/api/getLastFeaturedID.php b/dashboard/api/getLastFeaturedID.php new file mode 100644 index 000000000..3700aaa6b --- /dev/null +++ b/dashboard/api/getLastFeaturedID.php @@ -0,0 +1,16 @@ +prepare("SELECT starFeatured FROM levels ORDER BY starFeatured DESC LIMIT 1"); +$featuredID->execute(); +$featuredID = $featuredID->fetchColumn(); + +if (!$featuredID) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 1, 'message' => 'No featured level found.'])); + +exit(json_encode(['dashboard' => true, 'success' => true, 'id' => $featuredID])); +?> \ No newline at end of file diff --git a/dashboard/api/login.php b/dashboard/api/login.php new file mode 100644 index 000000000..81a8e8560 --- /dev/null +++ b/dashboard/api/login.php @@ -0,0 +1,76 @@ + false, 'error' => '-2'])); + exit(json_encode(['success' => false, 'error' => '-1'])); + } + $accountID = $gs->getAccountIDFromName($userName); + if($accountID == 0) exit(json_encode(['success' => false, 'error' => '-3'])); + $query = $db->prepare("SELECT auth FROM accounts WHERE accountID = :accountID"); + $query->execute([':accountID' => $accountID]); + $auth = $query->fetchColumn(); + if($auth == 'none') { + $auth = $gs->randomString(8); + $query = $db->prepare("UPDATE accounts SET auth = :auth WHERE accountID = :accountID"); + $query->execute([':auth' => $auth, ':accountID' => $accountID]); + } + $color = $gs->getAccountCommentColor($accountID); + $gs->logAction($accountID, 2); + $userID = $gs->getUserID($accountID, $userName); + $action = $db->prepare("SELECT * FROM users WHERE userID = :userID"); + $action->execute([':userID' => $userID]); + $action = $action->fetch(); + $iconType = ($action['iconType'] > 8) ? 0 : $action['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $action['accIcon']], 1 => ['type' => 'ship', 'value' => $action['accShip']], 2 => ['type' => 'ball', 'value' => $action['accBall']], 3 => ['type' => 'ufo', 'value' => $action['accBird']], 4 => ['type' => 'wave', 'value' => $action['accDart']], 5 => ['type' => 'robot', 'value' => $action['accRobot']], 6 => ['type' => 'spider', 'value' => $action['accSpider']], 7 => ['type' => 'swing', 'value' => $action['accSwing']], 8 => ['type' => 'jetpack', 'value' => $action['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = $iconsRendererServer.'/icon.png?type=' . $iconTypeMap[$iconType]['type'] . '&value=' . $iconValue . '&color1=' . $action['color1'] . '&color2=' . $action['color2'] . ($action['accGlow'] != 0 ? '&glow=' . $action['accGlow'] . '&color3=' . $action['color3'] : ''); + $clanID = $gs->isPlayerInClan($accountID); + if($clanID) { + $clan = $gs->getClanInfo($clanID); + $clanArray = [ + 'name' => $clan['clan'], + 'color' => $clan['color'] + ]; + } else $clanArray = []; + exit(json_encode(["success" => true, "user" => $userName, "accountID" => $accountID, "auth" => $auth, "color" => $color, "mainIcon" => $avatarImg, 'clan' => $clanArray])); +} elseif(isset($_GET["auth"])) { + $auth = ExploitPatch::charclean($_GET["auth"]); + if(empty($auth)) exit(json_encode(['success' => false, 'error' => '-3'])); + $check = GeneratePass::isValidToken($auth); + if(!is_array($check)) exit(json_encode(['success' => false, 'error' => $check])); + else { + $gs->logAction($check['accountID'], 2); + $action = $db->prepare("SELECT * FROM users WHERE userID = :userID"); + $action->execute([':userID' => $check['userID']]); + $action = $action->fetch(); + $iconType = ($action['iconType'] > 8) ? 0 : $action['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $action['accIcon']], 1 => ['type' => 'ship', 'value' => $action['accShip']], 2 => ['type' => 'ball', 'value' => $action['accBall']], 3 => ['type' => 'ufo', 'value' => $action['accBird']], 4 => ['type' => 'wave', 'value' => $action['accDart']], 5 => ['type' => 'robot', 'value' => $action['accRobot']], 6 => ['type' => 'spider', 'value' => $action['accSpider']], 7 => ['type' => 'swing', 'value' => $action['accSwing']], 8 => ['type' => 'jetpack', 'value' => $action['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = $iconsRendererServer.'/icon.png?type=' . $iconTypeMap[$iconType]['type'] . '&value=' . $iconValue . '&color1=' . $action['color1'] . '&color2=' . $action['color2'] . ($action['accGlow'] != 0 ? '&glow=' . $action['accGlow'] . '&color3=' . $action['color3'] : ''); + $clanID = $gs->isPlayerInClan($check['accountID']); + if($clanID) { + $clan = $gs->getClanInfo($clanID); + $clanArray = [ + 'name' => $clan['clan'], + 'color' => $clan['color'] + ]; + } else $clanArray = []; + exit(json_encode(['success' => true, 'accountID' => $check['accountID'], 'userID' => $check['userID'], 'user' => $check["userName"], 'color' => $check['color'], "mainIcon" => $avatarImg, 'clan' => $clanArray])); + } +} else exit(json_encode(['success' => false, 'error' => '0'])); +?> \ No newline at end of file diff --git a/dashboard/api/makePost.php b/dashboard/api/makePost.php new file mode 100644 index 000000000..fb47dc9d0 --- /dev/null +++ b/dashboard/api/makePost.php @@ -0,0 +1,51 @@ + true, 'success' => false, 'error' => 1, 'message' => 'Please supply a valid account credentials.'])); +} +$body = trim(ExploitPatch::rucharclean($_POST['body'])); +if(empty($body)) { + http_response_code(400); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => 'Please enter post message.'])); +} +$userID = $gs->getUserID($accountID); +$checkBan = $gs->getPersonBan($accountID, $userID, 3); +if($checkBan) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 3, 'message' => 'You are banned!', 'reason' => base64_decode($checkBan['reason']), 'expires' => $checkBan['expires']])); +} +$query = $db->prepare("SELECT timestamp FROM acccomments WHERE userID = :userID ORDER BY timestamp DESC LIMIT 1"); +$query->execute([':userID' => $userID]); +$res = $query->fetch(); +$time = time() - 5; +if($res["timestamp"] > $time) { + http_response_code(400); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 4, 'message' => 'You send posts too fast.'])); +} +if($enableCommentLengthLimiter && strlen($body) > $maxAccountCommentLength) { + http_response_code(400); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 5, 'message' => 'Your post is too long.'])); +} else { + $accountUsername = $gs->getAccountName($accountID); + $body = ExploitPatch::url_base64_encode($body); + $query = $db->prepare("INSERT INTO acccomments (userID, userName, comment, timestamp) VALUES (:userID, :name, :body, :time)"); + $query->execute([':userID' => $userID, ':name' => $accountUsername, ':body' => $body, ':time' => time()]); + $gs->logAction($accountID, 14, $accountUsername, $body, $db->lastInsertId()); + Automod::checkAccountPostsSpamming($userID); + exit(json_encode(['dashboard' => true, 'success' => true])); +} +exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 6, 'message' => 'Unexpected error.'])); +?> \ No newline at end of file diff --git a/dashboard/api/profile.php b/dashboard/api/profile.php new file mode 100644 index 000000000..b88688e15 --- /dev/null +++ b/dashboard/api/profile.php @@ -0,0 +1,193 @@ + false, 'error' => $check])); + if(isset($json['accountID'])) $accountID = $json['accountID']; + else $accountID = $check['accountID']; +} +if(empty($accountID)) exit(json_encode(['success' => false, 'error' => 0, 'message' => 'You didn\'t specify account ID.'])); +$profile = $db->prepare('SELECT * FROM users WHERE extID = :accountID'); +$profile->execute([':accountID' => $accountID]); +$profile = $profile->fetch(); +if(!$profile) exit(json_encode(['success' => false, 'error' => 1, 'message' => 'Nothing was found.'])); +$clan = $stats = $icons = $bans = $posts = $demonsCount = $starsCount = $platformerCount = $bans = []; +if($profile['clan']) { + $clanInfo = $gs->getClanInfo($profile['clan']); + if($clanInfo) { + $clan = [ + 'ID' => (int)$clanInfo['ID'], + 'name' => $clanInfo['clan'], + 'tag' => $clanInfo['tag'], + 'desc' => $clanInfo['desc'], + 'owner' => [ + 'accountID' => (int)$clanInfo['clanOwner'], + 'userID' => (int)$gs->getUserID($clanInfo['clanOwner']), + 'userName' => $gs->getAccountName($clanInfo['clanOwner']) + ], + 'color' => $clanInfo['color'], + 'isClosed' => $clanInfo['isClosed'] == 1, + 'creationDate' => (int)$clanInfo['creationDate'] + ]; + } +} +if($profile['dinfo']) { + $demonsArray = explode(',', $profile['dinfo']); + $demonsCount = [ + 'easy' => [ + 'classic' => (int)$demonsArray[0], + 'platformer' => (int)$demonsArray[5] + ], + 'medium' => [ + 'classic' => (int)$demonsArray[1], + 'platformer' => (int)$demonsArray[6] + ], + 'hard' => [ + 'classic' => (int)$demonsArray[2], + 'platformer' => (int)$demonsArray[7] + ], + 'insane' => [ + 'classic' => (int)$demonsArray[3], + 'platformer' => (int)$demonsArray[8] + ], + 'extreme' => [ + 'classic' => (int)$demonsArray[4], + 'platformer' => (int)$demonsArray[9] + ], + 'weekly' => (int)$demonsArray[10], + 'gauntlet' => (int)$demonsArray[11] + ]; +} +if($profile['sinfo']) { + $starsArray = explode(',', $profile['sinfo']); + $starsCount = [ + 'auto' => (int)$starsArray[0], + 'easy' => (int)$starsArray[1], + 'normal' => (int)$starsArray[2], + 'hard' => (int)$starsArray[3], + 'harder' => (int)$starsArray[4], + 'extreme' => (int)$starsArray[5], + 'daily' => (int)$starsArray[6], + 'weekly' => (int)$starsArray[7] + ]; +} +if($profile['pinfo']) { + $platformerArray = explode(',', $profile['pinfo']); + $platformerCount = [ + 'auto' => (int)$platformerArray[0], + 'easy' => (int)$platformerArray[1], + 'normal' => (int)$platformerArray[2], + 'hard' => (int)$platformerArray[3], + 'harder' => (int)$platformerArray[4], + 'extreme' => (int)$platformerArray[5], + 'map' => (int)$platformerArray[6] + ]; +} +$stats = [ + 'stars' => (int)$profile['stars'], + 'moons' => (int)$profile['moons'], + 'demons' => (int)$profile['demons'], + 'diamonds' => (int)$profile['diamonds'], + 'goldCoins' => (int)$profile['coins'], + 'userCoins' => (int)$profile['userCoins'], + 'creatorPoints' => (int)$profile['creatorPoints'], + 'demonsCount' => $demonsCount, + 'starsCount' => $starsCount, + 'platformerCount' => $platformerCount +]; +$icons = [ + 'currentIcon' => [ + 'iconType' => (int)$profile['iconType'], + 'iconID' => (int)$profile['icon'] + ], + 'colors' => [ + 'mainColor' => (int)$profile['color1'], + 'secondaryColor' => (int)$profile['color2'], + 'glowColor' => (int)$profile['color3'] + ], + 'special' => (int)$profile['special'], + 'cube' => (int)$profile['accIcon'], + 'ship' => (int)$profile['accShip'], + 'ball' => (int)$profile['accBall'], + 'wave' => (int)$profile['accDart'], + 'robot' => (int)$profile['accRobot'], + 'spider' => (int)$profile['accSpider'], + 'glow' => $profile['accGlow'] == 1, + 'swing' => (int)$profile['accSwing'], + 'jetpack' => (int)$profile['accJetpack'], + 'explosion' => (int)$profile['accExplosion'] +]; +$allBans = $gs->getAllBansFromPerson($accountID, 0); +foreach($allBans AS &$ban) { + $bans[] = [ + 'banID' => $ban['banID'], + 'moderator' => [ + 'accountID' => (int)$ban['modID'], + 'userID' => (int)$gs->getUserID($ban['modID']), + 'userName' => $gs->getAccountName($ban['modID']) + ], + 'reason' => ExploitPatch::rucharclean(base64_decode($ban['banType'])), + 'banType' => $ban['banType'], + 'personType' => $ban['personType'], + 'expires' => $ban['expires'], + 'timestamp' => $ban['timestamp'], + ]; +} +$postComments = $db->prepare('SELECT * FROM acccomments WHERE userID = :userID ORDER BY commentID DESC'); +$postComments->execute([':userID' => $profile['userID']]); +$postComments = $postComments->fetchAll(); +foreach($postComments AS &$postComment) { + $replies = []; + $repliesCheck = $db->prepare('SELECT * FROM replies WHERE commentID = :commentID ORDER BY replyID DESC'); + $repliesCheck->execute([':commentID' => $postComment['commentID']]); + $repliesCheck = $repliesCheck->fetchAll(); + foreach($repliesCheck AS &$reply) { + $replies[] = [ + 'replyID' => (int)$reply['replyID'], + 'account' => [ + 'accountID' => (int)$reply['accountID'], + 'userID' => (int)$gs->getUserID($reply['accountID']), + 'userName' => $gs->getAccountName($reply['accountID']) + ], + 'text' => base64_decode($reply['body']), + 'timestamp' => (int)$reply['timestamp'] + ]; + } + $posts[] = [ + 'commentID' => (int)$postComment['commentID'], + 'post' => ExploitPatch::url_base64_decode($postComment['comment']), + 'likes' => (int)$postComment['likes'], + 'dislikes' => (int)$postComment['dislikes'], + 'isSpam' => (int)$postComment['isSpam'], + 'timestamp' => (int)$postComment['timestamp'], + 'replies' => $replies + ]; +} +exit(json_encode([ + 'dashboard' => true, + 'success' => true, + 'profile' => [ + 'accountID' => (int)$profile['extID'], + 'userID' => (int)$profile['userID'], + 'userName' => $profile['userName'], + 'clan' => $clan, + 'dlPoints' => (int)$profile['dlPoints'], + 'stats' => $stats, + 'icons' => $icons, + 'lastPlayed' => (int)$profile['lastPlayed'], + 'bans' => $bans, + 'posts' => $posts + ] +], JSON_INVALID_UTF8_IGNORE)); +?> \ No newline at end of file diff --git a/dashboard/api/rateDemon.php b/dashboard/api/rateDemon.php new file mode 100644 index 000000000..796680934 --- /dev/null +++ b/dashboard/api/rateDemon.php @@ -0,0 +1,81 @@ + true, 'success' => false, 'error' => 1, 'message' => 'Please supply a valid account credentials.'])); +} + +http_response_code(400); // Set the bad request response code now to not repeat it after + +$levelID = isset($_POST['level']) ? ExploitPatch::number(urldecode($_POST['level'])) : exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => 'Please specify a valid level ID.'])); +$demon = isset($_POST['demon']) ? ExploitPatch::number(urldecode($_POST['demon'])) : exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 3, 'message' => 'Please specify a demon difficulty.'])); + +// Check for valid demon difficulty +if (!empty($demon) && $demon < 0 || $demon > 5) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 4, 'message' => 'Please specify a valid demon difficulty.'])); + +// Check for rate demon permission +if ($gs->getMaxValuePermission($accountID, "actionRateDemon") == false) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 5, 'message' => 'You do not have the necessary permission to change the demon difficulty of a level!'])); +} + +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); +$query->execute([':levelID' => $levelID]); +$query = $query->fetch(); + +// Check if the level exists +if (!$query) { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 6, 'message' => 'This level does not exist!'])); +} + +if (!$query['starDemon']) { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 7, 'message' => 'This level is not a Demon level!'])); +} + +// Taken from incl/levels/rateGJDemon.php +switch($demon){ + case 1: + $dmn = 3; + $dmnname = "Easy"; + break; + case 2: + $dmn = 4; + $dmnname = "Medium"; + break; + case 3: + $dmn = 0; + $dmnname = "Hard"; + break; + case 4: + $dmn = 5; + $dmnname = "Insane"; + break; + case 5: + $dmn = 6; + $dmnname = "Extreme"; + break; +} + +// Change demon difficulty +$query = $db->prepare("UPDATE levels SET starDemonDiff = :demon WHERE levelID = :levelID"); +$query->execute([':demon' => $dmn, ':levelID' => $levelID]); +// Insert into mod actions +$query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('10', :value, :levelID, :timestamp, :id)"); +$query->execute([':value' => $dmnname, ':timestamp' => time(), ':id' => $accountID, ':levelID' => $levelID]); + +http_response_code(200); // Set back the response code to 200 before exitting +exit(json_encode(['dashboard' => true, 'success' => true, 'level' => $levelID])); +?> \ No newline at end of file diff --git a/dashboard/api/rateLevel.php b/dashboard/api/rateLevel.php new file mode 100644 index 000000000..661fdcaef --- /dev/null +++ b/dashboard/api/rateLevel.php @@ -0,0 +1,196 @@ + true, 'success' => false, 'error' => 1, 'message' => 'Please supply a valid account credentials.'])); +} + +http_response_code(400); + +$levelID = isset($_POST['level']) ? ExploitPatch::number(urldecode($_POST['level'])) : exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => 'Please supply a valid level ID.']));; +$stars = ExploitPatch::number(urldecode($_POST['stars'])); +$feature = ExploitPatch::number(urldecode($_POST['feature'])); +$featuredID = ExploitPatch::number(urldecode($_POST['featuredID'])); +$coins = ExploitPatch::number(urldecode($_POST['coins'])); + +// Set Star Auto to 1 if stars is equal to 1 +$starAuto = !empty($stars) && $stars == 1 ? 1 : 0; + +// Check for rate permissions +if($gs->getMaxValuePermission($accountID, "commandRate") == false || $gs->getMaxValuePermission($accountID, "actionRateStars") == false) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 3, 'message' => 'You do not have the necessary permissions to use this!'])); +} + +// Check for feature permission +if(!empty($feature) && $feature == 1) { + if ($gs->getMaxValuePermission($accountID, "commandFeature") == false) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 4, 'message' => 'You do not have the necessary permission to feature a level!'])); + } +} + +// Check for epic permission +if(!empty($feature) && $feature > 1) { + if ($gs->getMaxValuePermission($accountID, "commandEpic") == false) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 5, 'message' => 'You do not have the necessary permission to make a level epic!'])); + } +} + +// Check for permission to use feature ID parameter +if(!empty($featuredID) && $featuredID >= 1) { + if ($gs->getMaxValuePermission($accountID, "commandFeature") == false) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 6, 'message' => 'You do not have the necessary permission to set a custom feature ID!'])); + } +} + +// Check for permission to use coins parameter +if (!empty($coins) && !$gs->getMaxValuePermission($accountID, "commandVerifycoins")) { + http_response_code(403); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 7, 'message' => 'You do not have the necessary permission to verify the coins of a level!'])); +} + +// Check for valid amount of stars +if (!empty($stars) && $stars < 0 || $stars > 10) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 8, 'message' => 'Please specify a valid amount of stars.'])); + +// Check for valid feature state +if (!empty($feature) && $feature < 0 || $feature > 5) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 9, 'message' => 'Please specify a valid feature state.'])); + +// Check for valid coins (false/true) +if (isset($_POST['coins']) && $coins < 0 || $coins > 1) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 10, 'message' => 'Please specify a valid true/false boolean for the coins parameter.'])); + +if (isset($_POST['featuredID']) && $featuredID <= 0) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 11, 'message' => 'You cannot set a featured ID below 0.'])); + +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); +$query->execute([':levelID' => $levelID]); +$query = $query->fetch(); + +// Check if the level exists +if (!$query) { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 12, 'message' => 'This level does not exist!'])); +} + +// Set star auto from database if it cant fetch stars from parameters +if (empty($stars)) $starAuto = $query["starAuto"]; + +// Check for coins only update +if (!isset($_POST['stars']) && !isset($_POST['feature']) && !isset($_POST['featuredID']) && isset($_POST['coins'])) { + if ($query["starStars"] > 0) { + $gs->verifyCoinsLevel($accountID, $levelID, $coins); + + http_response_code(200); + exit(json_encode(['dashboard' => true, 'success' => true, 'level' => $levelID])); + } else { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 13, 'message' => 'This level is not star rated!'])); + } +} + +// Check for feature only update +if (!isset($_POST['stars']) && isset($_POST['feature']) && !isset($_POST['coins'])) { + if ($query["starStars"] != 0) { + if (isset($_POST['featuredID'])) { + switch($feature) { + case 0: + $featureState = $featuredID; + $epic = 0; + break; + case 1: + case 2: + case 3: + case 4: + $featureState = $featuredID; + $epic = $feature - 1; + break; + } + $query = $db->prepare("UPDATE levels SET starFeatured=:feature, starEpic=:epic, rateDate = :timestamp WHERE levelID=:levelID"); + $query->execute([':feature' => $feature, ':epic' => $epic, ':timestamp' => time(), ':levelID' => $levelID]); + + // Insert action into mod actions + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $featureState, ':timestamp' => time(), ':id' => $accountID, ':levelID' => $levelID]); + } else $gs->featureLevel($accountID, $levelID, $feature); + + http_response_code(200); + exit(json_encode(['dashboard' => true, 'success' => true, 'level' => $levelID])); + } else { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 14, 'message' => 'This level is not star rated!'])); + } +} + +// Check for featured ID only update +if (!isset($_POST['stars']) && !isset($_POST['feature']) && !isset($_POST['coins']) && isset($_POST['featuredID'])) { + if ($query["starStars"] > 0 && $query["starFeatured"] > 0) { + $query = $db->prepare("UPDATE levels SET starFeatured = :starFeatured, rateDate = :timestamp WHERE levelID = :levelID"); + $query->execute([':starFeatured' => $featuredID, ':timestamp' => time(), ':levelID' => $levelID]); + + http_response_code(200); + exit(json_encode(['dashboard' => true, 'success' => true, 'level' => $levelID])); + } else { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 15, 'message' => 'This level is not rated/featured!'])); + } +} + +// Check if stars has been set before doing any requests here +if (!isset($_POST['stars'])) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 16, 'message' => 'Please supply an amount of stars.'])); + +$difficulty = $gs->getDiffFromStars($stars); + +// Solve $feature problems +if (!isset($_POST['feature'])) { + if (isset($_POST['stars']) && $stars == 0) $feature = 0; + elseif ($query['starFeatured'] >= 1 && $query['starEpic'] >= 1) $feature = $query['starEpic']; + elseif ($query['starFeatured'] >= 1 && $query['starEpic'] == 0) $feature = 1; +} + +// Set coins from database if it cant fetch coins from parameters +if (!isset($_POST['coins'])) $coins = $query["starCoins"]; +// Set coins to 0 if the level is set to be unrated +if (isset($_POST['stars']) && $stars == 0 && $query['starStars'] >= 1 && $coins == 0) $coins = 0; + +$gs->rateLevel($accountID, $levelID, $stars, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); +// Feature ID check +if (!empty($featuredID) && !empty($feature) && $featuredID >= 1) { + switch($feature) { + case 0: + $featureState = $featuredID; + $epic = 0; + break; + case 1: + case 2: + case 3: + case 4: + $featureState = $featuredID; + $epic = $feature - 1; + break; + } + $query = $db->prepare("UPDATE levels SET starFeatured=:feature, starEpic=:epic, rateDate = :timestamp WHERE levelID=:levelID"); + $query->execute([':feature' => $feature, ':epic' => $epic, ':timestamp' => time(), ':levelID' => $levelID]); + + // Insert action into mod actions + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $featureState, ':timestamp' => time(), ':id' => $accountID, ':levelID' => $levelID]); +} else $gs->featureLevel($accountID, $levelID, $feature); + +if ($stars <= 0) $gs->verifyCoinsLevel($accountID, $levelID, 0); +else $gs->verifyCoinsLevel($accountID, $levelID, isset($_POST['coins']) ? $coins : $query["starCoins"]); + +http_response_code(200); +exit(json_encode(['dashboard' => true, 'success' => true, 'level' => $levelID])); +?> \ No newline at end of file diff --git a/dashboard/api/rates.php b/dashboard/api/rates.php new file mode 100644 index 000000000..b89688c8f --- /dev/null +++ b/dashboard/api/rates.php @@ -0,0 +1,96 @@ + true, 'success' => false, 'error' => 1, 'message' => 'Please specify your last rate check time.'])); +$rates = $db->prepare("SELECT * FROM levels WHERE rateDate != 0 AND rateDate > :time ORDER BY rateDate DESC"); +$rates->execute([':time' => $time]); +$rates = $rates->fetchAll(); +if(empty($rates)) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => 'Nothing was rated!'])); +foreach($rates as &$rate) { + $songInfo = $song = $songName = false; + $rate['extID'] = is_numeric($rate['extID']) ? $rate['extID'] : 0; + if($rate['songID'] != 0) { + $songInfo = $gs->getSongInfo($rate['songID']); + if($songInfo) { + $song = [ + 'ID' => $songInfo['ID'], + 'author' => $songInfo['authorName'], + 'name' => $songInfo['name'], + 'size' => $songInfo['size'], + 'download' => urldecode($songInfo['download']), + 'reuploader' => false, + 'newgrounds' => true, + 'customSong' => true + ]; + if($songInfo['reuploadID'] != 0) { + $accountName = $gs->getAccountName($songInfo['reuploadID']); + $song['reuploader'] = [ + 'accountID' => $songInfo['reuploadID'], + 'userID' => $gs->getUserID($songInfo['reuploadID'], $accountName), + 'username' => $accountName + ]; + $song['newgrounds'] = false; + } + } else { + $songName = explode(' by ', $gs->getAudioTrack($rate['audioTrack'])); + $song = [ + 'audioTrack' => $rate['audioTrack'], + 'name' => $songName[0], + 'author' => $songName[1], + 'customSong' => false + ]; + } + } else { + $songName = explode(' by ', $gs->getAudioTrack($rate['audioTrack'])); + $song = [ + 'audioTrack' => $rate['audioTrack'], + 'name' => $songName[0], + 'author' => $songName[1], + 'customSong' => false + ]; + } + $levels[] = [ + 'ID' => $rate['levelID'], + 'name' => $rate['levelName'], + 'desc' => ExploitPatch::rucharclean(ExploitPatch::url_base64_decode($rate['levelDesc'])), + 'stats' => [ + 'stars' => $rate['starStars'], + 'featured' => ($rate['starFeatured'] == 0 ? false : true), + 'isRated' => ($rate['starStars'] == 0 ? false : true), + 'isCoinsRated' => ($rate['starCoins'] == 0 ? false : true), + 'coins' => $rate['coins'], + 'likes' => $rate['likes'] - ($rate['dislikes'] ?? 0), + 'downloads' => $rate['downloads'], + 'requestedStars' => $rate['requestedStars'], + 'epic' => $rate['starEpic'] + ], + 'diffuculty' => [ + 'number' => $rate['starDifficulty'], + 'demonDiff' => $rate['starDemonDiff'], + 'name' => $gs->getDifficulty($rate['starDifficulty'], $rate['starAuto'], $rate['starDemon']), + 'isDemon' => ($rate['starDemon'] == 0 ? false : true), + 'isAuto' => ($rate['starAuto'] == 0 ? false : true) + ], + 'author' => [ + 'username' => $rate['userName'], + 'accountID' => $rate['extID'], + 'userID' => $rate['userID'] + ], + 'timestamps' => [ + 'uploadDate' => $rate['uploadDate'], + 'updateDate' => $rate['updateDate'], + 'rateDate' => $rate['rateDate'] + ], + 'song' => $song + ]; +} +exit(json_encode(['dashboard' => true, 'success' => true, 'levels' => $levels])); +?> \ No newline at end of file diff --git a/dashboard/api/replies.php b/dashboard/api/replies.php new file mode 100644 index 000000000..00afefb2f --- /dev/null +++ b/dashboard/api/replies.php @@ -0,0 +1,86 @@ + false, 'error' => $check])); + if($_POST["delete"] == 1) { + $reply = $db->prepare("SELECT * FROM replies WHERE replyID = :id"); + $reply->execute([':id' => $id]); + $reply = $reply->fetch(); + if($reply["accountID"] == $check['accountID']) { + $reply = $db->prepare("DELETE FROM replies WHERE replyID = :id"); + $reply->execute([':id' => $id]); + exit(json_encode(['dashboard' => true, 'success' => true])); + } else exit(json_encode(['dashboard' => true, 'success' => false])); + } + if(empty($_POST["body"])) { + $reply = $db->prepare("SELECT * FROM replies WHERE commentID = :id ORDER BY replyID DESC"); + $reply->execute([':id' => $id]); + $reply = $reply->fetchAll(); + $replies = []; + foreach($reply as &$rep) { + $body = base64_decode($rep["body"]); + if($enableCommentLengthLimiter) $body = substr($body, 0, $maxCommentLength); + $username = $gs->getAccountName($rep["accountID"]); + $replies[] = [ + 'replyID' => $rep['replyID'], + 'commentID' => $id, + 'account' => [ + 'username' => $username, + 'accountID' => $rep["accountID"], + 'userID' => $gs->getUserID($rep["accountID"], $username) + ], + 'body' => $body, + 'timestamp' => $rep["timestamp"] + ]; + } + exit(json_encode(['dashboard' -> true, 'success' => true, 'replies' => $replies, 'count' => count($replies)])); + } else { + $userID = $gs->getUserID($check["accountID"], $gs->getAccountName($check["accountID"])); + $checkBan = $gs->getPersonBan($check["accountID"], $userID, 3); + if(Automod::isAccountsDisabled(1) || $checkBan) exit(json_encode(['dashboard' => true, 'success' => false])); + $body = base64_encode(strip_tags(ExploitPatch::rucharclean($_POST["body"]))); + if($enableCommentLengthLimiter && strlen(base64_decode($body)) > $maxCommentLength) exit("-1"); + $reply = $db->prepare("INSERT INTO replies (commentID, accountID, body, timestamp) VALUES (:cid, :acc, :body, :time)"); + if($reply->execute([':cid' => $id, ':acc' => $check['accountID'], ':body' => $body, ':time' => time()])) exit(json_encode(['dashboard' => true, 'success' => true])); + else exit(json_encode(['dashboard' => true, 'success' => false])); + } +} else { + $reply = $db->prepare("SELECT * FROM replies WHERE commentID = :id ORDER BY replyID DESC"); + $reply->execute([':id' => $id]); + $reply = $reply->fetchAll(); + $replies = []; + foreach($reply as &$rep) { + $body = base64_decode($rep["body"]); + if($enableCommentLengthLimiter) $body = substr(base64_decode($body), 0, $maxCommentLength); + $username = $gs->getAccountName($rep["accountID"]); + $replies[] = [ + 'replyID' => $rep['replyID'], + 'commentID' => $id, + 'account' => [ + 'username' => $username, + 'accountID' => $rep["accountID"], + 'userID' => $gs->getUserID($rep["accountID"], $username) + ], + 'body' => $body, + 'timestamp' => $rep["timestamp"] + ]; + } + exit(json_encode(['dashboard' => true, 'success' => true, 'replies' => $replies, 'count' => count($replies)])); +} +?> \ No newline at end of file diff --git a/dashboard/api/runCron.php b/dashboard/api/runCron.php new file mode 100644 index 000000000..17a5eea8b --- /dev/null +++ b/dashboard/api/runCron.php @@ -0,0 +1,25 @@ + true, 'success' => false, 'error' => 1, 'message' => 'Please supply a valid account credentials.'])); +} + +$runCron = Cron::doEverything($accountID, true); +if(!$runCron) { + http_response_code(400); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => 'Please wait a few minutes before running Cron again.'])); +} + +exit(json_encode(['dashboard' => true, 'success' => true])); +?> \ No newline at end of file diff --git a/dashboard/api/searchLevel.php b/dashboard/api/searchLevel.php new file mode 100644 index 000000000..abc60af9a --- /dev/null +++ b/dashboard/api/searchLevel.php @@ -0,0 +1,94 @@ + true, 'success' => false, 'error' => 1, 'message' => "Please supply a valid level ID."])); +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :lvid"); +$query->execute([':lvid' => $levelID]); +$rate = $query->fetch(); +if(!$rate) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => "This level wasn't found."])); +$songInfo = $song = $songName = false; +$rate['extID'] = is_numeric($rate['extID']) ? $rate['extID'] : 0; +if($rate['songID'] != 0) { + $songInfo = $gs->getSongInfo($rate['songID']); + if($songInfo) { + $song = [ + 'ID' => $songInfo['ID'], + 'author' => $songInfo['authorName'], + 'name' => $songInfo['name'], + 'size' => $songInfo['size'], + 'download' => urldecode($songInfo['download']), + 'reuploader' => false, + 'newgrounds' => true, + 'customSong' => true + ]; + if($songInfo['reuploadID'] != 0) { + $accountName = $gs->getAccountName($songInfo['reuploadID']); + $song['reuploader'] = [ + 'accountID' => $songInfo['reuploadID'], + 'userID' => $gs->getUserID($songInfo['reuploadID'], $accountName), + 'username' => $accountName + ]; + $song['newgrounds'] = false; + } + } else { + $songName = explode(' by ', $gs->getAudioTrack($rate['audioTrack'])); + $song = [ + 'audioTrack' => $rate['audioTrack'], + 'name' => $songName[0], + 'author' => $songName[1], + 'customSong' => false + ]; + } +} else { + $songName = explode(' by ', $gs->getAudioTrack($rate['audioTrack'])); + $song = [ + 'audioTrack' => $rate['audioTrack'], + 'name' => $songName[0], + 'author' => $songName[1], + 'customSong' => false + ]; +} +$level = [ + 'ID' => $rate['levelID'], + 'name' => $rate['levelName'], + 'desc' => ExploitPatch::rucharclean(ExploitPatch::url_base64_decode($rate['levelDesc'])), + 'stats' => [ + 'stars' => $rate['starStars'], + 'featured' => ($rate['starFeatured'] == 0 ? false : true), + 'isRated' => ($rate['starStars'] == 0 ? false : true), + 'isCoinsRated' => ($rate['starCoins'] == 0 ? false : true), + 'coins' => $rate['coins'], + 'likes' => $rate['likes'] - ($rate['dislikes'] ?? 0), + 'downloads' => $rate['downloads'], + 'requestedStars' => $rate['requestedStars'], + 'epic' => $rate['starEpic'] + ], + 'diffuculty' => [ + 'number' => $rate['starDifficulty'], + 'demonDiff' => $rate['starDemonDiff'], + 'name' => $gs->getDifficulty($rate['starDifficulty'], $rate['starAuto'], $rate['starDemon']), + 'isDemon' => ($rate['starDemon'] == 0 ? false : true), + 'isAuto' => ($rate['starAuto'] == 0 ? false : true) + ], + 'author' => [ + 'username' => $rate['userName'], + 'accountID' => $rate['extID'], + 'userID' => $rate['userID'] + ], + 'timestamps' => [ + 'uploadDate' => $rate['uploadDate'], + 'updateDate' => $rate['updateDate'], + 'rateDate' => $rate['rateDate'] + ], + 'song' => $song +]; +exit(json_encode(['dashboard' => true, 'success' => true, 'level' => $level])); +?> \ No newline at end of file diff --git a/dashboard/api/sends.php b/dashboard/api/sends.php new file mode 100644 index 000000000..0eb7a67c3 --- /dev/null +++ b/dashboard/api/sends.php @@ -0,0 +1,50 @@ + true, 'success' => false, 'error' => 1, 'message' => "Please supply a valid level ID."])); +} + +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); +$query->execute([":levelID" => $levelID]); +$query = $query->fetch(); + +if(!$query) { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => "This level wasn't found."])); +} + +$query = $db->prepare("SELECT * FROM suggest WHERE suggestLevelId = :levelID ORDER BY timestamp DESC"); +$query->execute([":levelID" => $levelID]); +$sendsInfo = $query->fetchAll(); + +if(!$sendsInfo) { + http_response_code(404); + exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 3, 'message' => "This level wasn't sent."])); +} + +$sends = []; + +foreach ($sendsInfo as $send) { + $sends[] = [ + "modUsername" => $gs->getAccountName($send["suggestBy"]), + "modID" => $send["suggestBy"], + "stars" => $send["suggestStars"], + "featured" => $send["suggestFeatured"], + "timestamp" => $send["timestamp"] + ]; +} + +exit(json_encode(['dashboard' => true, 'success' => true, 'sends' => $sends])); +?> \ No newline at end of file diff --git a/dashboard/api/songs.php b/dashboard/api/songs.php new file mode 100644 index 000000000..094314343 --- /dev/null +++ b/dashboard/api/songs.php @@ -0,0 +1,72 @@ +prepare("SELECT accountID FROM accounts WHERE discordID = :id AND discordLinkReq = 0"); + $discord->execute([':id' => $ds]); + $acc = $discord->fetch(); + if($acc["accountID"]) { + if($search == 'MySongs') $songs = $db->prepare("SELECT * FROM songs WHERE reuploadID = :id AND isDisabled = 0 ORDER BY reuploadTime DESC"); + else $songs = $db->prepare("SELECT * FROM favsongs INNER JOIN songs on favsongs.songID = songs.ID WHERE favsongs.accountID = :id AND songs.isDisabled = 0 ORDER BY favsongs.ID DESC"); + $songs->execute([':id' => $acc["accountID"]]); + $songs = $songs->fetchAll(); + if($songs) { + foreach($songs as &$song) { + $song["download"] = str_replace("http://", 'https://', str_replace("gcsdb/dashboard/", '', $song["download"])); + $data[] = ["ID" => $song["ID"], "author" => $song["authorName"], "name" => $song["name"], "download" => $song["download"], "reuploadID" => $song["reuploadID"], "reuploadTime" => $song["reuploadTime"]]; + } + $count = count($data); + if($count > 0) { + if($count == 1) exit(json_encode(["dashboard" => true, "success" => true, "numeric" => true, $data[0]])); + else exit(json_encode(["dashboard" => true, "success" => true, "songs" => $data, "count" => $count, "numeric" => false])); + } + } else exit(json_encode(["dashboard" => true, "success" => false, "error" => 4])); + } else exit(json_encode(["dashboard" => true, "success" => false, "error" => 3])); + } else { + $explode = explode(" - ", str_replace(" — ", " - ", $search), 2); + if(!$explode[1]) { + $author = $name = $search; + $separator = 'OR'; + } + else { + $author = $explode[0]; + $name = $explode[1]; + $separator = 'AND'; + } + $songs = $db->prepare("SELECT * FROM songs WHERE (authorName LIKE '%".$author."%' ".$separator." name LIKE '%".$name."%') AND reuploadID > 0 AND isDisabled = 0"); + $songs->execute(); + $songs = $songs->fetchAll(); + foreach($songs as &$song) { + $song["download"] = str_replace("http://", 'https://', str_replace("gcsdb/dashboard/", '', $song["download"])); + $data[] = ["ID" => $song["ID"], "author" => $song["authorName"], "name" => $song["name"], "download" => $song["download"], "reuploadID" => $song["reuploadID"], "reuploadTime" => $song["reuploadTime"]]; + } + $count = count($data); + if($count > 0) { + if($count == 1) exit(json_encode(["dashboard" => true, "success" => true, "numeric" => true, "song" => $data[0], 'songs' => $data[0]])); + else exit(json_encode(["dashboard" => true, "success" => true, "songs" => $data, "count" => $count, "numeric" => false])); + } else exit(json_encode(["dashboard" => true, "success" => false, "error" => 2])); + } + } else { + $songs = $db->prepare("SELECT * FROM songs WHERE ID = :id AND reuploadID > 0 AND isDisabled = 0"); + $songs->execute([':id' => $search]); + $song = $songs->fetch(); + if($song) { + $song["download"] = str_replace("http://", 'https://', str_replace("gcsdb/dashboard/", '', $song["download"])); + $oneSong = ["ID" => $song["ID"], "author" => $song["authorName"], "name" => $song["name"], "download" => $song["download"], "reuploadID" => $song["reuploadID"], "reuploadTime" => $song["reuploadTime"]]; + exit(json_encode(["dashboard" => true, "success" => true, "numeric" => true, "song" => $oneSong, 'songs' => $oneSong])); + } + else exit(json_encode(["dashboard" => true, "success" => false, "error" => 1])); + } +} +echo json_encode(["dashboard" => true, "success" => false, 'error' => 0]); +?> \ No newline at end of file diff --git a/dashboard/api/stats.php b/dashboard/api/stats.php new file mode 100644 index 000000000..98a5c3b42 --- /dev/null +++ b/dashboard/api/stats.php @@ -0,0 +1,105 @@ +prepare("SELECT + (SELECT COUNT(*) FROM users) AS users, + (SELECT COUNT(*) FROM users WHERE lastPlayed > :time - 2592000) AS activeUsers, + (SELECT COUNT(*) FROM levels) AS levels, + (SELECT COUNT(*) FROM levels WHERE starStars >= 1) AS ratedLevels, + (SELECT COUNT(*) FROM levels WHERE starStars >= 1 AND starFeatured >= 1 AND starEpic = 0) AS featuredLevels, + (SELECT COUNT(*) FROM levels WHERE starStars >= 1 AND starFeatured >= 1 AND starEpic = 1) AS epicLevels, + (SELECT COUNT(*) FROM levels WHERE starStars >= 1 AND starFeatured >= 1 AND starEpic = 2) AS legendaryLevels, + (SELECT COUNT(*) FROM levels WHERE starStars >= 1 AND starFeatured >= 1 AND starEpic = 3) AS mythicLevels, + (SELECT COUNT(*) FROM dailyfeatures WHERE type = 0) AS dailies, + (SELECT COUNT(*) FROM dailyfeatures WHERE type = 1) AS weeklies, + (SELECT COUNT(*) FROM gauntlets) AS gauntlets, + (SELECT COUNT(*) FROM mappacks) AS mapPacks, + (SELECT SUM(downloads) FROM levels) AS downloads, + (SELECT SUM(objects) FROM levels) AS objects, + (SELECT SUM(likes) FROM levels) AS likes, + (SELECT (SELECT COUNT(*) FROM comments) + (SELECT COUNT(*) FROM acccomments)) AS totalComments, + (SELECT COUNT(*) FROM comments) AS comments, + (SELECT COUNT(*) FROM acccomments) AS posts, + (SELECT COUNT(*) FROM replies) AS postReplies, + (SELECT SUM(stars) FROM users) AS stars, + (SELECT SUM(creatorPoints) FROM users) AS creatorPoints, + (SELECT COUNT(*) FROM bans) AS bannedPlayers, + (SELECT COUNT(personType) FROM bans WHERE personType = 0) AS accountIDBans, + (SELECT COUNT(personType) FROM bans WHERE personType = 1) AS userIDBans, + (SELECT COUNT(personType) FROM bans WHERE personType = 2) AS IPBans, + (SELECT COUNT(banType) FROM bans WHERE banType = 0) AS leaderboardBans, + (SELECT COUNT(banType) FROM bans WHERE banType = 1) AS creatorBans, + (SELECT COUNT(banType) FROM bans WHERE banType = 2) AS levelUploadBans, + (SELECT COUNT(banType) FROM bans WHERE banType = 3) AS commentBans, + (SELECT COUNT(banType) FROM bans WHERE banType = 4) AS accountBans +"); +$query->execute([':time' => time()]); +$stats = $query->fetch(PDO::FETCH_ASSOC); +$stats = [ + 'users' => [ + 'total' => (int)$stats['users'], + 'active' => (int)$stats['activeUsers'] + ], + 'levels' => [ + 'total' => (int)$stats['levels'], + 'rated' => (int)$stats['ratedLevels'], + 'featured' => (int)$stats['featuredLevels'], + 'epic' => (int)$stats['epicLevels'], + 'legendary' => (int)$stats['legendaryLevels'], + 'mythic' => (int)$stats['mythicLevels'] + ], + 'special' => [ + 'dailies' => (int)$stats['dailies'], + 'weeklies' => (int)$stats['weeklies'], + 'gauntlets' => (int)$stats['gauntlets'], + 'map_packs' => (int)$stats['mapPacks'] + ], + 'downloads' => [ + 'total' => (int)$stats['downloads'], + 'average' => (double)($stats['downloads'] / $stats['levels']) + ], + 'objects' => [ + 'total' => (int)$stats['objects'], + 'average' => (double)($stats['objects'] / $stats['levels']) + ], + 'likes' => [ + 'total' => (int)$stats['likes'], + 'average' => (double)($stats['likes'] / $stats['levels']) + ], + 'comments' => [ + 'total' => (int)$stats['totalComments'], + 'comments' => (int)$stats['comments'], + 'posts' => (int)$stats['posts'], + 'post_replies' => (int)$stats['postReplies'] + ], + 'gained_stars' => [ + 'total' => (int)$stats['stars'], + 'average' => (double)($stats['stars'] / $stats['users']) + ], + 'creator_points' => [ + 'total' => (float)$stats['creatorPoints'], + 'average' => (double)($stats['creatorPoints'] / $stats['users']) + ], + 'bans' => [ + 'total' => (int)$stats['bannedPlayers'], + 'personTypes' => [ + 'accountIDBans' => (int)$stats['accountIDBans'], + 'userIDBans' => (int)$stats['userIDBans'], + 'IPBans' => (int)$stats['IPBans'] + ], + 'banTypes' => [ + 'leaderboardBans' => (int)$stats['leaderboardBans'], + 'creatorBans' => (int)$stats['creatorBans'], + 'levelUploadBans' => (int)$stats['levelUploadBans'], + 'commentBans' => (int)$stats['commentBans'], + 'accountBans' => (int)$stats['accountBans'] + ] + ] +]; +exit(json_encode(['dashboard' => true, 'success' => true, 'stats' => $stats])); +?> \ No newline at end of file diff --git a/dashboard/api/whoRated.php b/dashboard/api/whoRated.php new file mode 100644 index 000000000..dfb470be2 --- /dev/null +++ b/dashboard/api/whoRated.php @@ -0,0 +1,32 @@ + true, 'success' => false, 'error' => 1, 'message' => "Please supply a valid level ID."])); +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :lvid"); +$query->execute([':lvid' => $levelID]); +$levelInfo = $query->fetch(); +if(!$levelInfo) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 2, 'message' => "This level wasn't found."])); +$query = $db->prepare("SELECT * FROM modactions WHERE value3 = :lvid AND type = '1'"); +$query->execute([':lvid' => $levelID]); +$result = $query->fetchAll(); +if($query->rowCount() == 0) exit(json_encode(['dashboard' => true, 'success' => false, 'error' => 3, 'message' => "This level wasn't rated."])); +foreach($result as &$action){ + $userName = $gs->getAccountName($action['account']); + $data[] = [ + 'username' => $userName, + 'accountID' => $action['account'], + 'difficulty' => $action['value'], + 'stars' => $action['value2'], + 'timestamp' => $action['timestamp'] + ]; +} +exit(json_encode(['dashboard' => true, 'success' => true, 'level' => ['name' => $levelInfo['levelName'], 'author' => $levelInfo['userName']], 'rates' => $data])); +?> \ No newline at end of file diff --git a/dashboard/automod/index.php b/dashboard/automod/index.php new file mode 100644 index 000000000..919b52adc --- /dev/null +++ b/dashboard/automod/index.php @@ -0,0 +1,425 @@ +title($dl->getLocalizedString("automodTitle")); +$dl->printFooter('../'); +if(!$gs->checkPermission($_SESSION["accountID"], "dashboardManageAutomod")) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod')); +} +if($_POST['resolve'] == 1) { + $actionID = ExploitPatch::number($_POST['actionID']); + $action = Automod::getAutomodActionByID($actionID); + if($action && !$action['resolved']) Automod::changeAutomodAction($actionID, 1); +} +if(isset($_POST['disableType'])) { + $disableType = ExploitPatch::number($_POST['disableType']); + $expires = (new DateTime($_POST['expires']))->getTimestamp(); + switch(true) { + case isset($_POST['levels']): + $isDisable = $_POST['levels'] == 1; + if($expires > time() || !$isDisable) Automod::changeLevelsAutomodState($disableType, $isDisable, $expires); + break; + case isset($_POST['accounts']): + $isDisable = $_POST['accounts'] == 1; + if($expires > time() || !$isDisable) Automod::changeAccountsAutomodState($disableType, $isDisable, $expires); + break; + } +} +$actions = Automod::getAutomodActions(); +$actionTypes = Automod::getAutomodTypes(); +$actionsDiv = ''; +foreach($actions AS &$actionValue) { + $actionType = $actionTypes[$actionValue['type']]; + $actionButtons = $actionDesc = $spammerUsername = ''; + switch($actionType) { + case 1: + $actionTitle = $dl->getLocalizedString('possibleLevelsSpamming'); + $actionButtonsText = $dl->getLocalizedString('disableLevelsUploading'); + $openTabFunction = 'openLevelsDisableTab(0, '.$actionValue['ID'].')'; + break; + case 2: + $actionTitle = $dl->getLocalizedString('possibleAccountsSpamming'); + $actionButtonsText = $dl->getLocalizedString('disableAccountsRegistering'); + $openTabFunction = 'openAccountsDisableTab(0, '.$actionValue['ID'].')'; + break; + case 3: + $actionTitle = $dl->getLocalizedString('possibleCommentsSpamming'); + $actionButtonsText = $dl->getLocalizedString('disableCommenting'); + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' / '.$actionValue['value3'].' +
+
'; + $openTabFunction = 'openLevelsDisableTab(1, '.$actionValue['ID'].')'; + break; + case 4: + $actionTitle = $dl->getLocalizedString('possibleCommentsSpammer'); + $actionButtonsText = $dl->getLocalizedString('banCommenting'); + $spammerUsername = $gs->getUserName($actionValue['value4']); + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' / '.$actionValue['value3'].' +
+
+ '.$spammerUsername.' +
+
'; + $openTabFunction = 'a(\'account/banPerson.php?person='.urlencode($actionValue['value4']).'&personType=1&banType=3\', true, true, \'GET\')'; + break; + case 5: + $actionTitle = $dl->getLocalizedString('possibleAccountPostsSpamming'); + $actionButtonsText = $dl->getLocalizedString('disablePosting'); + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' / '.$actionValue['value3'].' +
+
'; + $openTabFunction = 'openAccountsDisableTab(1, '.$actionValue['ID'].')'; + break; + case 6: + $actionTitle = $dl->getLocalizedString('possibleAccountPostsSpammer'); + $actionButtonsText = $dl->getLocalizedString('banCommenting'); + $spammerUsername = $gs->getUserName($actionValue['value4']); + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' / '.$actionValue['value3'].' +
+
+ '.$spammerUsername.' +
+
'; + $openTabFunction = 'a(\'account/banPerson.php?person='.urlencode($actionValue['value4']).'&personType=1&banType=3\', true, true, \'GET\')'; + break; + case 7: + $actionTitle = $dl->getLocalizedString('possibleRepliesSpamming'); + $actionButtonsText = $dl->getLocalizedString('disablePosting'); + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' / '.$actionValue['value3'].' +
+
'; + $openTabFunction = 'openAccountsDisableTab(1, '.$actionValue['ID'].')'; + break; + case 8: + $actionTitle = $dl->getLocalizedString('possibleRepliesSpammer'); + $actionButtonsText = $dl->getLocalizedString('banCommenting'); + $spammerUsername = $gs->getAccountName($actionValue['value4']); + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' / '.$actionValue['value3'].' +
+
+ '.$spammerUsername.' +
+
'; + $openTabFunction = 'a(\'account/banPerson.php?person='.urlencode($actionValue['value4']).'&personType=0&banType=3\', true, true, \'GET\')'; + break; + default: + $actionTitle = $dl->getLocalizedString('unknownWarning'); + $actionButtons = ''; + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' +
+
+ '.$actionValue['value3'].' +
+
+
+
+ '.$actionValue['value4'].' +
+
+ '.$actionValue['value5'].' +
+
+ '.$actionValue['value6'].' +
+
'; + break; + } + if(empty($actionDesc)) { + if(empty($actionValue['value1'])) $actionCompare = '∞'; + else $actionCompare = ceil($actionValue['value2'] / $actionValue['value1'] * 10) / 10; + $actionDesc = '
+
+ '.$actionValue['value1'].' +
+
+ '.$actionValue['value2'].' +
+
+ x'.$actionCompare.' +
+
'; + } + if(empty($actionButtons)) { + if($actionValue['resolved']) $actionButtons = ''; + else $actionButtons = ' +
+ + + +
+ '; + } + $actionsDiv .= '
+
+

'.$actionTitle.'

+
+
+

'.$actionDesc.'

+
+
+ '.$actionButtons.' +
+
'; +} +if(empty($actionsDiv)) $actionsDiv = '
+ +

'.$dl->getLocalizedString('noWarnings').'

+
'; +$levelsCount = Automod::getLevelsCountPerDay(); +if(empty($levelsCount['yesterday'])) $levelsCountCompare = '∞'; +else $levelsCountCompare = ceil(($levelsCount['today'] / $levelsCount['yesterday']) * 1000) / 10 .'%'; +$levelsDisableTimes = Automod::getLevelsDisableStates(); +$levelsUploadingState = empty($levelsDisableTimes[0]) ? 'enabled' : 'disabled'; +$levelsCommentingState = empty($levelsDisableTimes[1]) ? 'enabled' : 'disabled'; +$levelsLeaderboardSubmitsState = empty($levelsDisableTimes[2]) ? 'enabled' : 'disabled'; +$accountsCount = Automod::getAccountsCountPerDay(); +if(empty($accountsCount['yesterday'])) $accountsCountCompare = '∞'; +else $accountsCountCompare = ceil(($accountsCount['today'] / $accountsCount['yesterday']) * 1000) / 10 .'%'; +$accountsDisableTimes = Automod::getAccountsDisableStates(); +$accountRegisteringState = empty($accountsDisableTimes[0]) ? 'enabled' : 'disabled'; +$accountPostingState = empty($accountsDisableTimes[1]) ? 'enabled' : 'disabled'; +$accountStatsUpdatingState = empty($accountsDisableTimes[2]) ? 'enabled' : 'disabled'; +$accountMessagingState = empty($accountsDisableTimes[3]) ? 'enabled' : 'disabled'; +$dl->printPage('
+

'.$dl->getLocalizedString("automodTitle").'

+
+
+ '.$actionsDiv.' +
+
+
+
+

'.$dl->getLocalizedString('levels').'

+
+
+
+
+ '.$levelsCount['yesterday'].' +
+
+ '.$levelsCount['today'].' +
+
+ '.$levelsCountCompare.' +
+
+
+
+ '.$dl->getLocalizedString('uploading').' +
+
+ '.$dl->getLocalizedString($levelsUploadingState).' +
+
+
+
+ '.$dl->getLocalizedString('commenting').' +
+
+ '.$dl->getLocalizedString($levelsCommentingState).' +
+
+
+
+ '.$dl->getLocalizedString('leaderboardSubmits').' +
+
+ '.$dl->getLocalizedString($levelsLeaderboardSubmitsState).' +
+
+
+
+ + + +
+
+ +
+
+

'.$dl->getLocalizedString('accounts').'

+
+
+
+
+ '.$accountsCount['yesterday'].' +
+
+ '.$accountsCount['today'].' +
+
+ '.$accountsCountCompare.' +
+
+
+
+ '.$dl->getLocalizedString('registering').' +
+
+ '.$dl->getLocalizedString($accountRegisteringState).' +
+
+
+
+ '.$dl->getLocalizedString('accountPosting').' +
+
+ '.$dl->getLocalizedString($accountPostingState).' +
+
+
+
+ '.$dl->getLocalizedString('updatingProfileStats').' +
+
+ '.$dl->getLocalizedString($accountStatsUpdatingState).' +
+
+
+
+ '.$dl->getLocalizedString('messaging').' +
+
+ '.$dl->getLocalizedString($accountMessagingState).' +
+
+
+
+ + + +
+
+
+
+
+', 'mod'); +?> \ No newline at end of file diff --git a/dashboard/clan/.htaccess b/dashboard/clan/.htaccess new file mode 100644 index 000000000..0621d3743 --- /dev/null +++ b/dashboard/clan/.htaccess @@ -0,0 +1,9 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} !^/replies.php +RewriteCond %{REQUEST_URI} !/settings +RewriteRule ^ %{REQUEST_URI}/../../clan/?id=%{REQUEST_URI} [L] + +RewriteCond %{REQUEST_URI} /settings +RewriteRule ^ %{REQUEST_URI}/../../../clan/?id=%{REQUEST_URI} [L] diff --git a/dashboard/clan/index.php b/dashboard/clan/index.php new file mode 100644 index 000000000..d9c68bce5 --- /dev/null +++ b/dashboard/clan/index.php @@ -0,0 +1,476 @@ +printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'profile')); +$getID = explode("/", $_GET["id"])[count(explode("/", $_GET["id"]))-1]; +if($getID == "settings") { + $getID = explode("/", $_GET["id"])[count(explode("/", $_GET["id"]))-2]; + $_POST["settings"] = 1; + $dl->printFooter('../../'); + echo ''; + if(isset($_GET['pending'])) $_POST['pending'] = 1; +} else $dl->printFooter('../'); +$clanid = str_replace("%20", " ", ExploitPatch::remove($getID)); +if(!is_numeric($clanid)) $clanid = $gs->getClanID($clanid); +$clan = $gs->getClanInfo($clanid); +$isPlayerInClan = $gs->isPlayerInClan($_SESSION["accountID"]); +if(!$clanid OR !$clan) { + $dl->title($dl->getLocalizedString("clan")); + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noClan").'

+ +
+
', 'profile')); +} +$dl->title($dl->getLocalizedString("clan").' '.$clan["clan"]); +$back = $members = $settings = $menu = $pending = $membermenu = $requests = $closed = $kick = ""; +if(isset($_SERVER["HTTP_REFERER"])) $back = '
'; else $back = ''; +if(!empty($clan)) { + if(isset($_POST["settings"]) AND $_POST["settings"] == 1 AND $clan["clanOwner"] == $_SESSION["accountID"]) { + if(!isset($_POST["ichangedsmth"])) { + if(isset($_POST["givethisclan"])) { + if(isset($_POST["newOwner"])) { + $newOwner = ExploitPatch::number($_POST["newOwner"]); + $mbrs = $db->prepare("SELECT * FROM users WHERE clan = :cid AND extID = :own"); + $mbrs->execute([':cid' => $clan["ID"], ':own' => $newOwner]); + $mbrs = $mbrs->fetch(); + if(empty($mbrs)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("notInYourClan").'

+ +
+
', 'profile')); + else { + $giveclan = $db->prepare("UPDATE clans SET clanOwner = :own WHERE ID = :id"); + $giveclan->execute([':own' => $newOwner, ':id' => $clan["ID"]]); + exit($dl->printSong('
+

'.$dl->getLocalizedString("clan").'

+
+

'.sprintf($dl->getLocalizedString("givedClan"), $gs->getAccountName($newOwner)).'

+ +
+
', 'profile')); + } + } + $mbrs = $db->prepare("SELECT * FROM users WHERE clan = :cid AND extID != :own"); + $mbrs->execute([':cid' => $clan["ID"], ':own' => $clan["clanOwner"]]); + $mbrs = $mbrs->fetchAll(); + foreach($mbrs as &$mbr) $members .= ''; + exit($dl->printSong('
+
+
+

'.$dl->getLocalizedString("areYouSure").'

+

'.$dl->getLocalizedString("giveClanDesc").'

+
+ + +
+ +
+
', 'profile')); + } elseif(isset($_POST["delclan"])) { + if(isset($_POST["yesdelete"]) AND $_POST["yesdelete"] == 1) { + $delete = $db->prepare("DELETE FROM clans WHERE ID = :id"); + $delete->execute([':id' => $clan["ID"]]); + $delete = $db->prepare("UPDATE users SET clan = 0 WHERE clan = :id"); + $delete->execute([':id' => $clan["ID"]]); + exit($dl->printSong('
+

'.$dl->getLocalizedString("clan").'

+
+

'.sprintf($dl->getLocalizedString("deletedClan"), $clan["clan"]).'

+ +
+
', 'profile')); + } + exit($dl->printSong('
+
+
+

'.$dl->getLocalizedString("areYouSure").'

+

'.$dl->getLocalizedString("deleteClanDesc").'

+
+ +
+
+ + + +
+
+
+
+
', 'profile')); + } elseif(isset($_POST["pending"])) { + if((isset($_POST["yes"]) OR isset($_POST["no"])) AND is_numeric($_POST["accountID"])) { + $reqs = $db->prepare("SELECT * FROM clanrequests WHERE clanID = :id AND accountID = :acc"); + $reqs->execute([':id' => $clan["ID"], ':acc' => ExploitPatch::number($_POST["accountID"])]); + $reqs = $reqs->fetch(); + if(!empty($reqs)) { + $reqs = $db->prepare("DELETE FROM clanrequests WHERE accountID = :acc AND clanID = :cid"); + $reqs->execute([':acc' => ExploitPatch::number($_POST["accountID"]), ':cid' => $clan["ID"]]); + if(isset($_POST["yes"])) { + $join = $db->prepare("UPDATE users SET clan = :cid, joinedAt = :time WHERE extID = :id"); + $join->execute([':cid' => $clan["ID"], ':id' => ExploitPatch::number($_POST["accountID"]), ':time' => time()]); + $gs->sendNotify($clan["clanOwner"], ExploitPatch::number($_POST["accountID"]), ['action' => 6, 'value1' => true, 'value2' => $clan["ID"], 'value3' => time()]); + } else $gs->sendNotify($clan["clanOwner"], ExploitPatch::number($_POST["accountID"]), ['action' => 6, 'value1' => false, 'value2' => $clan["ID"], 'value3' => time()]); + } + } + $reqs = $db->prepare("SELECT * FROM clanrequests WHERE clanID = :id ORDER BY timestamp DESC"); + $reqs->execute([':id' => $clan["ID"]]); + $reqs = $reqs->fetchAll(); + foreach($reqs as &$rqs) { + $mbrs = $db->prepare("SELECT * FROM users WHERE extID = :id"); + $mbrs->execute([':id' => $rqs["accountID"]]); + $mbr = $mbrs->fetch(); + $stats = $dl->createProfileStats($mbr['stars'], $mbr['moons'], $mbr['diamonds'], $mbr['coins'], $mbr['userCoins'], $mbr['demons'], $mbr['creatorPoints'], 0); + $requests .= '
+
+
'.$stats.'
+
+
+ + + +
+
+ + + +
+
+
+
'; + } + if(empty($requests)) $requests = '
+

'.$dl->getLocalizedString("noRequests").'

+
'; + exit($dl->printSong('
+

'.$dl->getLocalizedString("pendingRequests").'

+
+
+ '.$requests.' +
+ + +
+
', 'profile')); + } + if($clan["isClosed"] == 1) { + $clIcon = ''; + $pending = '

'.$dl->getLocalizedString("pendingRequests").'

+ + +
'; + } + else $clIcon = ''; + exit($dl->printSong('
+
+
+

'.$dl->getLocalizedString("settings").'

+
+
+

'.$dl->getLocalizedString("mainSettings").'

+
+
+

'.$dl->getLocalizedString("clanName").'

+ +
+
+

'.$dl->getLocalizedString("clanTag").'

+ +
+
+

'.$dl->getLocalizedString("clanDesc").'

+ +
+
+

'.$dl->getLocalizedString("clanColor").'

+
+
+
+

'.$dl->getLocalizedString("dangerZone").'

+
+
+

'.$dl->getLocalizedString("closedClan").'

+ + + + +
+
+ '.$pending.' +
+

'.$dl->getLocalizedString("giveClan").'

+ + + +
+
+
+

'.$dl->getLocalizedString("deleteClan").'

+ + +
+
+
+
+ + +
+
+ ', 'profile')); + } else { + $name = strip_tags(ExploitPatch::rucharclean(str_replace(' ', '', $_POST["clanname"]), 20)); + $tag = strip_tags(ExploitPatch::charclean(str_replace(' ', '', strtoupper($_POST["clantag"])), 5)); + $desc = base64_encode(strip_tags(ExploitPatch::rucharclean($_POST["clandesc"], 255))); + $color = ExploitPatch::charclean(mb_substr($_POST["clancolor"], 1), 6); + $isClosed = ExploitPatch::number($_POST["isclosed"], 1); + if(!empty($name) AND !empty($color) AND !empty($tag) AND strlen($tag) > 1 AND is_numeric($isClosed)) { + if($filterClanNames >= 1) { + $bannedClanNamesList = array_map('strtolower', $bannedClanNames); + switch($filterClanNames) { + case 1: + if(in_array(strtolower($name), $bannedClanNamesList)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanName").'

+ +
+
')); + break; + case 2: + foreach($bannedClanNamesList as $bannedClanName) { + if(!empty($bannedClanName) && mb_strpos(strtolower($name), $bannedClanName) !== false) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanName").'

+ +
+
')); + } + } + } + if($filterClanTags >= 1) { + $bannedClanTagsList = array_map('strtolower', $bannedClanTags); + switch($filterClanTags) { + case 1: + if(in_array(strtolower($tag), $bannedClanTagsList)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanTag").'

+ +
+
')); + break; + case 2: + foreach($bannedClanTagsList as $bannedClanTag) { + if(!empty($bannedClanTag) && mb_strpos(strtolower($tag), $bannedClanTag) !== false) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanTag").'

+ +
+
')); + } + } + } + $name = base64_encode($name); + $tag = base64_encode($tag); + $check = $db->prepare('SELECT count(*) FROM clans WHERE clan LIKE :c AND ID != :id'); + $check->execute([':c' => $name, ':id' => $clan['ID']]); + $check = $check->fetchColumn(); + if($check > 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("takenClanName").'

+ +
+
', 'browse')); + $check = $db->prepare('SELECT count(*) FROM clans WHERE tag LIKE :t AND ID != :id'); + $check->execute([':id' => $clan['ID'], ':t' => $tag]); + $check = $check->fetchColumn(); + if($check > 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("takenClanTag").'

+ +
+
', 'browse')); + $update = $db->prepare("UPDATE clans SET `clan` = :n, `desc` = :d, `color` = :c, `isClosed` = :ic, `tag` = :t WHERE `ID` = :id"); + $update->execute([':id' => $clan["ID"], ':n' => $name, ':d' => $desc, ':c' => $color, ':ic' => $isClosed, ':t' => $tag]); + $clan["clan"] = htmlspecialchars(base64_decode($name)); + $clan["tag"] = htmlspecialchars(base64_decode($tag)); + $clan["desc"] = htmlspecialchars(base64_decode($desc)); + $clan["color"] = $color; + $clan["isClosed"] = $isClosed; + } + } + } + if(isset($_POST["leave"]) AND $_POST["leave"] == 1 AND $isPlayerInClan == $clan["ID"] AND $clan["clanOwner"] != $_SESSION["accountID"]) { + $leave = $db->prepare("UPDATE users SET clan = 0, joinedAt = :time WHERE extID = :id"); + $leave->execute([':time' => time(), ':id' => $_SESSION["accountID"]]); + $gs->sendNotify($_SESSION["accountID"], $clan['clanOwner'], ['action' => 4, 'value1' => false, 'value2' => $clan["ID"], 'value3' => time()]); + $isPlayerInClan = false; + } + elseif(isset($_POST["join"]) AND !$isPlayerInClan AND $_SESSION["accountID"] != 0) { + if($clan["isClosed"] == 1) { + if($_POST["join"] == 1) { + $join = $db->prepare("SELECT * FROM clanrequests WHERE accountID = :acc AND clanID = :cid"); + $join->execute([':acc' => $_SESSION["accountID"], ':cid' => $clan["ID"]]); + $join = $join->fetch(); + if(empty($join)) { + $join = $db->prepare("INSERT INTO clanrequests (accountID, clanID, timestamp) VALUES (:acc, :cid, :time)"); + $join->execute([':acc' => $_SESSION["accountID"], ':cid' => $clan["ID"], ':time' => time()]); + $gs->sendNotify($_SESSION["accountID"], $clan['clanOwner'], ['action' => 3, 'value1' => true, 'value2' => $clan["ID"], 'value3' => time()]); + } + } else { + $join = $db->prepare("DELETE FROM clanrequests WHERE accountID = :acc AND clanID = :cid"); + $join->execute([':acc' => $_SESSION["accountID"], ':cid' => $clan["ID"]]); + } + } else { + $join = $db->prepare("UPDATE users SET clan = :cid, joinedAt = :j WHERE extID = :id"); + $join->execute([':id' => $_SESSION["accountID"], ':cid' => $clan["ID"], ':j' => time()]); + $isPlayerInClan = $clan["ID"]; + $gs->sendNotify($_SESSION["accountID"], $clan['clanOwner'], ['action' => 4, 'value1' => true, 'value2' => $clan["ID"], 'value3' => time()]); + } + } + if(isset($_POST["kick"]) AND is_numeric($_POST["accountID"]) AND $clan["clanOwner"] == $_SESSION["accountID"]) { + $kick = $db->prepare("SELECT clan FROM users WHERE extID = :id"); + $kick->execute([':id' => ExploitPatch::number($_POST["accountID"])]); + $kick = $kick->fetch(); + if($kick["clan"] == $clan["ID"] AND $clan["clanOwner"] != ExploitPatch::number($_POST["accountID"])) { + $kick = $db->prepare("UPDATE users SET clan = 0, joinedAt = :j WHERE extID = :id"); + $kick->execute([':id' => ExploitPatch::number($_POST["accountID"]), ':j' => time()]); + $gs->sendNotify($clan["clanOwner"], ExploitPatch::number($_POST["accountID"]), ['action' => 5, 'value1' => true, 'value2' => $clan["ID"], 'value3' => time()]); + } + } + if($clan["isClosed"] == 1) $closed = ' '; + $clanname = "

[".htmlspecialchars($clan["tag"]).'] '.htmlspecialchars($clan["clan"]).$closed."

"; + $mbrs = $db->prepare("SELECT * FROM users WHERE clan = :cid"); + $mbrs->execute([':cid' => $clan["ID"]]); + $mbrs = $mbrs->fetchAll(); + foreach($mbrs as &$mbr) { + if($clan["clanOwner"] == $_SESSION["accountID"]) $kick = '
+ '; + $allstars += $mbr['stars']; + $allmoons += $mbr['moons']; + $alldias += $mbr['diamonds']; + $allcoins += $mbr['coins']; + $allucoins += $mbr['userCoins']; + $alldemons += $mbr['demons']; + $allcp += $mbr['creatorPoints']; + $stats = $dl->createProfileStats($mbr['stars'], $mbr['moons'], $mbr['diamonds'], $mbr['coins'], $mbr['userCoins'], $mbr['demons'], $mbr['creatorPoints'], 0); + $mbr["userName"] = $mbr["userName"] == 'Undefined' ? $gs->getAccountName($mbr['extID']) : $mbr['userName']; + // Avatar management + $avatarImg = ''; + $iconType = ($mbr['iconType'] > 8) ? 0 : $mbr['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $mbr['accIcon']], 1 => ['type' => 'ship', 'value' => $mbr['accShip']], 2 => ['type' => 'ball', 'value' => $mbr['accBall']], 3 => ['type' => 'ufo', 'value' => $mbr['accBird']], 4 => ['type' => 'wave', 'value' => $mbr['accDart']], 5 => ['type' => 'robot', 'value' => $mbr['accRobot']], 6 => ['type' => 'spider', 'value' => $mbr['accSpider']], 7 => ['type' => 'swing', 'value' => $mbr['accSwing']], 8 => ['type' => 'jetpack', 'value' => $mbr['accJetpack']]]; + $iconValue = isset($iconTypeMap[$iconType]) ? $iconTypeMap[$iconType]['value'] : 1; + if($mbr["extID"] != $clan["clanOwner"]) { + $badgeImg = ''; + $queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); + $queryRoleID->execute([':accountID' => $mbr["extID"]]); + if($roleAssignData = $queryRoleID->fetch(PDO::FETCH_ASSOC)) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } + } + $avatarImg = 'Avatar'; + $members .= '
+
'.$kick.'
+
'.$stats.'
+

'.sprintf($dl->getLocalizedString("joinedAt"), $dl->convertToDate($mbr["joinedAt"], true)).'

+
'; + } else { + $avatarImg = 'Avatar'; + $owner = '
+
+
'.$stats.'
+

'.sprintf($dl->getLocalizedString("createdAt"), $dl->convertToDate($clan["creationDate"], true)).'

+
'; + } + } + $allstats = $dl->createProfileStats($allstars, $allmoons, $alldias, $allcoins, $allucoins, $alldemons, $allcp, 0); + $total = '
+
'.$allstats.'
+
'; + if(empty($members)) $members .= '
+

'.$dl->getLocalizedString("noMembers").'

+
'; + if(empty($clan["desc"])) $clan["desc"] = $dl->getLocalizedString("noClanDesc"); + if($clan["clanOwner"] == $_SESSION["accountID"]) $settings = '
'; + elseif($isPlayerInClan == $clan["ID"]) $membermenu = '
'; + elseif(!$isPlayerInClan) { + if($clan["isClosed"] == 1) { + $join = $db->prepare("SELECT * FROM clanrequests WHERE accountID = :acc AND clanID = :cid"); + $join->execute([':acc' => $_SESSION["accountID"], ':cid' => $clan["ID"]]); + $join = $join->fetch(); + if(empty($join)) $membermenu = '
'; + else $membermenu = '
'; + } else $membermenu = '
'; + } + $membercount = count($mbrs) - 1; // cuz owner + $dontmind = mb_substr($membercount, -1); + if($dontmind == 1) $dm = 0; elseif($dontmind < 5 AND $dontmind > 0) $dm = 1; else $dm = 2; + if($membercount > 9 AND $membercount < 20) $dm = 2; + $clanDescription = $dl->parseMessage(htmlspecialchars($clan["desc"])); + if($_SESSION["accountID"] != 0 AND $clan["clanOwner"] != $_SESSION["accountID"] AND !empty($membermenu)) $menu = ''; + $dl->printSong('
+
+ + '.$back.'
'.$clanname.'
'.$settings.$menu.' +
+

'.$clanDescription.'

+
+ '.$total.' +
+
+

'.$dl->getLocalizedString("clanOwner").'

+
'.$owner.'
+
+
+

'.$dl->getLocalizedString("clanMembers").'

+

'.sprintf($dl->getLocalizedString("members".$dm), $membercount).'

+
+ '.$members.' +
', 'profile'); +} +?> \ No newline at end of file diff --git a/dashboard/clans/create.php b/dashboard/clans/create.php new file mode 100644 index 000000000..98391fa7f --- /dev/null +++ b/dashboard/clans/create.php @@ -0,0 +1,144 @@ +printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'browse')); +require "../".$dbPath."incl/lib/exploitPatch.php"; +require_once "../".$dbPath."incl/lib/mainLib.php"; +$gs = new mainLib(); +require "../".$dbPath."incl/lib/connection.php"; +require "../".$dbPath."config/security.php"; +$isPlayerInClan = $gs->isPlayerInClan($_SESSION["accountID"]); +$dl->printFooter('../'); +$dl->title($dl->getLocalizedString("createClan")); +if($isPlayerInClan) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("alreadyInClan").'

+ +
+
', 'browse')); +if(isset($_POST["name"]) AND isset($_POST["desc"]) AND isset($_POST["color"])) { + $name = strip_tags(ExploitPatch::rucharclean(str_replace(' ', '', $_POST["name"]), 20)); + $tag = strip_tags(ExploitPatch::charclean(str_replace(' ', '', strtoupper($_POST["tag"])), 5)); + $desc = base64_encode(strip_tags(ExploitPatch::rucharclean($_POST["desc"], 255))); + $color = ExploitPatch::charclean(mb_substr($_POST["color"], 1), 6); + if(!empty($name) AND !empty($color) AND !empty($tag) AND strlen($tag) > 1) { + if($filterClanNames >= 1) { + $bannedClanNamesList = array_map('strtolower', $bannedClanNames); + switch($filterClanNames) { + case 1: + if(in_array(strtolower($name), $bannedClanNamesList)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanName").'

+ +
+
')); + break; + case 2: + foreach($bannedClanNamesList as $bannedClanName) { + if(!empty($bannedClanName) && mb_strpos(strtolower($name), $bannedClanName) !== false) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanName").'

+ +
+
')); + } + } + } + if($filterClanTags >= 1) { + $bannedClanTagsList = array_map('strtolower', $bannedClanTags); + switch($filterClanTags) { + case 1: + if(in_array(strtolower($tag), $bannedClanTagsList)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanTag").'

+ +
+
')); + break; + case 2: + foreach($bannedClanTagsList as $bannedClanTag) { + if(!empty($bannedClanTag) && mb_strpos(strtolower($tag), $bannedClanTag) !== false) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badClanTag").'

+ +
+
')); + } + } + } + $name = base64_encode($name); + $tag = base64_encode($tag); + $check = $db->prepare('SELECT count(*) FROM clans WHERE clan LIKE :c'); + $check->execute([':c' => $name]); + $check = $check->fetchColumn(); + if($check > 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("takenClanName").'

+ +
+
', 'browse')); + $check = $db->prepare('SELECT count(*) FROM clans WHERE tag LIKE :t'); + $check->execute([':t' => $tag]); + $check = $check->fetchColumn(); + if($check > 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("takenClanTag").'

+ +
+
', 'browse')); + $clan = $db->prepare("INSERT INTO clans (`clan`, `desc`, `clanOwner`, `color`, `creationDate`, `tag`) VALUES (:c, :d, :co, :col, :cr, :t)"); + $clan->execute([':c' => $name, ':d' => $desc, ':co' => $_SESSION["accountID"], ':col' => $color, ':cr' => time(), ':t' => $tag]); + $owner = $db->prepare("UPDATE users SET clan = :c, joinedAt = :j WHERE extID = :i"); + $owner->execute([':c' => $db->lastInsertId(), ':j' => time(), ':i' => $_SESSION["accountID"]]); + $dl->printSong('
+

'.$dl->getLocalizedString("createClan").'

+
+

'.sprintf($dl->getLocalizedString("createdClan"), $color, base64_decode($name)).'

+ +
+
', 'browse'); + } +} else $dl->printSong('
+

'.$dl->getLocalizedString("createClan").'

+
+

'.$dl->getLocalizedString("createClanDesc").'

+
+
+
+
+ +
+
+', 'browse'); +?> \ No newline at end of file diff --git a/dashboard/clans/index.php b/dashboard/clans/index.php new file mode 100644 index 000000000..c644b3bcc --- /dev/null +++ b/dashboard/clans/index.php @@ -0,0 +1,65 @@ +printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'browse')); +$isPlayerInClan = $gs->isPlayerInClan($_SESSION["accountID"]); +$dl->printFooter('../'); +$dl->title($dl->getLocalizedString("clans")); +$clans = $db->prepare("SELECT clans.*, COUNT(users.clan) AS members FROM clans LEFT JOIN users ON clans.id = users.clan GROUP BY clans.id ORDER BY members DESC;"); +$clans->execute(); +$clans = $clans->fetchAll(); +$style = $closed = $create = ""; +foreach($clans as &$clan) { + $closed = ''; + $name = htmlspecialchars(base64_decode($clan["clan"])); + $tag = htmlspecialchars(base64_decode($clan["tag"])); + $desc = $dl->parseMessage(htmlspecialchars(base64_decode($clan["desc"]))); + if(empty($desc)) $desc = $dl->getLocalizedString("noClanDesc"); + $members = $db->prepare("SELECT count(clan) FROM users WHERE clan = :id"); + $members->execute([':id' => $clan["ID"]]); + $members = $members->fetchColumn() - 1; + $dontmind = mb_substr($members, -1); + if($dontmind == 1) $dm = 0; elseif($dontmind < 5 AND $dontmind > 0) $dm = 1; else $dm = 2; + if($members > 9 AND $members < 20) $dm = 2; + if($clan["isClosed"] == 1) $closed = ' '; + // Avatar management + $avatarImg = ''; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $clan["clanOwner"]]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = isset($iconTypeMap[$iconType]) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; + } + $options .= '
+

'.sprintf($dl->getLocalizedString('demonlistLevel'), ' ['.$tag.'] '.$name.$closed.'', $clan["clanOwner"], $gs->getAccountName($clan["clanOwner"]), $avatarImg).'

+

'.sprintf($dl->getLocalizedString("members".$dm), $members).'

+

'.$desc.'

+
'; +} +if(empty($options)) { + $style = "align-items: center !important;align-content: center !important;justify-content:center !important;"; + $options = '

'.$dl->getLocalizedString("noClans").'

'; +} +if($_SESSION["accountID"] != 0 AND !$isPlayerInClan) $create = ''; +$dl->printSong('
+

'.$dl->getLocalizedString("clans").'

+
+ '.$options.' +
'.$create.' +
', 'browse'); +?> \ No newline at end of file diff --git a/dashboard/download/updater.php b/dashboard/download/updater.php new file mode 100644 index 000000000..75f3e8f52 --- /dev/null +++ b/dashboard/download/updater.php @@ -0,0 +1,43 @@ + true, 'time' => $time, 'client' => $client])); +} else { + if($_GET["dl"] == "updater") $fileName = $file = "GDPS-Updater.exe"; + elseif($_GET["dl"] == "client") $fileName = $file = "GDPS-Client.exe"; + else $fileName = $file = $gdps.".zip"; + $bufferSize = 2097152; + $filesize = filesize($file); + $offset = 0; + $length = $filesize; + if (isset($_SERVER['HTTP_RANGE'])) { + preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches); + $offset = intval($matches[1]); + $end = $matches[2] || $matches[2] === '0' ? intval($matches[2]) : $filesize - 1; + $length = $end + 1 - $offset; + header('HTTP/1.1 206 Partial Content'); + header("Content-Range: bytes $offset-$end/$filesize"); + } + header('Content-Type: ' . mime_content_type($file)); + header("Content-Length: $filesize"); + header("Content-Disposition: attachment; filename=\"$fileName\""); + header('Accept-Ranges: bytes'); + + $file = fopen($file, 'r'); + fseek($file, $offset); + while ($length >= $bufferSize) + { + print(fread($file, $bufferSize)); + $length -= $bufferSize; + } + if ($length) print(fread($file, $length)); + fclose($file); +} +?> \ No newline at end of file diff --git a/dashboard/errors/400/index.php b/dashboard/errors/400/index.php new file mode 100644 index 000000000..0710ce4ec --- /dev/null +++ b/dashboard/errors/400/index.php @@ -0,0 +1,47 @@ +title($e); +echo '

'.$e.'

+

'.$dl->getLocalizedString($e).'

+

'.$dl->getLocalizedString($e.'!').'

'; +?> + \ No newline at end of file diff --git a/dashboard/errors/403/index.php b/dashboard/errors/403/index.php new file mode 100644 index 000000000..439c7091d --- /dev/null +++ b/dashboard/errors/403/index.php @@ -0,0 +1,47 @@ +title($e); +echo '

'.$e.'

+

'.$dl->getLocalizedString($e).'

+

'.$dl->getLocalizedString($e.'!').'

'; +?> + \ No newline at end of file diff --git a/dashboard/errors/404/index.php b/dashboard/errors/404/index.php new file mode 100644 index 000000000..ead09efea --- /dev/null +++ b/dashboard/errors/404/index.php @@ -0,0 +1,47 @@ +title($e); +echo '

'.$e.'

+

'.$dl->getLocalizedString($e).'

+

'.$dl->getLocalizedString($e.'!').'

'; +?> + \ No newline at end of file diff --git a/dashboard/errors/500/index.php b/dashboard/errors/500/index.php new file mode 100644 index 000000000..dadd47f8e --- /dev/null +++ b/dashboard/errors/500/index.php @@ -0,0 +1,47 @@ +title($e); +echo '

'.$e.'

+

'.$dl->getLocalizedString($e).'

+

'.$dl->getLocalizedString($e.'!').'

'; +?> + \ No newline at end of file diff --git a/dashboard/errors/502/index.php b/dashboard/errors/502/index.php new file mode 100644 index 000000000..6fa6664ac --- /dev/null +++ b/dashboard/errors/502/index.php @@ -0,0 +1,47 @@ +title($e); +echo '

'.$e.'

+

'.$dl->getLocalizedString($e).'

+

'.$dl->getLocalizedString($e.'!').'

'; +?> + \ No newline at end of file diff --git a/dashboard/errors/index.php b/dashboard/errors/index.php new file mode 100644 index 000000000..742d3251d --- /dev/null +++ b/dashboard/errors/index.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/dashboard/incl/XOR.php b/dashboard/incl/XOR.php new file mode 100644 index 000000000..ee878c91c --- /dev/null +++ b/dashboard/incl/XOR.php @@ -0,0 +1,61 @@ +text2ascii($key); + $plaintext = $this->text2ascii($plaintext); + + $keysize = count($key); + $input_size = count($plaintext); + + $cipher = ""; + + for ($i = 0; $i < $input_size; $i++) + $cipher .= chr($plaintext[$i] ^ $key[$i % $keysize]); + + return $cipher; + } + + public function crack($cipher, $keysize) { + $cipher = $this->text2ascii($cipher); + $occurences = $key = array(); + $input_size = count($cipher); + + for ($i = 0; $i < $input_size; $i++) { + $j = $i % $keysize; + if (++$occurences[$j][$cipher[$i]] > $occurences[$j][$key[$j]]) + $key[$j] = $cipher[$i]; + } + + return $this->ascii2text(array_map(function($v) { return $v ^ 32; }, $key)); + } + + public function plaintext($cipher, $key) { + $key = $this->text2ascii($key); + $cipher = $this->text2ascii($cipher); + $keysize = count($key); + $input_size = count($cipher); + $plaintext = ""; + + for ($i = 0; $i < $input_size; $i++) + $plaintext .= chr($cipher[$i] ^ $key[$i % $keysize]); + + return $plaintext; + } + + private function text2ascii($text) { + return array_map('ord', str_split($text)); + } + + private function ascii2text($ascii) { + $text = ""; + + foreach($ascii as $char) + $text .= chr($char); + + return $text; + } +} \ No newline at end of file diff --git a/dashboard/incl/auth.php b/dashboard/incl/auth.php new file mode 100644 index 000000000..b2175d2a4 --- /dev/null +++ b/dashboard/incl/auth.php @@ -0,0 +1,25 @@ +query("SHOW COLUMNS FROM `accounts` LIKE 'auth'"); + $exist = $check->fetchAll(); + if(empty($exist)) return 'no'; + if($_SESSION["accountID"] != 0) { + $query = $db->prepare("SELECT auth FROM accounts WHERE accountID = :id"); + $query->execute([':id' => $_SESSION["accountID"]]); + $auth = $query->fetch(); + if($_COOKIE["auth"] != $auth["auth"]) $_SESSION["accountID"] = 0; + } else { + $query = $db->prepare("SELECT accountID FROM accounts WHERE auth = :id"); + $query->execute([':id' => $_COOKIE["auth"]]); + $auth = $query->fetch(); + if(!empty($auth) AND $_COOKIE["auth"] != 'none') $_SESSION["accountID"] = $auth["accountID"]; + } + return true; + } +} +?> diff --git a/dashboard/incl/cvolton.css b/dashboard/incl/cvolton.css index 5ebabb0fd..6fdbeff96 100644 --- a/dashboard/incl/cvolton.css +++ b/dashboard/incl/cvolton.css @@ -1,44 +1,2640 @@ -.menubar { - background-color: #1e2124; - color: #d6ddde; +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap'); + +@media screen and (max-width: 1537px) { + body {overflow-y: auto !important} + .maindiv{width:100% !important} + .secondarydiv{flex-direction: column;} + .mainlist{width:80% !important} + .new-form-control.songs {grid-template-columns: repeat(1, 100%) !important;} + .last{margin-bottom: 55px !important;} + .profileform{width:75vw !important;margin: 20px 0px 50px 0px !important;} + .packfc .profilepic {font-size: 15px !important;} + .itemsbox {width: 75% !important;} + .songUploadDiv {min-width: 55vw !important;} + .icon-cube, .icon-ball, .icon-ufo, .icon-robot, .icon-spider, .icon-swing, .icon-jetpack {height: 50px !important;} + .icon-ship {height: 37px !important;} + .icon-wave {height: 33px !important;} + .friends-button-div {min-width: 40% !important; width: 40% !important;} } -.content { - background-color: #36393e; - color: #a7a8aa; + +@media (min-width: 1537px) { + .container {max-width: 1450px;} + .nav-link {width:max-content;} + .btn-group {position:initial !important; background: #00000000 !important;} } -html,body{ - height:100%; - background-color: #36393e + +@media (max-width: 1000px) { + .form h1 {font-size:28px !important;} + .form__inner p {font-size:17px !important;} + .profilenick {font-size: 20px !important;} + .mavdiv .accbtn, .profilepic, .profilepic .accbtn {font-size: 20px !important;} + .songacc {font-size: 13px !important;} + .songidyeah {font-size: 11px !important;} + .btn-group {right: 5px !important;} + .btn-group button, .btn-group a, .btn-group i {font-size: 13px !important;overflow-wrap: anywhere; height: 100%;} + .mainpagecardh1 {font-size:27px !important;} + .mph1 {font-size:20px !important;height: 40px !important; width: 40px !important;} + .welcomeh1 {font-size:29px !important;} + .welcomep, .mpp {font-size:17px !important;} + .welcomeh3 {font-size:20px !important;} + .mpsmall {font-size:12px !important;} + .profileform{width:95vw !important;} + .profilemsg{font-size:17px !important;} + .mainlist{width:95% !important} + .msgbox {min-width:85vw !important} + .form, .demonlist{width:95vw !important} + .dlh1 {font-size:25px !important;} + .dltext, .dltext button {font-size:20px !important;} + .dlp {font-size:15px !important;} + .dlposttext, .dlposttext button {font-size:10px !important;} + .dlcard {display: grid !important;} + .dlcard iframe {margin-top: 10px !important; width: 100% !important;} + .accnamedesc, .acccontrol {width:100% !important;} + .profacclist {flex-direction: column !important;} + .songauthor {font-size:10px !important;} + .songname {font-size:15px !important;} + .clandesc {font-size:15px !important;} + .clanname {font-size:25px !important;} + .msgupd, .goback {font-size: 15px !important; padding: 10px !important;} + .clanname i {font-size:10px !important;} + .clancreatetext {font-size:14px !important;} + .clanownernick, .a-btn h1, .profilename, .messengerbox h1 {font-size:25px !important;} + .clanmembernick {font-size:18px !important;} + .clanownertext, .clanmemberstext {font-size:16px !important;} + .mainsettings {flex-direction: column !important;} + .secondsettingsform {width: 100% !important;} + .music {font-size:13px !important;} + .dlpoints {font-size: 15px !important; right: 5% !important; top: 3%} + .itemslist {width: max-content !important; flex-direction: row !important; flex-wrap: nowrap !important;} + .itembtn {width: max-content !important; margin-right: 0px !important; height: auto !important;} + .itemoverflow {overflow-x: auto !important; max-width: 100%; padding-right: 10px;overflow-y: hidden !important;} + .acccomments {flex-direction: column-reverse !important; align-items: center !important;} + .acccomments h3 {font-size:11px !important;} + .acclistnick, .acclistdiv {flex-direction: column; align-items: center; margin-left: 0px !important;} + .accresultrole {font-size: 13px !important; text-align: center !important;} + .clansname h1 {font-size:25px !important; display: flex; flex-direction: column; margin-bottom: 0px !important;} + .clansname {align-items: flex-start !important; flex-direction: column !important;} + .clansdesc {font-size:15px !important;} + .clansmembercount {margin-bottom:5px !important; margin-top:5px !important;} + .clansmembercount h3 {font-size:17px !important;} + .clansmembercount button {padding: 10px !important; font-size:13px !important;} + .suggest .accbtn, .suggest .levelname {font-size:23px !important;} + .suggest .dltext {font-size:18px !important;} + .buffer {margin: 0px !important;} + .quests-form {height: 80% !important;} + .audioDiv {width: 98% !important; left: 1% !important; grid-gap: 0px !important;} + .audioDiv .cover, .audioDiv .image {width: 67px !important; height: 67px !important} + .audioDiv .name {margin: 0px !important; font-size: 18px !important;} + .audioDiv .track {width: 78% !important; margin-left: 10px !important;} + .audioDiv .length {min-width: 0px !important; width: 100% !important;} + .audioDiv .author {font-size: 15px !important;} + .audioDiv .volumeDiv {height: 65px !important;} + .audioDiv .queueDiv {bottom: 110% !important;} + .queueDiv .track {width: 100% !important; margin-left: 0px !important;} + .audioDiv .length.volume {min-width: 65px !important; bottom: 28px !important;} + .audioDiv .anotherVolume {opacity: 1 !important; visibility: initial !important;} + .audioDiv i.volume {opacity: 0 !important; visibility: hidden !important;} + .audioDiv.showButton {width: max-content !important; padding: 15px !important;} + .audioDiv.showButton i {font-size: 20px !important;} + .audioDiv .cover i {font-size: 40px !important;} + .queueDiv .cover, .queueDiv .image {width: 55px !important; height: 55px !important} + .audioDiv:not(.show):not(.showButton):not(.queueDiv), .audioDiv.showButton:not(.show) {animation-duration: 0s !important;} + .icon-cube, .icon-ball, .icon-ufo, .icon-robot, .icon-spider, .icon-swing, .icon-jetpack {height: 45px !important;} + .icon-ship {height: 30px !important;} + .icon-wave {height: 27px !important;} + .icon-kit-div {width: 20% !important;} + .icon-kit {flex-wrap: wrap !important;} + .form-control {flex-wrap: wrap !important; justify-content: center !important;} + .profilepic {width: max-content !important;} + .modactionsspoiler {font-size: 20px !important;} + .another-chat-div {height: 80%;} + .itemsbox {flex-direction: column !important; margin-left: 2.5vw !important; width: 95vw !important; margin-bottom: 50px !important; margin-top: 15px !important; flex-wrap: nowrap !important;} + .manage-button-div {align-items: flex-start !important; flex-direction: column !important;} + .friends-button-div {min-width: 100% !important; width: 100% !important;} + #htmlpage > div {margin-bottom: 50px !important; margin-top: 20px !important;} + .automod-form {max-height: 140vh !important; height: 140vh !important;} + .automod-boxes, .attribute-divs {flex-direction: column !important;} + .attribute-button {width: 100% !important;} + .profilelikes {font-size: 20px !important; height: max-content;} + .twice-form {max-height: 100vh !important; height: 100vh !important;} + .cards-form {height: 90% !important;} + .cards-overflow {height: max-content !important;} } -.fill { - flex:1; + +* { + font-family: 'Google Sans', 'Inter', 'Segoe UI' , 'Calibri Light', 'Arial'; +} + +#ads, .adsbygoogle, #ads_bottom_static { + display: none !important; +} + +.btn-group { + position: fixed; + bottom: -50px; + right: 20px; + backdrop-filter: blur(2px); + background: #00000050; +} + +.closebtn { + margin: 0px !important; + font-size: 25px !important; + padding: 14px 19px !important; + margin-left: 5px !important; +} + +.smallpage { + margin-top: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-wrap:wrap; +} + +.btns { + display:flex; + width:100%; + grid-gap:5px; +} + +.music { + display: flex; + margin: 0px; + padding: 4px 8px; + background: #141414; + border-radius: 500px; + align-items: center; + font-size: 16px; +} + +input, select { + cursor: pointer; +} + +.replybtn { + color: #909090; + margin-left: 5px; + font-size: 13px; + display: block; +} + +.table-inverse td, .table-inverse th, .table-inverse thead th { + border-color: #32383e00; +} + +.samepass { + display: none; + color: #ffb4b4; + text-align: center; + width: 100%; + margin: -11px 0px -11px 0px; + transition: 0.3s; +} + +.btn-success { + background: #5effaf !important; + color: #149957 !important; +} + +.no { + display: block; + transition: 0.3s; +} + +.nav-link { + transition: 0.2s; +} + +.flag { + vertical-align: sub; +} + +.footer { + display: flex; + justify-content: space-between; + position: fixed; + bottom: 0px; + font-size: 15px; + padding: 10px; + text-align: left; + width: 100%; + color: #c0c0c0; + justify-self: center; + background: linear-gradient(0deg, rgba(33,37,41,1) 0%, rgba(30,33,36,0) 100%); +} + +.profilepic { + font-size: 25px; + display: flex; + width: 100%; + margin: 0px 5px 0px 5px; + align-items: center; + justify-content: center; + grid-gap: 5px; + color: #c0c0c0; +} + +.icon, .icon:visited { + transition: 0.3s; + margin-right: 10px; + background: #1e2124; + height: 32px; + width: 32px; + margin-left: -4px; + text-align: center; + display: inline-flex; + border-radius: 500px; + align-items: center; + justify-content: center; +} + +.table-inverse td, .table-inverse th, .table-inverse thead th { + text-align:center; + vertical-align: middle; +} + +.newMsgs { + font-size: 10px; + margin-left: -5px; + margin-right: 3px; + color: #e35151; +} + +.msgupd { + background-color: #212529; + color: #c0c0c0; + font-size: 20px; + padding: 15px; + margin-left: 10px; + border: none; + margin-top: 15px; + border-radius: 500px; + transition: 0.3s; + cursor: pointer; +} + +.goback { + background-color: #212529; + color: #c0c0c0; + font-size: 20px; + padding: 15px; + margin-right: 10px; + border: none; + margin-top: 15px; + border-radius: 500px; + transition: 0.3s; + cursor: pointer; +} + +.socials { + text-align: right; + justify-self: center; + justify-items: right; + margin: 0px 3px 0px 3px; + background: rgba(0,0,0,0); +} + +.h-captcha { + justify-self: center; +} + +.field { + display: grid; + width: 100%; +} + +.notify { + background: rgb(73 41 40 / 67%); + border: 1px solid #ff7f7f; + border-radius: 20px; + padding: 5px 10px; + text-align: center; + height: max-content; + pointer-events: none; + color: white; + font-weight: 700; + max-width: 90vw; + width: max-content; + backdrop-filter: blur(5px); + opacity: 0; + transition: 0.3s; + z-index: 10; +} + +.notifyblue { + width: 100%; + background: rgba(29,133,240, 0.2); + border: 2px solid #016bfc; + border-radius: 20px; + margin: 0px 0px 20px 0px; + padding-top: 20px; + padding-bottom: 20px; + text-align: center; +} + +.checkbox { + justify-items: left; + color: #c0c0c0; + display: flex; + align-items: center; + grid-gap: 5px; +} + +.messengeryou { + padding: 10px; + width: 53%; + justify-self: right; + border-radius: 20px; + background: #29282c; +} + +.ratecheck { + text-align: left; + color: white; +} + +.messenger { + padding: 10px; + margin-top: 10px; + width: 100%; + border-radius: 20px; + background: #29282c; +} + + +.field-options { + appearance: none; + transition: 0.2s; +} + +.profile { + padding: 10px; + margin-top: 10px; + width: 100%; + border-radius: 20px; + background: #29282c; +} + +.messengernotyou { + padding: 10px; + width: 53%; + justify-self: left; + border-radius: 20px; + background: #29282c; +} + +.dmbox { + min-width: 500px; + overflow: scroll; + overflow-x: hidden; + padding: 5px 0px 15px 15px !important; +} + +::-webkit-scrollbar { + width:15px; + border-radius:500px; +} + +::-webkit-scrollbar-track { + background-color: rgba(0, 0, 0, 0); + border-radius: 100px; +} + +.msgbox::-webkit-scrollbar-thumb, +.form::-webkit-scrollbar-thumb, +.clandesc::-webkit-scrollbar-thumb, +.audioDiv .queueDiv::-webkit-scrollbar-thumb, +.rewards-div::-webkit-scrollbar-thumb { + background-color: #373A3F; + border-radius: 100px; + border: 4px solid #181a1b; +} + +.itemslist::-webkit-scrollbar-thumb, +.new-form-control::-webkit-scrollbar-thumb, +.dmbox::-webkit-scrollbar-thumb, +.itemoverflow::-webkit-scrollbar-thumb, +.clan-form-control::-webkit-scrollbar-thumb { + background-color: #373A3F; + border-radius: 100px; + border: 4px solid #141414; +} + +.itemslist { + overflow: auto; + border-radius: 20px; + grid-gap: 10px; + display: flex; + margin: 10px 0px 10px 10px; + flex-wrap: wrap; + align-content: flex-start; +} + +::-webkit-scrollbar-thumb { + background-color: #181a1b; + border-radius: 100px; + border: 4px solid #373A3F; +} + +.messageyou { + font-size:13px; + overflow-wrap: anywhere; + color:white; + margin:5px; + text-align:right; +} + +.messagenotyou { + font-size: 13px; + overflow-wrap: anywhere; + color: white; + margin: 5px; + text-align: left; +} + +.profilemsg { + font-size: 17px; + color: #c0c0c0; + margin: 5px; + text-align: left; + overflow-wrap: anywhere; +} + +.msgbox { + text-align:center; + min-width: 500px; + max-height: 50vh; + overflow:scroll; + overflow-x: hidden; +} + +.receiver { + text-align:center; + font-size:20px; + color:white; +} + +.subjectyou { + overflow-wrap: anywhere; + text-align:right; + font-size:17px; + font-weight:500; + margin-right:5px; + color:white; } + +.subjectnotyou { + overflow-wrap: anywhere; + text-align:left; + font-size:17px; + font-weight:500; + margin-left:5px; + color:white; +} + +.profilenick { + text-align:left; + font-size:22px; + font-weight:700; + margin-left:5px; + color:white; + width: 100%; +} + +.table .table { + color: #fff; + background: #141414; +} + +.tcell { + padding: .75rem; + vertical-align: top; + border-top: 3px solid #181a1b !important; +} + +.field input, +.card input, +.form-control, +#input { + border-radius: 500px; + border: 0px solid #141414; + background-color: rgb(0 0 0 / 2%); + outline: none; + width: 100%; + padding: 7px 10px; + font-size: 13px; + background: #141414; + color: gray; +} + +.field:focus { + border-radius: 500px; + background-color: rgb(0 0 0 / 2%); + outline: none; + width: 100%; + padding: 7px 10px; + font-size: 13px; + background: #141414; + color: gray; +} + +.details .field input { + display: block; + width: 130%; +} + +.details { + width:100%; +} + +.form__create > div { + display: flex; + width:100%; + justify-content:center; +} + +#selecthihi { + display: flex; + justify-content: space-between; + grid-gap: 10px; +} + +#selecthihi > select { + width: 100%; + margin: 0px; +} + +#file-upload-button { + opacity:0; + cursor:pointer; +} + +form p, +p { + font-size: 20px; + color: #c0c0c0; + text-align:center; + margin:0px; + width:100%; +} + +.a { + font-size: 20px; + text-align:center; + margin:0px; + transition:0.2s; + width:100%; +} + +.a-btn { + border: none; + background: none; + cursor: pointer; +} + +.a-btn:hover { + text-decoration: underline; + text-decoration-color: dodgerblue; +} + +summary { + color: #d0d0d0; + font-size: 15px; + cursor: pointer; +} + +select::-webkit-scrollbar { + width:15px; + border-radius:500px; +} + +select::-webkit-scrollbar-thumb { + background-color: #141414; + border-radius: 100px; + border: 4px solid #373A3F; +} + +#bruh{ + margin-bottom:20px; + font-weight: 700; +} +h2{ + font-size: 17px; + color: #c0c0c0; + margin-top: 0px; + text-align:center; + margin-bottom: 1rem; + font-weight: 400; +} + +h3{ + margin: 15px 0px 20px 0px; + font-weight: 400; + text-align:center; +} + +body { + display: flex; + align-items: center; + justify-content: start; + flex-direction: column; + margin: 0px; + padding: 0px; + overflow: hidden; + background-color: #36393e; + min-height: calc(100vh - 10px); +} + .btn-primary, .btn-primary:visited { - background-color: #212529; - border-color: #212529; + background-color: #212529; + color: #fff; + font-weight: 700; + font-size: 15px; + padding: 7px 20px; + border: none; + border-radius: 500px; + width: 100%; + transition: 0.3s; + cursor: pointer; } + .btn-primary:hover, .btn-primary:active, -.btn-primary:focus { - background-color: #47494e; - border-color: #47494e; +.btn-primary:focus, +.msgupd:hover, +.msgupd:active, +.msgupd:focus, +.goback:hover, +.goback:active, +.goback:focus { + background-color: #47494e; + border-color: #47494e; +} + +.btn-rendel, +.btn-rendel:visited { + background-color: #373A3F; + font-weight: 700; + font-size: 15px; + color: #007bff; + padding: 7px 20px; + border: none; + border-radius: 500px; + width: 100%; + transition: 0.3s; + cursor: pointer; +} + +.btn-rendel:hover, +.btn-rendel:active, +.btn-rendel:focus { + color: #007bff; + background-color: #47494e; + text-decoration: none; + border-color: #47494e; +} + +.accbtn { + background-color: #373A3F00; + font-weight: 700; + font-size: 15px; + color: #007bff; + margin:0px; + padding:0px; + border: none; + border-radius: 500px; + cursor: pointer; +} + +.btn-song, +.btn-song:visited { + background-color: #212529; + color: #fff; + font-weight: 700; + font-size: 15px; + padding: 7px 20px; + border: none; + border-radius: 500px; + width: 100%; + transition: 0.3s; + cursor: pointer; +} + +.btn-song:hover, +.btn-song:active, +.btn-song:focus { + background-color: #47494e; + border-color: #47494e; +} + +.btn-upload-song, +.btn-upload-song:visited { + background-color: #212529; + color: #c0c0c0; + font-weight: 700; + font-size: 15px; + padding: 7px 20px; + border: none; + border-radius: 500px; + width: 100%; + transition: 0.3s; + cursor: pointer; +} + +.btn-upload-song input[type=file] { + position: absolute; + opacity: 0; + cursor: pointer; + width: 0px; + height: 0px; +} + +.btn-upload-song:hover, +.btn-upload-song:active, +.btn-upload-song:focus, +.btn-size:hover, +.btn-size:active, +.btn-size:focus { + background-color: #47494e; + border-color: #47494e; +} + +.btn-size, +.btn-size:visited { + background-color: #ffb1ab !important; + border-color: #ffb1ab !important; + transition: 0.3s; + color: #bc6363 !important; +} + +.btn-size:hover, +.btn-size:active, +.btn-size:focus { + color: #c0c0c0; +} + +.btn-size > .icon, +.btn-size:visited > .icon { + background: #dc8a80; +} + +.btn-size:hover > .icon, +.btn-size:active > .icon, +.btn-size:focus > .icon{ + background: #1e2124; +} + +.btn-block, +.btn-block:visited, +.btn-block:hover, +.btn-block:active, +.btn-block:focus, +.btn-primary:disabled{ + background: #141414; + color: #303030; + font-weight: 700; + font-size: 15px; + padding: 7px 20px; + border: none; + border-radius: 500px; + width: 100%; + transition: 0.3s; + cursor: default; +} + +.form { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + background: #181a1b; + width: max-content; + margin: auto auto; + border-radius: 30px; + padding: 7px 20px; + box-shadow: 0 0 20px 1px #181a1b1f; +} + +.form h1 { + margin: 15px 0 15px; + font-size: 34px; + color: white; + text-align:center; +} + +#navbarepta { + width: 100vw; +} + +.color123 > input { + position: relative; + padding: 0px; + background: none; + border: none; + height: 40px; +} + +.color123 > input::-webkit-color-swatch { + border: none; + border-radius: 500px; +} + +.form__inner { + display: grid; + grid-template-columns: 1fr; + justify-items: center; + align-items: stretch; + grid-gap: 20px; + font-size: 14px; + width: 100%; +} + +.imgflag { + width: 18px; +} + +select, +option, +.quest { + display: grid; + grid-template-columns: 3fr; + border-radius: 500px; + border: 1px solid #141414; + background-color: rgb(0 0 0 / 2%); + outline: none; + width:100%; + padding: 7px 10px; + font-size: 13px; + background: #141414; + color: #c0c0c0; +} +.number { + margin: 0px 2px 0px 2px; +} + +.dropdown-menu { + display: block; + opacity: 0; + visibility: hidden; + position: absolute !important; + border-radius: 30px; + text-align: left; + transition: 0.2s; + background: #181a1b; +} + +.dropdown-menu.show { + display: block; + opacity: 1; + visibility: visible; + position: static; + border-radius: 30px; + text-align: left; + transition: 0.2s; + background: #181a1b; +} + +.dropdown-item{ + padding: .25rem 2rem 0.3rem 1rem; + border-radius: 27px; + transition: 0.3s; + cursor: pointer; + color: #c0c0c0; + width: 0%; + margin-left: 3%; + text-align: left; + font-size: 104%; +} + +#center { + text-align:center; +} + +.dropdown-item:active, +.dropdown-item:hover, +.dropdown-item:focus, +.dropdown-item.now:hover, +.dropdown-item.now:active, +.dropdown-item.now:focus { + font-size: 104%; + background: #47a0ff; + padding: .25rem 2.2rem 0.3rem 1rem; + border-radius: 27px; + color: #181a1b; + text-align: left; + margin-left: 4%; + width: 92%; +} +.dropdown-item:hover > .icon, +.dropdown-item:active > .icon, +.dropdown-item:focus > .icon, +.dropdown-item.now:hover > .icon, +.dropdown-item.now:active > .icon, +.dropdown-item.now:focus > .icon{ + background: #8abfff; +} + +.dropdown-success:hover, +.dropdown-success:active, +.dropdown-success:focus { + background: #5effaf; } +.dropdown-success:hover > .icon, +.dropdown-success:active > .icon, +.dropdown-success:focus > .icon { + background: #1ce681; +} + +.dropdown-error:hover, +.dropdown-error:active, +.dropdown-error:focus { + background: #ff8787; +} +.dropdown-error:hover > .icon, +.dropdown-error:active > .icon, +.dropdown-error:focus > .icon { + background: #f55656; +} + +table, +.btn-group { + border-radius: 20px; +} + +.card { + border-radius: 25px; + display: inline; + background-color: rgb(0 0 0 / 2%); + outline: none; + padding: 5px 10px; + font-size: 17px; + grid-gap: 20px; + background: #181a1b; + color: #c0c0c0; +} + +.menubar { + background-color: #1e2124; + color: #d6ddde; + justify-content: center; +} + +.content { + background-color: transparent; + color: #a7a8aa; + border-radius: 25px; +} + +.fill { + flex: 1; +} + .buffer { + margin-top: 20px; + margin-bottom: 20px; + margin-left: 20px; + margin-right: 20px; +} + +.container-box { + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.login-input { + border-radius: 20px; + background: #141414; +} + +.black-dropdown { + background-color: #e7e7e7; +} + +#lol { + color: white; + justify-content: center; +} + +hr { + background-image: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.2), transparent); + border: 0px; + height: 1px; + margin: 0px; + box-sizing:content-box; + display: block; +} + +#comments, +.comments { + margin: 10px 0px 0px 0px; + overflow-wrap: break-word; + color: #535456; + font-size: 13px; + display: flex; + justify-content: space-between; + text-align: right; + align-items: baseline; + grid-gap: 3px; +} + +.tooactive { + font-weight: 700; +} + +.btn-outline-secondary:enabled { + cursor: pointer; +} + +audio::-webkit-media-controls-panel { + color-scheme: dark !important; + background: #181a1b; + color:white; +} + +.audio { + position: fixed; + left: 4px; + bottom: 45px; + width: calc(100% - 8px); +} + +button > .icon:hover { + background: #47494e !important; + cursor: pointer; +} + +.dropdown-item > .icon:hover, +.dropdown-item.now > .icon:hover { + background: #8abfff !important; + cursor: pointer; +} + +progress { + width: 100%; + margin-top: 13px; + margin-bottom: -8px; + display: block; + background: #141414; + transition: 0.2s; + border-radius: 500px; +} + +progress::-webkit-progress-bar { + background: transparent; + transition: 0.2s; + border-radius: 500px; +} + +progress::-webkit-progress-value { + border-radius: 500px; + background: linear-gradient(-90deg, rgba(106,255,115,1) 0%, rgba(0,255,86,1) 100%); + transition: 0.2s; + padding: 5px; +} + +label { + margin: 0px; +} + +.mainlist { + width: 48%; + padding: 5px 20 20px 20; + border-radius: 47px; +} + +.maindiv { margin-top:20px; + width: 70%; +} + +.secondarydiv { + display: flex; + grid-gap: 10px; +} + +.lilcard { + display: inherit; + border-radius: 30px; + margin: 0px; + flex-wrap: nowrap; + padding: 0 5 15 20; + min-width: 100%; + justify-content: space-between; + height: max-content; + margin-bottom: 0px; + align-items: center; + width:100%; + background: #29282c; + transition: 0.3s; +} + +.lilcard:hover, +.lilcard:active, +.lilcard:focus { + background: #47494e; +} + +.lilcardcontent { + width: 100%; + display: flex; + height: 100%; + flex-wrap: wrap; + flex-direction: column; + justify-content: space-between; +} + +.dropdown-item.big { + padding: 15px; + padding-left: 25px; + border-radius: 500px; + .icon{height: 45px;width: 45px;} +} + +.new-form { + position:relative; + padding-bottom: 20px; + height:70vh; + width: 100%; + margin-top:10px; margin-bottom:20px; - margin-left:20px; - margin-right:20px; + border-radius:45px; + overflow:hidden; + max-height:80vh; + justify-content:flex-start; } -.container-box { + +.new-form-control { + display: inherit; + border-radius: 40px; + margin-top: 15px; + flex-wrap: wrap; + padding: 5px 0px 15px 15px; + overflow-y: scroll; + min-width: 100%; + justify-content: space-between; height: 100%; + margin-bottom: 0px; + align-items: start; + align-content: start; + overflow-x: hidden; +} + +.dropdown-item.now { + background: #141414; + width: 95%; + .icon{background: #101010;} +} + +.new-form-control.songs { + display: grid; + grid-template-columns: repeat(2, 49.5%); +} + +.songidyeah { + font-size: 13px; +} + +.btn-outline-secondary i { + font-size: 20px; +} + +.mainpagecardh1 { + width: max-content; + text-align: left; +} + +.mpp { + margin-bottom: 10px; + width:100%; + text-align:left; +} + +.profileform { + width: 60vw; + max-height: max-content; + position: relative; +} + +.dlemptycard { + height: 23vh; + display: inherit; + border-radius: 30px; + margin-top: 15px; + flex-wrap: nowrap; + padding: 20 0 20 20; + min-width: 100%; + justify-content: space-between; + margin-bottom: 0px; + align-items: center; +} + +.demonlist { + padding-right: 5px; + width: 70vw; + height: 77vh; + margin-top: 10px; + border-radius: 45px; + overflow: scroll; + overflow-x: hidden; + max-height: 80vh; + justify-content: flex-start; + position: relative; +} + +.dlbutton { + position: fixed; + padding: 15px; + width: max-content; + font-size: 21px; + bottom: 6.5vh; +} + +.dlcard { + display: inherit; + border-radius: 30px; + margin-top: 15px; + flex-wrap: nowrap; + padding: 20px; + min-width: 100%; + justify-content: space-between; + height: max-content; + margin-bottom: 0px; + align-items: center; +} + +.dlseconddiv { display: flex; + height: 100%; + flex-wrap: wrap; + flex-direction: column; + justify-content: space-between; +} + +.dltext { + display: inline-flex; + font-size: 25px; + font-weight: 400; + grid-gap: 5px; + flex-wrap: wrap; + align-items: center; +} + +.dlp { + margin-bottom: 10px; + width: calc(100% - 10px); + text-align: left; + overflow-wrap: anywhere; +} + +.dlposttext { + display: inline-flex; + align-items: center; + width: max-content; +} + +.profcard1 { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 7px; + align-items: center; +} + +.profh1 { + text-align:left !important; + margin: 0px !important; + font-size: 30px; + overflow-wrap: anywhere; +} + +.profacclist { + display: flex; + width: 100%; +} + +.acccontrol { + width: 30%; + border-radius: 25px; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; +} + +.acccontrol2 { + display: grid; + grid-template-columns: repeat(2, 50%); + justify-items: center; + align-items: center; + width: 100%; +} + +.songbtnpic { + font-size:13px; + height:25px; + width:25px; + background:#373A3F; + margin: 0px; +} + +.songpic { + display: flex; + align-items: center; + grid-gap: 5px; +} + +.accnamedesc { + width: 70%; + margin-left: 5px; +} + +.songauthor { + font-size: 12.5px; +} + +.songname { + font-size: 17.5px; + color: white; +} + +.acccontrol2 .profilepic, +.acccontrol .profilepic { + margin: 0px; + font-size: 20px; +} + +.msgbox.list, +.dmbox.list { + display: flex; + max-width: 500px; + border-radius: 35px; justify-content: center; + align-content: flex-start; + align-items: center; + padding: 15px 0px 15px 15px; + grid-gap: 10px; + margin: 0px; + margin-bottom: 15px; +} + +.messengerbox h1 { + margin-bottom: 0px; +} + +.message { + display: flex; + width: 100%; + grid-gap: 5px; +} + +.message.you { + justify-content: flex-end; +} + +.message.notyou { + justify-content: flex-start; + align-items: flex-end; +} + +.clanname { + margin: 0px; + margin-top: 0px; + font-weight: 700; + display: flex; + grid-gap: 5px; align-items: center; } -.login-input { - width: 250px; + +.clandesc { + max-height: 90px; + overflow: scroll; + overflow-x: hidden; + padding-left: 15px; } -.black-dropdown { - background-color: #e7e7e7; + +.clanownertext { + margin: 0px; + color: gray; + text-align: right; + margin-bottom: 5px; + font-size: 20px; + margin-top: 5px; +} + +.clanmemberstext { + width: max-content; + margin: 0px; + color: gray; + margin-bottom: 5px; + font-size: 20px; + margin-top: 5px; +} + +.clan-owner-form { + overflow-wrap: anywhere; + display: flex; + border-radius: 30px; + flex-wrap: wrap; + padding-top: 0px; + max-height: 45vh; + padding-bottom: 10px; + min-width: 100%; + height: max-content; + margin-bottom: 5px; + align-items: center; +} + +.clancreatetext { + justify-content: flex-end; + grid-gap: 0.5vh; + font-size: 16px; +} + +.clanmemberndiv { + display: flex; + margin-bottom: 10px; + align-items: center; +} + +.clanmemberndiv h2 { + margin-bottom: 0px; +} + +.secondsettingsform { + width: 70%; +} + +.mainsettings { + display: flex; + grid-gap: 10px; + width: 100%; + margin-bottom: 30px; +} + +.profilewclanname { + display: flex; + flex-direction: column; + align-items: center; + margin: 5px 0px 10px 0px; +} + +.profilename { + margin: 0px !important; +} + +.dlpoints { + position: absolute; + font-size: 20px; + right: 50px; + color: gray; +} + +body:has(.audio) { + .btn-group {bottom: 15px !important; position: fixed !important;} +} + +.draganddrop { + position: absolute; + width: 100vw; + height: 100vh; + background: #00000080; + transition: 0.3s; + color: white; + display: flex; + justify-content: center; + align-items: center; + z-index: 2; +} + +.dadh1 i { + font-size: 150px; + margin-bottom: 15px; + margin-right: 33px; +} + +.dadh1 { + display: flex; + flex-direction: column; +} + +.itemsbox { + border-radius: 30px; + width: 50%; + padding: 0px; + display: flex; + margin: auto; +} + +.itembtn { + padding: 10px; + border-radius: 20px; + height: max-content; + margin: 0px; + width: 100%; + border: none; +} + +.itemoverflow { + border-radius: 30px; + overflow: scroll; + width: 100%; + overflow-x: hidden; + height: 100%; +} + +.acccomments { + display: flex; + justify-content: space-between; + margin-top: 10px; +} + +.acclistnick { + margin: 0px; + font-size: 27px; + margin-left:5px; + display: flex; +} + +.accresultrole { + margin: 0px; + width: 100%; + text-align: right; +} + +.acclistdiv { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; +} + +.clanscard { + display: inherit; + border-radius: 30px; + margin-top: 15px; + flex-wrap: nowrap; + padding: 0px 5px 15px 20px; + min-width: 100%; + justify-content: space-between; + height: max-content; + margin-bottom: 0px; + align-items: center; +} + +.clansname { + display: flex; + justify-content: space-between; + align-items: center; +} + +.clansname h1 { + width: max-content; + text-align: left; + overflow-wrap: anywhere; +} + +.clansdesc { + margin-bottom: 10px; + width:100%; + text-align: left; + overflow-wrap: anywhere; +} + +.clan-form-control { + display: inherit; + border-radius: 45px; + margin-top: 15px; + flex-wrap: wrap; + padding: 0px 0px 15px 15px; + overflow-y: scroll; + overflow-x: hidden; + min-width: 100%; + justify-content: space-between; + height: 100%; + margin-bottom: 0px; + align-items: start; + align-content:start; +} + +.clan-form { + position: relative; + padding-bottom: 20px; + width: 60vw; + height: 77vh; + margin-top: 1.5%; + border-radius: 45px; + overflow: auto; + overflow-x: hidden; + max-height: 80vh; + justify-content: flex-start; +} + +.clansmembercount { + display: flex; + grid-gap: 5px; + align-items: center; +} + +.packcard { + border-radius: 30px; + margin-top: 15px; + background: #1e2124; +} + +.packh2 { + text-align: right; + margin: 15px 5px 5px 0px; + font-style: italic; +} + +.commentsdiv { + display: flex; + justify-content: space-between; + margin-top: 10px; +} + +.packlevels { + height: 225px; + overflow-y: scroll; +} + +.longfc { + display: flex; + width: 100%; + height: max-content; + align-items: center; +} + +.gauntletlevels { + height: 375px; + overflow-y: scroll; + margin-top: 10px; +} + +.suggested { + height: max-content; + border-radius: 33px; + margin-top: 10px; +} + +.suggest .accbtn { + font-size: 30px; +} + +.sugnamedesc { + width: 100%; +} + +.suggest .dltext, +.suggest .dltext b { + font-size: 22px; +} + +.suggest .levelname { + font-size: 30px; +} + +.suggest, +.suggest .dltext { + display: flex; + grid-gap: 5px; + align-items: baseline; + flex-wrap: wrap; +} + +.mavdiv { + width: 100%; + display: flex; + align-items: center; +} + +.mavdiv details { + width: 100%; +} + +.packname p { + margin-bottom: 10px; +} + +.clan-form h1 { + margin: 10px 0px 0px; +} + +.packname h1 { + margin: 0px; + margin-bottom: 5px; + font-weight: 700; +} + +.clan-form.clans h1 { + margin: 15px 0 15px; +} + +.messenger.msgs { + margin-top: 0px; +} + +.mavdiv .accbtn, +.profilepic .accbtn { + font-size: 25px; +} + +.audioDiv { + display: flex; + background: #181a1b; + border-radius: 20px; + position: fixed; + bottom: 45px; + left: 30px; + padding: 10px; + align-items: center; + grid-gap: 10px; + min-width: 350px; + z-index: 2; + transition: 0.2s; +} + +.audioDiv p { + width: max-content; +} + +.audioDiv .name { + font-weight: 700; + font-size: 24px; + color: white; + max-width: calc(100% - 62px); + margin-right: 62px; + text-align: left; +} + +.audioDiv .author { + font-size: 20px; + max-width: calc(100% - 62px); + text-align: left; +} + +.audioDiv .image { + width: 81px; + height: 81px; + aspect-ratio: 1/1; + border-radius: 15px; + position: absolute; + top: 0px; +} + +.audioDiv .track { + display: flex; + flex-direction: column; + line-height: 1; + grid-gap: 7px; + position: relative +} +/* +.audioDiv .length { + margin: 0px; + width: 300px; + background: #121212; + color: white; + border-radius: 500px; + height: 13px; +} +*/ +.audioDiv .cover { + position: relative; + width: 81px; + height: 81px; + cursor: pointer +} + +.audioDiv .cover i { + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + color: white; + background: #0000006b; + font-size: 50px; + opacity: 0; + transition: 0.25s; +} + +.audioDiv .cover:hover i { + opacity: 1; +} + +.audioDiv .length { + -webkit-appearance: none; + height: 10px; + background: #121212; + border-radius: 500px; + background-image: linear-gradient(#c0c0c0, #c0c0c0); + background-size: 0% 100%; + background-repeat: no-repeat; + min-width: 300px; + width: 100%; +} + +.audioDiv .length::-webkit-slider-runnable-track { + -webkit-appearance: none; + box-shadow: none; + border: none; + background: transparent; +} + +.audioDiv .length.volume { + rotate: 270deg; + margin: 0px; + position: absolute; + bottom: 30px; + right: -22px; + min-width: 70px; + width: 100%; +} + +.audioDiv i.volume { + color: white; + font-size: 17px; + transition: 0.2s; + position: absolute; + bottom: 0px; + opacity: 0; + visibility: hidden; +} + +.audioDiv i.volume.show { + opacity: 1; + visibility: initial; +} + +.audioDiv .volumeDiv { + display: flex; + position: relative; + height: 75px; + width: 25px; +} + +.audioDiv .duration { + display: flex; + font-size: 18px; + align-items: center; + grid-gap: 5px; +} + +.audioDiv .duration button:disabled { + color: gray; +} + +.audioDiv .queueDiv { + position: absolute; + right: 0px; + top: auto; + bottom: 104px; + left: 0px; + width: max-content; + display: flex; + flex-direction: column-reverse; + overflow-y: auto; + overflow-x: hidden; + max-height: 205px; + opacity: 0; + visibility: hidden; + transition: 0.2s; +} + +.queueDiv .item { + display: flex; + width: 100%; + align-items: center; + grid-gap: 17px; +} + +.queueDiv .cover, .queueDiv .image{ + width: 55px; + height: 55px; +} + +.queueDiv .cover i { + font-size: 30px; +} + +.audioDiv .anotherVolume { + opacity: 0; + visibility: hidden; + transition: 0.2s; +} + +.audioDiv .anotherVolume.show { + opacity: 1; + visibility: initial; +} + +.queueDiv .name { + font-size: 21px; +} + +.queueDiv .author { + font-size: 17px; +} + +.queueDiv:has(.item) { + opacity: 1; + visibility: initial; +} + +.audioDiv button i { + cursor: pointer; +} + +.audioDiv .indicator { + display: none; +} + +.audioDiv .track .buttons { + position: absolute; + top: 1px; + right: 8px; + display: flex; + grid-gap: 10px; +} + +.audioDiv .track .buttons button, +.queueDiv .track .buttons { + background: transparent; + border: none; + outline: none; + padding: 0px; + font-size: 18px; +} + +.queueDiv .track { + width: 100%; +} + +.queueDiv .name { + margin: 0px; +} + +.audioDiv.showButton { + width: max-content; + border-radius: 500px; + color: white; + min-width: 0px; + padding: 27px; + z-index: 3; +} + +.audioDiv.showButton i { + font-size: 48px; +} + +.audioDiv.showButton:not(.show) { + opacity: 1; + visibility: initial; + animation-name: button; + animation-duration: 1.5s; + animation-iteration-count: initial; +} + +.audioDiv.showButton.show { + opacity: 0; + visibility: hidden; +} + +.audioDiv:not(.show):not(.showButton):not(.queueDiv) { + opacity: 0; + visibility: hidden; + animation-name: player; + animation-duration: 1.5s; + animation-iteration-count: initial; +} + +.songUploadDiv { + width: 100%; + padding: 0px 10px 10px 10px; + background: #141414; + border-radius: 30px; + min-width: 35vw; +} + +.forgotPassword { + width: 100%; + text-align: right; + background: none; + border: none; + padding: 0px; + margin: 5px 0px; + cursor: pointer; + color: #c0c0c0; + font-weight: 700; + transition: 0.2s; + outline: none; + font-size: 15px; +} + +.forgotPassword:hover { + text-decoration: underline; +} + +.itembtn:disabled { + padding: 10px; + border-radius: 20px; + height: max-content; + margin: 0px; + margin-right: 10px; + width: 100%; + border: none; + background: #231717 +} + +@keyframes player { + 0% { + opacity: 1; + visibility: initial; + } + 70% { + opacity: 1; + visibility: initial; + } + 100% { + opacity: 0; + visibility: hidden; + } +} + +@keyframes button { + 0% { + opacity: 0; + visibility: hidden; + } + 70% { + opacity: 0; + visibility: hidden; + } + 100% { + opacity: 1; + visibility: initial; + } +} + +.audioDiv.showButton:not(:has(.indicator)) { + opacity: 0; + visibility: hidden; + animation-duration: 0s; +} + +@keyframes fadeInAnimation { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.icon-kit { + display: flex; + width: 100%; + height: max-content; + justify-content: space-between; + margin-top:10px; + padding: 5px 5%; + align-items: center; + border-radius: 20px; +} + +.profile-stats { + display: flex; + width: 100%; + height: max-content; + align-items: center; +} + +.profile-comments { + overflow-wrap: anywhere; + display: flex; + border-radius: 30px; + margin-top: 20px; + flex-wrap: wrap; + padding-top: 0; + max-height: 45vh; + padding-bottom: 10px; + min-width: 100%; + height: max-content; + margin-bottom: 17px; + align-items: center; +} + +.icon-kit-icon { + animation: fadeInAnimation ease 1s; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} + +.icon-kit-div { + width: 100%; + display: flex; + align-content: center; + justify-content: center; +} + +.icon-ship { + height: 45px; +} + +.icon-wave { + height: 40px; +} + +.icon-cube, +.icon-ball, +.icon-ufo, +.icon-robot, +.icon-spider, +.icon-swing, +.icon-jetpack { + height: 60px; +} + +.selectField { + display: flex; + grid-gap: 5px; +} + +.selectField select { + width: 60%; + margin: 0px; +} + +.bantitle { + display: flex; + width: 100%; + justify-content: space-between; + margin-bottom: 7px; + align-items: center; + grid-gap: 5px; + flex-direction: column; +} + +.banusernamediv { + flex-direction: row; + justify-content: center; +} + +.banusernamediv button .profilenick, +.banusernamediv .profilenick { + width: max-content; +} + +.banbuttons { + min-width: 250px; + display: flex; + flex-direction: column; + grid-gap: 10px; + align-items: center; +} + +.modactionsspoiler { + font-size: 25px; +} + +.accounts-badge-icon-div { + display: flex; + align-items: center; + grid-gap: 5px; +} + +.chatdiv { + height: 75vh; + flex-wrap: nowrap !important; +} + +.chatbox { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + justify-content: center; +} + +.messenger-username { + display: flex; + margin-bottom: 5px; + align-items: center; + justify-content: center; + grid-gap: 10px; +} + +.messenger-username .goback, +.messenger-username .msgupd { + margin: 0px; +} + +.chat-opened { + background-color: #313539; + border: 2px solid #6e757b; + padding: 8px; +} + +.friends-button-div { + position: relative; + min-width: 30%; + width: 30%; +} + +.friends-button { + position: absolute; + bottom: 10px; + width: max-content; + padding: 15px; + right: 10px; + font-size: 20px; + background-color: #141414; + border: 4px solid #212529; +} + +.itemslist.hide { + display: none; +} + +.page-buttons { + display: flex; + height: max-content; + width: 100%; +} + +.form.new-form h1 { + margin-bottom: 5px; +} + +.manage-button-div { + display: flex; + align-items: flex-end; + grid-gap: 5px; +} + +.btn-manage { + width: 35px; + color: white; + padding: 7px; + font-size: 20px; + height: 35px; + text-align: center; +} + +.profilenick.big { + font-size: 27px; + grid-gap: 10px; + display: flex; + align-items: center; + cursor: pointer; +} + +.profilelikes { + text-align: right; +} + +.profilelikes.big { + font-size: 25px; + width: max-content; +} + +.profilemsg.big { + font-size: 22px; +} + +.comments.big { + font-size: 15px; + margin-left: 5px; +} + +.profile.big { + display: flex; + flex-direction: column; + grid-gap: 5px; +} + +.level-card { + overflow: visible; + height: max-content; + padding: 5px 15px 15px 15px; +} + +.goback-title { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 5px; + grid-gap: 10px; +} + +.no-margin { + margin: 0px; +} + +.delete-comment-div { + width: 100%; + display: flex; + justify-content: flex-end; + grid-gap: 10px; + align-items: center; +} + +.empty-section { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + flex-direction: column; + grid-gap: 10px; +} + +.empty-section i { + font-size: 100px; + color: #c0c0c0; +} + +.empty-section p { + font-size: 30px; + color: #c0c0c0; +} + +.checkboxes { + display: grid !important; + align-items: center; + justify-content: center; + width: 100%; + flex-direction: column; + grid-gap: 5px; + grid-template-columns: repeat(2, 49.5%); +} + +.checkbox input { + width: 20px; + height: 20px; +} + +.checkbox h3 { + margin: 0px; + font-size: 17px; + text-align: left; +} + +.manage-level-name-div { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + grid-gap: 5px; +} + +.chat { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.empty-itemoverflow { + display: flex; + align-items: center; + justify-content: center; + padding-left: 15px; +} + +.messenger-link { + font-weight: 700; + color: #007bff; + cursor: pointer; +} + +.automod-boxes { + display: flex; + width: 100%; + justify-content: center; + grid-gap: 10px; + margin-top: 10px; + height: calc(100% - 68px); +} + +.automod-boxes .new-form-control { + min-width: 45%; + margin: 0px; +} + +.new-form-control .profile { + border-radius: 25px; +} + +.automod-boxes .profile { + display: flex; + flex-direction: column; + grid-gap: 5px; +} + +.automod-boxes .btns { + margin-top: 5px; +} + +.no-scroll { + overflow: hidden !important; + max-height: max-content !important; + padding: 0px !important; +} + +.attribute-div { + background: #373A3F; + border-radius: 20px; + font-size: 25px; + padding: 7px 15px; + color: white; + width: 100%; + display: flex; + grid-gap: 5px; + align-items: center; +} + +.attribute-divs { + display: flex; + grid-gap: 5px; + background: #141414; + padding: 10px; + border-radius: 25px; +} + +.attribute-div.attribute-button { + width: max-content; + transition: 0.2s; + cursor: pointer +} + +.attributes { + display: flex; + flex-direction: column; + grid-gap: 5px; + padding: 10px !important; + background: #373A3F; + border-radius: 30px; +} + +.attribute-divs.text text { + font-size: 20px; +} + +.attribute-div.enabled { + color: #bbffbb; + font-weight: 700; +} + +.attribute-div.disabled { + color: #ffbbbb; + font-weight: 700; +} + +.attribute-div.attribute-button:hover { + background: #47494e; +} + +.invisible { + width: 0px; + height: 0px; + border: none; + background: none; + position: absolute; + opacity: 0; +} + +.profilepercent { + font-size: 20px; + font-weight: 400; + margin-left: -5px; + color: gray; +} + +.song-info { + flex-wrap: wrap !important; + justify-content: space-evenly !important; + border-radius: 20px; +} + +.song-info .profilepic { + width: max-content; +} + +.new-messages-notify { + background: #e35151; + border-radius: 500px; + padding: 2px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + word-wrap: break-word; + font-size: 13px; + font-weight: 400; + color: white; +} + +.new-messages-notify.smaller { + width: 17px; + height: 17px; +} + +.btn-circle { + width: max-content; + height: max-content; + color: white; + padding: 8px; + font-size: 13px; + background-color: #373A3F; + font-weight: 700; + font-size: 15px; + border: none; + border-radius: 500px; + transition: 0.3s; + cursor: pointer; +} + +.btn-circle:hover, +.btn-circle:active, +.btn-circle:focus { + background-color: #47494e; + text-decoration: none; +} + +.active-profile-pic { + background: #c0c0c0; + color: #141414; + padding: 2px 6px; + border-radius: 15px; + width: max-content; +} + +.cards-form { + margin: 0px; + width: 150%; +} + +.error-divs { + display: flex; + position: fixed; + bottom: 50px; + right: 10px; + flex-direction: column; + grid-gap: 10px; +} + +.notify-show { + opacity: 1; +} + +.rewards-div { + display: flex; + flex-direction: column; + grid-gap: 10px; + justify-content: flex-start !important; + overflow-y: scroll; + position: absolute; + padding: 15px 0px 15px 15px; + margin: 20px; + width: 96% !important; + background: #181a1bdb; + height: calc(100% - 20px); + top: -10px; + border-radius: 20px; + backdrop-filter: blur(3px); + pointer-events: none; + opacity: 0; + transition: 0.3s; +} + +.rewards-div.show { + pointer-events: initial; + opacity: 1; +} + +.rewards-div h1 { + margin: 0px; +} + +.h1-with-close { + display: flex; + justify-content: space-between; + position: relative; +} + +.only-icon-button { + background: none; + border: none; + font-size: 25px; + cursor: pointer; + border-radius: 1000px; +} + +.comments i { + color: #c0c0c0; } \ No newline at end of file diff --git a/dashboard/incl/dashboardLib.php b/dashboard/incl/dashboardLib.php index 799064fe5..2b46d14b0 100644 --- a/dashboard/incl/dashboardLib.php +++ b/dashboard/incl/dashboardLib.php @@ -1,28 +1,63 @@ auth($dbPath); +// Dashboard library +class dashboardLib { public function printHeader($isSubdirectory = true){ $this->handleLangStart(); + global $gdps; + global $dashboardFavicon; + if(file_exists("../../incl/cvolton.css")) $css = filemtime("../../incl/cvolton.css"); + elseif(file_exists("../incl/cvolton.css")) $css = filemtime("../incl/cvolton.css"); + else $css = filemtime("incl/cvolton.css"); echo ' - '; - if($isSubdirectory){ - echo ''; + + + + + '; + if($isSubdirectory) echo ''; else echo ''; + echo ' + + + + + + + + + + + + '.$gdps.''; + echo ' +
'; + } + public function getLocalizedString($stringName, $lang = '') { + if(empty($lang)) { + if(!isset($_COOKIE["lang"]) OR !ctype_alpha($_COOKIE["lang"])) { + if(file_exists(__DIR__.'/lang/locale'.strtoupper(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2))).'.php') $lang = strtoupper(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2)); + else $lang = "EN"; + } else $lang = $_COOKIE["lang"]; + } + $lang = substr($lang, 0, 2); + $locale = __DIR__."/lang/locale".$lang.".php"; + if(file_exists($locale)) require $locale; + else require __DIR__."/lang/localeEN.php"; + if(isset($string[$stringName])) return $string[$stringName]; + else { + require __DIR__."/lang/localeEN.php"; + if(isset($string[$stringName])) return $string[$stringName]; + else return "lnf:$stringName"; } - echo ' - - - - - - - [Beta] GDPS Dashboard - '; - echo ' - '; } public function printBoxBody(){ - echo '
+ echo '
'; } @@ -30,287 +65,1454 @@ public function printBox($content, $active = "", $isSubdirectory = true){ $this->printHeader($isSubdirectory); $this->printNavbar($active); $this->printBoxBody(); - echo "$content"; + echo $content; $this->printBoxFooter(); $this->printFooter(); } + public function printSong($content, $active = "", $isSubdirectory = true){ + $this->printHeader($isSubdirectory); + $this->printNavbar($active, $isSubdirectory); + echo ''.$content.''; + } public function printBoxFooter(){ - echo '
'; + echo '
'; } - public function printFooter(){ - echo ' - '; + public function printFooter($sub = ''){ + global $dbPath; + global $vk; + global $discord; + global $twitter; + global $youtube; + global $twitch; + echo '
'; } public function printLoginBox($content){ - $this->printBox("

Login

".$content); + $this->printBox("

".$this->getLocalizedString("loginBox")."

".$content); } public function printLoginBoxInvalid(){ - $this->printLoginBox("

Invalid username or password. Click here to try again."); + $this->printLoginBox("

".$this->getLocalizedString("wrongNickOrPass").""); } public function printLoginBoxError($content){ $this->printLoginBox("

An error has occured: $content. Click here to try again."); } - public function printNavbar($active){ - require_once __DIR__."/../../incl/lib/mainLib.php"; + public function printNavbar($active, $isSubdirectory = true) { + global $gdps; + global $lrEnabled; + global $msgEnabled; + global $songEnabled; + global $sfxEnabled; + global $clansEnabled; + global $pc; + global $mac; + global $android; + global $ios; + global $pcLauncher; + global $macLauncher; + global $androidLauncher; + global $iosLauncher; + global $thirdParty; + global $dbPath; + global $dashboardIcon; + require_once __DIR__."/../".$dbPath."incl/lib/Captcha.php"; + require __DIR__."/../".$dbPath."config/security.php"; + require __DIR__."/../".$dbPath."config/mail.php"; + require_once __DIR__."/../".$dbPath."incl/lib/mainLib.php"; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + if(!isset($enableCaptcha)) global $enableCaptcha; + if(!isset($preactivateAccounts)) global $preactivateAccounts; + if($enableCaptcha) { + $captchaTypes = ['hcaptcha', 'grecaptcha', 'turnstile']; + $captchaUsed = $captchaTypes[$captchaType-1]; + } $gs = new mainLib(); - $homeActive = ""; - $accountActive = ""; - $modActive = ""; - $reuploadActive = ""; - $statsActive = ""; - switch($active){ + $homeActive = $accountActive = $browseActive = $modActive = $reuploadActive = $statsActive = $msgActive = $profileActive = ""; + switch($active) { case "home": - $homeActive = "active"; + $homeActive = "active tooactive"; break; case "account": - $accountActive = "active"; + $accountActive = "active tooactive"; + break; + case "browse": + $browseActive = "active tooactive"; break; case "mod": - $modActive = "active"; + $modActive = "active tooactive"; break; case "reupload": - $reuploadActive = "active"; + $reuploadActive = "active tooactive"; break; case "stats": - $statsActive = "active"; + $statsActive = "active tooactive"; + break; + case "msg": + $msgActive = "active tooactive"; + break; + case "profile": + $profileActive = "active tooactive"; break; } - echo '

'; + echo ''; + if($gs->checkPermission($_SESSION["accountID"], "dashboardModTools")) { echo ''; } }else{ - echo $browse . ""; + echo $browse.""; } - echo ' + echo ' - + + +
+
+
+
+ +
- '; +
+

'.$this->getLocalizedString("songAddNameFieldPlaceholder").'

+

'.$this->getLocalizedString("songAddAuthorFieldPlaceholder").'

+
+ + + +
+
+ + +
+ +
+
+ +
+
+
+ +
+
+'; + if((date("F", time()) == "December" AND date("j", time()) > 17) OR (date("F", time()) == "January" AND date("j", time()) < 10)) echo ""; } public function printPage($content, $isSubdirectory = true, $navbar = "home"){ - $dl = new dashboardLib(); - $dl->printHeader($isSubdirectory); - $dl->printNavbar($navbar); - echo '
+ $this->printHeader($isSubdirectory); + $this->printNavbar($navbar); + echo '
'.$content.'
-
'; - $dl->printFooter(); +
'; } - public function handleLangStart(){ + public function handleLangStart() { if(!isset($_COOKIE["lang"]) OR !ctype_alpha($_COOKIE["lang"])){ - setcookie("lang", "EN", 2147483647, "/"); + if(file_exists('/lang/locale'.strtoupper(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2))).'.php') setcookie("lang", strtoupper(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2)), 2147483647, "/"); + else setcookie("lang", "EN", 2147483647, "/"); } + if(!isset($_SESSION["accountID"])) $_SESSION["accountID"] = 0; } - public function getLocalizedString($stringName){ - if(!isset($_COOKIE["lang"]) OR !ctype_alpha($_COOKIE["lang"])){ - $lang = "EN"; - }else{ - $lang = $_COOKIE["lang"]; + public function hex2RGB($hexStr, $returnAsString = false, $seperator = ',') { + $hexStr = preg_replace("/[^0-9A-Fa-f]/", '', $hexStr); + $rgbArray = []; + if(strlen($hexStr) == 6) { + $colorVal = hexdec($hexStr); + $rgbArray['red'] = 0xFF & ($colorVal >> 0x10); + $rgbArray['green'] = 0xFF & ($colorVal >> 0x8); + $rgbArray['blue'] = 0xFF & $colorVal; + } elseif (strlen($hexStr) == 3) { + $rgbArray['red'] = hexdec(str_repeat(substr($hexStr, 0, 1), 2)); + $rgbArray['green'] = hexdec(str_repeat(substr($hexStr, 1, 1), 2)); + $rgbArray['blue'] = hexdec(str_repeat(substr($hexStr, 2, 1), 2)); + } else return false; + return $returnAsString ? implode($seperator, $rgbArray) : $rgbArray; + } + public function convertToDate($timestamp, $acc = false){ + if($acc) { + if($timestamp == 0) return $this->getLocalizedString("never"); + if(date("d.m.Y", $timestamp) == date("d.m.Y", time())) return date("G:i", $timestamp); + elseif(date("Y", $timestamp) == date("Y", time())) return date("d.m", $timestamp); + else return date("d.m.Y", $timestamp); + } else return date("d.m.Y G:i:s", $timestamp); + } + public function createProfileStats($stars = 0, $moons = 0, $diamonds = 0, $goldCoins = 0, $userCoins = 0, $demons = 0, $creatorPoints = 0, $isCreatorBanned = 0, $returnText = true) { + if($stars == 0) $st = ''; else $st = '

'.$stars.'

'; + if($moons == 0) $ms = ''; else $ms = '

'.$moons.'

'; + if($diamonds == 0) $dm = ''; else $dm = '

'.$diamonds.'

'; + if($goldCoins == 0) $gc = ''; else $gc = '

'.$goldCoins.'

'; + if($userCoins == 0) $uc = ''; else $uc = '

'.$userCoins.'

'; + if($demons == 0) $dn = ''; else $dn = '

'.$demons.'

'; + if($creatorPoints == 0) $cp = ''; else $cp = '

'.$creatorPoints.'

'; + $all = $st.$ms.$dm.$gc.$uc.$dn.$cp; + if(empty($all) && $returnText) $all = '

'.$this->getLocalizedString("empty").'

'; + return $all; + } + public function generateLevelsCard($action, $modcheck = false, $extraDetails = '') { + global $dbPath; + global $iconsRendererServer; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + require_once __DIR__."/../".$dbPath."incl/lib/mainLib.php"; + $gs = new mainLib(); + $levelid = $action["levelID"]; + $levelname = $action["levelName"]; + $levelIDlol = ''; + $levelDesc = $this->parseMessage(htmlspecialchars(ExploitPatch::url_base64_decode($action["levelDesc"]))); + if(empty($levelDesc)) $levelDesc = ''.$this->getLocalizedString("noDesc").''; + $levelpass = $action["password"]; + $likes = ''.$action["likes"].''; + $dislikes = ''.$action["dislikes"].''; + $stats = '
'.$likes.' '.(isset($action['dislikes']) ? ' '.$dislikes : '').'
'; + if($modcheck) { + $levelpass = substr($levelpass, 1); + $levelpass = preg_replace('/(0)\1+/', '', $levelpass); + if($levelpass == 0 OR empty($levelpass)) $lp = '

'.$this->getLocalizedString("nopass").'

'; + else { + if(strlen($levelpass) < 4) while(strlen($levelpass) < 4) $levelpass = '0'.$levelpass; + $lp = '

'.$levelpass.'

'; + } + if($action["requestedStars"] <= 0 && $action["requestedStars"] > 10) $rs = '

0

'; + else $rs = '

'.$action["requestedStars"].'

'; + } else $lp = $rs = ''; + if($action["songID"] > 0) { + $songlol = $gs->getSongInfo($action["songID"]); + $songArtists = $gs->getLibrarySongInfo($action["songID"])["artists"]; + $artistIDs = preg_split('/\./', $songArtists); + $artistIDs = array_filter($artistIDs); + $artistIDsString = implode(', ', $artistIDs); + $authorNames = [$songlol["authorName"]]; + foreach($artistIDs as $id) { + $authorInfo = $gs->getLibrarySongAuthorInfo($id); + $authorNames[] = $authorInfo["name"]; + } + $artistNames = implode(', ', $authorNames); + $btn = ''; + $songid = '
'.$btn.'
'.($artistNames ? $artistNames : $songlol["authorName"]).'
'.$songlol["name"].'
'; + } else $songid = '

'.strstr($gs->getAudioTrack($action["audioTrack"]), ' by ', true).'

'; + $username = '
'; + $time = $this->convertToDate($action["uploadDate"], true); + $diff = $gs->getDifficulty($action["starDifficulty"], $action["auto"], $action["starDemonDiff"]); + if($modcheck) { + $stars = ''; } - $locale = __DIR__ . "/lang/locale".$lang.".php"; - if(file_exists($locale)){ - include $locale; - }else{ - include __DIR__ . "/lang/localeEN.php"; + if($action['levelLength'] == 5) $starIcon = 'moon'; else $starIcon = 'star'; + if(!empty($stars)) $st = '

'.$diff.', '.$action["starStars"].'

'.$stars; + else $st = '

'.$diff.', '.$action["starStars"].'

'; + $ln = '

'.$gs->getLength($action['levelLength']).'

'; + $dls = '

'.$action['downloads'].'

'; + $all = $dls.$stats.$st.$ln.$lp.$rs.$extraDetails; + // Avatar management + $avatarImg = ''; + $extIDvalue = $action['extID']; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $extIDvalue]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; } - if($lang == "TEST"){ - return "lnf:$stringName"; + $manage = ' + '; + return '
+
+
+
+
+

'.$manage.sprintf($this->getLocalizedString("demonlistLevel"), $levelname.'
', 0, $action["userName"], $avatarImg).'

+
+

'.$levelDesc.'

+
+
+
+ '.$all.' +
+ '.$songid.' +
+
+

'.$this->getLocalizedString("levelid").': '.$levelIDlol.'

'.$this->getLocalizedString("date").': '.$time.'

+
'; + } + public function generateCommentsCard($comment, $commentDeleteCheck = false) { + global $dbPath; + global $iconsRendererServer; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + require_once __DIR__."/../".$dbPath."incl/lib/mainLib.php"; + $gs = new mainLib(); + $commentAccountName = $gs->getUserName($comment['userID']); + $commentMessage = $this->parseMessage(htmlspecialchars(ExploitPatch::url_base64_decode($comment["comment"]))); + $extIDvalue = $gs->getExtID($comment['userID']); + $likes = ''.$comment["likes"].''; + $dislikes = ''.$comment["dislikes"].''; + $stats = ' '.$likes.' '.(isset($comment['dislikes']) ? ' '.$dislikes : ''); + $commentIDDiv = ''; + if($commentDeleteCheck || $extIDvalue == $_SESSION['accountID']) $deleteComment = ''; + $percentText = $comment['percent'] > 0 ? ''.$comment['percent'].'%' : ''; + // Avatar management + $avatarImg = ''; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute([':extID' => $extIDvalue]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = isset($iconTypeMap[$iconType]) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; } - if(isset($string[$stringName])){ - return $string[$stringName]; - }else{ - return "lnf:$stringName"; + // Badge management + $badgeImg = ''; + $queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); + $queryRoleID->execute([':accountID' => $extIDvalue]); + if($roleAssignData = $queryRoleID->fetch(PDO::FETCH_ASSOC)) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } } + // Color management + $queryColorLevel = $gs->getAccountCommentColor($extIDvalue); + return '
+
+
+

'.$avatarImg.$commentAccountName.$badgeImg.$percentText.'

+
'.$deleteComment.'

'.$stats.'

+
+

'.$commentMessage.'

+

+ '.$this->getLocalizedString("ID").': '.$commentIDDiv.' + '.$this->convertToDate($comment['timestamp'], true).' +

+
+
'; } - public function convertToDate($timestamp){ - return date("d/m/Y G:i:s", $timestamp); + public function generateLeaderboardsCard($x, $leaderboard, $action, $leaderboardDeleteCheck = false, $stats = '') { + global $dbPath; + global $iconsRendererServer; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + require_once __DIR__."/../".$dbPath."incl/lib/mainLib.php"; + $gs = new mainLib(); + switch($x) { + case 1: + $place = ' 1'; + break; + case 2: + $place = ' 2'; + break; + case 3: + $place = ' 3'; + break; + default: + $place = '# '.$x.''; + break; + } + if($leaderboardDeleteCheck) $deleteLeaderboard = ''; + // Avatar management + $avatarImg = ''; + $extIDvalue = $action['extID']; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $extIDvalue]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = isset($iconTypeMap[$iconType]) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; + } + return '
+
+ '.$deleteLeaderboard.' +
+
'.$stats.'
+

'.$this->getLocalizedString("accountID").': '.$extIDvalue.'

'.$this->getLocalizedString("date").': '.$this->convertToDate($leaderboard['uploadDate'], true).'

+
'; } - public function generateBottomRow($pagecount, $actualpage){ + public function parseMessage($body) { + global $dbPath; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + require_once __DIR__."/../".$dbPath."incl/lib/exploitPatch.php"; + $parseBody = explode(' ', $body); + $playersFound = $levelsFound = []; + foreach($parseBody AS &$element) { + $firstChar = mb_substr($element, 0, 1); + if(!in_array($firstChar, ['@', '#'])) continue; + $element = mb_substr($element, 1); + switch($firstChar) { + case '@': + if($playersFound[$element]) break; + $element = ExploitPatch::charclean($element); + $check = $db->prepare('SELECT count(*) FROM accounts WHERE userName = :userName AND isActive != 0'); + $check->execute([':userName' => $element]); + $check = $check->fetchColumn(); + if($check) { + $body = str_replace('@'.$element, '@'.$element.'', $body); + $playersFound[$element] = true; + } + break; + case '#': + if(!is_numeric($element) || $levelsFound[$element]) break; + $check = $db->prepare('SELECT levelName FROM levels WHERE levelID = :levelID AND unlisted = 0 AND unlisted2 = 0'); + $check->execute([':levelID' => $element]); + $check = $check->fetchColumn(); + if($check) { + $body = str_replace('#'.$element, '#'.htmlspecialchars($check).'', $body); + $levelsFound[$element] = true; + } + break; + } + } + return $body; + } + public function generateSongCard($song, $extraLabels = '', $includeAuthor = true) { + global $dbPath; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + require_once __DIR__."/../".$dbPath."incl/lib/mainLib.php"; + $gs = new mainLib(); + $fontsize = 27; + $modCheck = $gs->checkPermission($_SESSION["accountID"], "dashboardManageSongs"); + $check = $modCheck ?: ($_SESSION['accountID'] != 0 && $_SESSION['accountID'] == $song['reuploadID']); + $songsid = $song["ID"]; + $songIDlol = ''; + $time = $this->convertToDate($song["reuploadTime"], true); + $who = ''; + $author = htmlspecialchars(ExploitPatch::rucharclean($song["authorName"])); + $name = htmlspecialchars(ExploitPatch::rucharclean($song["name"])); + $size = $song["size"]; + $download = str_replace('http://', 'https://', $song["download"]); + if($_SESSION["accountID"] != 0) { + $favourites = $db->prepare("SELECT * FROM favsongs WHERE songID = :id AND accountID = :aid"); + $favourites->execute([':id' => $songsid, ':aid' => $_SESSION["accountID"]]); + $favourites = $favourites->fetch(); + if(!empty($favourites)) $favs = ''; + else $favs = ''; + } + if($song["reuploadID"] == 0) { + $time = "
Newgrounds
"; + $who = "".$author.""; + $btn = ''; + } else $btn = ''; + $isDisabled = $song['isDisabled'] != 0; + if($check) $manage = ''; + if(mb_strlen($author) + mb_strlen($name) > 30) $fontsize = 17; + elseif(mb_strlen($author) + mb_strlen($name) > 20) $fontsize = 20; + $songSize = '

'.$song["size"].' MB

'; + if($includeAuthor) $who = '

'.$who.'

'; + else { + $isDisabledText = !$isDisabled ? $this->getLocalizedString('songIsAvailable') : $this->getLocalizedString('songIsDisabled'); + $isDisabledIcon = !$isDisabled ? 'check' : 'xmark'; + $who = '

'.$isDisabledText.'

'; + } + $stats = $songSize.$who.$extraLabels; + return '
+
+

'.$author.' — '.$name.''.$btn.'

'.$favs.$manage.' +
+
'.$stats.'
+

'.$this->getLocalizedString("songIDw").': '.$songIDlol.'

'.$this->getLocalizedString("date").': '.$time.'

+
'; + } + public function generateSFXCard($sfx, $extraLabels = '', $includeAuthor = true) { + global $dbPath; + require __DIR__."/../".$dbPath."incl/lib/connection.php"; + require_once __DIR__."/../".$dbPath."incl/lib/mainLib.php"; + $gs = new mainLib(); + $fontsize = 27; + $modCheck = $gs->checkPermission($_SESSION["accountID"], "dashboardManageSongs"); + $check = $modCheck ?: ($_SESSION['accountID'] != 0 && $_SESSION['accountID'] == $song['reuploadID']); + $sfxsid = $sfx["ID"]; + $songIDlol = ''; + $time = $this->convertToDate($sfx["reuploadTime"], true); + $who = ''; + $author = htmlspecialchars($sfx["authorName"]); + $name = htmlspecialchars($sfx["name"]); + $size = round($sfx["size"] / 1024 / 1024, 2); + $download = str_replace('http://', 'https://', $sfx["download"]); + $btn = ''; + $isDisabled = $sfx['isDisabled'] != 0; + if($check) $manage = ''; + if(mb_strlen($name) > 30) $fontsize = 17; + elseif(mb_strlen($name) > 20) $fontsize = 20; + $songSize = '

'.$size.' MB

'; + if($includeAuthor) $who = '

'.$who.'

'; + else { + $isDisabledText = !$isDisabled ? $this->getLocalizedString('songIsAvailable') : $this->getLocalizedString('songIsDisabled'); + $isDisabledIcon = !$isDisabled ? 'check' : 'xmark'; + $who = '

'.$isDisabledText.'

'; + } + $stats = $songSize.$who.$extraLabels; + return '
+
+

'.$name.''.$btn.'

'.$favs.$manage.' +
+
'.$stats.'
+

'.$this->getLocalizedString("sfxID").': '.$songIDlol.'

'.$this->getLocalizedString("date").': '.$time.'

+
'; + } + public function generateBottomRow($pagecount, $actualpage) { $pageminus = $actualpage - 1; $pageplus = $actualpage + 1; - $bottomrow = '
'.sprintf($this->getLocalizedString("pageInfo"),$actualpage,$pagecount).'
'; - $bottomrow .= ' '.$this->getLocalizedString("first").''; - //updated to ".." - $bottomrow .= ' - + ', 'browse'); +} +?> \ No newline at end of file diff --git a/dashboard/levels/packCreate.php b/dashboard/levels/packCreate.php new file mode 100644 index 000000000..412f9c3b6 --- /dev/null +++ b/dashboard/levels/packCreate.php @@ -0,0 +1,252 @@ +title($dl->getLocalizedString("packCreateTitle")); +$dl->printFooter('../'); +$allPacks = ''; +if($gs->checkPermission($_SESSION["accountID"], "dashboardLevelPackCreate")){ +if(!empty($_POST["packName"])) { + if(!Captcha::validateCaptcha()) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'mod'); + die(); + } + $accountID = $_SESSION["accountID"]; + if($_POST['level_1'] == $_POST['level_3'] OR $_POST['level_3'] == $_POST['level_2'] OR $_POST['level_1'] == $_POST['level_2']) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("sameLevels").'

+ +
+
', 'mod'); + die(); + } + $name = ExploitPatch::rucharclean($_POST["packName"]); + $color = ExploitPatch::remove($dl->hex2RGB($_POST["color"], true)); + $stars = ExploitPatch::number($_POST["stars"]); + $coins = ExploitPatch::number($_POST["coins"]); + if(!is_numeric($_POST['level_1']) OR !is_numeric($_POST['level_2']) OR !is_numeric($_POST['level_3']) OR $stars > 10 OR $stars < 0 OR $coins > 2 OR $coins < 0 OR !$gs->getLevelName(ExploitPatch::remove($_POST['level_1'])) OR !$gs->getLevelName(ExploitPatch::remove($_POST['level_2'])) OR !$gs->getLevelName(ExploitPatch::remove($_POST['level_3']))) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidPost").'

+ +
+
', 'mod'); + die(); + } + $levels = ExploitPatch::number($_POST['level_1']) . ',' . ExploitPatch::number($_POST['level_2']) . ',' . ExploitPatch::number($_POST['level_3']); + switch($stars) { + case 1: + $diff = 0; + break; + case 2: + $diff = 1; + break; + case 3: + $diff = 2; + break; + case 4: + case 5: + $diff = 3; + break; + case 6: + case 7: + $diff = 4; + break; + case 8: + case 9: + $diff = 5; + break; + case 10: + $diff = 6; + break; + } + $query = $db->prepare("INSERT INTO mappacks (name, levels, stars, coins, difficulty, rgbcolors, colors2, timestamp) VALUES (:name, :levels, :stars, :coins, :diff, :rgb, :c2, :time)"); + $query->execute([':name' => $name, ':levels' => $levels, ':stars' => $stars, ':coins' => $coins, ':diff' => $diff, ':rgb' => $color, ':c2' => $color, ':time' => time()]); + $packID = $db->lastInsertId(); + $gs->sendLogsMapPackChangeWebhook($packID, $_SESSION['accountID']); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3, value4, value7) VALUES ('17',:value,:timestamp,:account,:levels, :stars, :coins, :rgb)"); + $query->execute([':value' => $name, ':timestamp' => time(), ':account' => $accountID, ':levels' => $levels, ':stars' => $stars, ':coins' => $coins, ':rgb' => $color]); + $success = $dl->getLocalizedString("packCreateSuccess").' '.$name."!"; + $dl->printSong('
+

'.$dl->getLocalizedString("packCreateTitle").'

+
+

'.$success.'

+ +
+
', 'mod'); +} else { + $packs = $db->prepare("SELECT ID, name, stars, coins, rgbcolors FROM mappacks ORDER BY ID DESC"); + $packs->execute(); + $packs = $packs->fetchAll(); + foreach($packs as &$pack) $allPacks .= ''; + $dl->printSong('
+
+ '.$allPacks.' +
+
+

' . $dl->getLocalizedString("packCreateTitle") . '

+
+

' . $dl->getLocalizedString("packCreateDesc") . '

+
+
+
+ + +
+
+ + + +
'.Captcha::displayCaptcha(true).' +
+
+ ', 'mod'); +} +} else + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod'); +?> \ No newline at end of file diff --git a/dashboard/levels/packs.php b/dashboard/levels/packs.php new file mode 100644 index 000000000..f54bc9300 --- /dev/null +++ b/dashboard/levels/packs.php @@ -0,0 +1,63 @@ +checkPermission($_SESSION["accountID"], 'dashboardLevelPackCreate')) { + switch(ExploitPatch::number($_GET["stars"])) { + case 1: + $diff = 0; + break; + case 2: + $diff = 1; + break; + case 3: + $diff = 2; + break; + case 4: + case 5: + $diff = 3; + break; + case 6: + case 7: + $diff = 4; + break; + case 8: + case 9: + $diff = 5; + break; + case 10: + $diff = 6; + break; + } + $levels = explode(',', ExploitPatch::remove($_GET["levels"])); + if(!$gs->getLevelName($levels[0]) OR !$gs->getLevelName($levels[1]) OR !$gs->getLevelName($levels[2])) die("-1"); + $color = explode(',', ExploitPatch::remove($_GET["color"])); + if($color[0] > 255) $color[0] = 255; elseif($color[0] < 0) $color[0] = 0; + if($color[1] > 255) $color[1] = 255; elseif($color[1] < 0) $color[1] = 0; + if($color[2] > 255) $color[2] = 255; elseif($color[2] < 0) $color[2] = 0; + $color = $color[0].','.$color[1].','.$color[2]; + if($_GET["stars"] > 10) $stars = 10; elseif($_GET["stars"] < 1) $stars = 1; else $stars = ExploitPatch::number($_GET["stars"]); + if($_GET["coins"] > 2) $coins = 2; elseif($_GET["coins"] < 1) $coins = 1; else $coins = ExploitPatch::number($_GET["coins"]); + $getPack = $db->prepare("SELECT * FROM mappacks WHERE ID = :packID"); + $getPack->execute([':packID' => $id]); + $getPack = $getPack->fetch(); + $change = $db->prepare("UPDATE mappacks SET name = :n, levels = :l, rgbcolors = :r, stars = :s, coins = :c, difficulty = :d WHERE id = :i"); + $change->execute([':n' => ExploitPatch::remove($_GET["name"]), ':l' => ExploitPatch::remove($_GET["levels"]), ':r' => $color, ':s' => $stars, ':c' => $coins, ':i' => $id, ':d' => $diff]); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3, value4, value7) VALUES ('21',:value,:timestamp,:account,:levels, :stars, :coins, :rgb)"); + $query->execute([':value' => ExploitPatch::remove($_GET["name"]), ':timestamp' => time(), ':account' => $_SESSION["accountID"], ':levels' => ExploitPatch::remove($_GET["levels"]), ':stars' => $stars, ':coins' => $coins, ':rgb' => $color]); + $gs->sendLogsMapPackChangeWebhook($id, $_SESSION['accountID'], $getPack); + echo 1; + } else { + $pck = $db->prepare("SELECT * FROM mappacks WHERE ID = :id"); + $pck->execute([':id' => $id]); + $map = $pck->fetch(); + echo $map["ID"].' | '.$map["name"].' | '.$map["stars"].' | '.$map["coins"].' | '.$map["rgbcolors"].' | '.$map["levels"]; + } +} +?> \ No newline at end of file diff --git a/dashboard/levels/rateLevel.php b/dashboard/levels/rateLevel.php new file mode 100644 index 000000000..9c07ee714 --- /dev/null +++ b/dashboard/levels/rateLevel.php @@ -0,0 +1,20 @@ + 10 || $stars < 0) header('Location: '.$_SERVER['HTTP_REFERER']); +$difficulty = $gs->getDiffFromStars($stars); +if($gs->checkPermission($_SESSION["accountID"], "actionRateStars")) { + $gs->featureLevel($_SESSION["accountID"], $levelID, $featured); + $gs->verifyCoinsLevel($_SESSION["accountID"], $levelID, 1); + $gs->rateLevel($_SESSION["accountID"], $levelID, $stars, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); +} elseif($gs->checkPermission($_SESSION["accountID"], "actionSuggestRating")) $gs->suggestLevel($_SESSION["accountID"], $levelID, $difficulty["diff"], $stars, $featured, $difficulty["auto"], $difficulty["demon"]); +header('Location: '.$_SERVER['HTTP_REFERER']); +?> \ No newline at end of file diff --git a/dashboard/levels/shareCP.php b/dashboard/levels/shareCP.php new file mode 100644 index 000000000..0ddce385c --- /dev/null +++ b/dashboard/levels/shareCP.php @@ -0,0 +1,109 @@ +printFooter('../'); +$dl->title($dl->getLocalizedString("shareCPTitle")); +if($gs->checkPermission($_SESSION["accountID"], "commandSharecpAll")){ +if(!empty($_POST["username"]) AND !empty($_POST["level"])) { + if(!Captcha::validateCaptcha()) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'mod'); + die(); + } + $userID = ExploitPatch::number($_POST["username"]); + $level = ExploitPatch::number($_POST["level"]); + $query = $db->prepare("SELECT * FROM cpshares WHERE levelID = :level AND userID = :user"); + $query->execute([':level' => $level, ':user' => $userID]); + $res = $query->fetchAll(); + if(count($res) != 0) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("alreadyShared").'

+ +
+
', 'mod'); + die(); + } + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :level AND userID = :user"); + $query->execute([':level' => $level, ':user' => $userID]); + $res = $query->fetchAll(); + if(count($res) != 0) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("shareToAuthor").'

+ +
+
', 'mod'); + die(); + } + $query = $db->prepare("SELECT IP FROM users WHERE userID = :user"); + $query->execute([':user' => $userID]); + $query = $query->fetchColumn(); + $res = $gs->getPersonBan($gs->getExtID($userID), $userID, 1, $query); + if($res) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("userIsBanned").'

+ +
+
', 'mod'); + die(); + } + $accountID = $_SESSION["accountID"]; + $query = $db->prepare("INSERT INTO cpshares (levelID, userID) VALUES (:level, :user)"); + $query->execute([':level' => $level, ':user' => $userID]); + $query = $db->prepare("UPDATE levels SET isCPShared = 1 WHERE levelID = :level"); + $query->execute([':level' => $level]); + $username = $gs->getAccountName($userID); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('11',:value,:timestamp,:account,:level)"); + $query->execute([':value' => $username, ':timestamp' => time(), ':account' => $accountID, ':level' => $level]); + $query = $db->prepare("SELECT levelName FROM levels WHERE levelID=:level"); + $query->execute([':level' => $level]); + $res = $query->fetch(); + $level = $res["levelName"]; + $success = sprintf($dl->getLocalizedString("shareCPSuccessNew"), $level, $username); + if($automaticCron) Cron::updateCreatorPoints($_SESSION['accountID'], false); + $dl->printSong('
+

'.$dl->getLocalizedString("shareCPTitle").'

+
+

'.$success.'

+ +
+
', 'mod'); +} else { + $dl->printSong('
+

' . $dl->getLocalizedString("shareCPTitle") . '

+
+

' . $dl->getLocalizedString("shareCPDesc") . '

+
+
+ '.Captcha::displayCaptcha(true).' + +
+
', 'mod'); +} +} else + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod'); +?> \ No newline at end of file diff --git a/dashboard/levels/vaultCodes.php b/dashboard/levels/vaultCodes.php new file mode 100644 index 000000000..b9a9e595c --- /dev/null +++ b/dashboard/levels/vaultCodes.php @@ -0,0 +1,214 @@ +title($dl->getLocalizedString("vaultCodesTitle")); +$dl->printFooter('../'); +$allVaultCodes = $vaultCodeName = $vaultCodeRewards = $vaultCodeUses = $vaultCodeDuration = ''; +if(isset($_GET['rewardID']) && !isset($_POST['rewardID'])) $_POST['rewardID'] = $_GET['rewardID']; +if(!$gs->checkPermission($_SESSION["accountID"], "dashboardVaultCodesManage")) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod')); +if(isset($_POST['vaultCodeName']) && isset($_POST['vaultCodeRewards'])) { + if(!Captcha::validateCaptcha()) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'mod')); + $vaultCodeName = base64_encode(trim(ExploitPatch::rucharclean($_POST['vaultCodeName']))); + $vaultCodeRewards = ExploitPatch::numbercolon($_POST['vaultCodeRewards']); + $vaultCodeUses = $_POST['vaultCodeUses'] > 0 ? ExploitPatch::number($_POST['vaultCodeUses']) : '-1'; + $vaultCodeDuration = !empty($_POST['vaultCodeDuration']) ? (new DateTime($_POST['vaultCodeDuration']))->getTimestamp() : 0; + if(empty($_POST['rewardID'])) { + $checkName = $db->prepare('SELECT count(*) FROM vaultcodes WHERE code LIKE :code'); + $checkName->execute([':code' => $vaultCodeName]); + $checkName = $checkName->fetchColumn(); + if($checkName) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("vaultCodeExists").'

+ +
+
', 'mod')); + $insertCode = $db->prepare('INSERT INTO vaultcodes (code, rewards, duration, uses, timestamp) VALUES (:code, :rewards, :duration, :uses, :timestamp)'); + $insertCode->execute([':code' => $vaultCodeName, ':rewards' => $vaultCodeRewards, ':duration' => $vaultCodeDuration, ':uses' => $vaultCodeUses, ':timestamp' => time()]); + $rewardID = $db->lastInsertId(); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value4, value5, value6, timestamp, account) VALUES ('42', :value, :value2, :value4, :value5, :value6, :timestamp, :accountID)"); + $query->execute([':value' => $vaultCodeName, ':value2' => $vaultCodeRewards, ':value4' => $vaultCodeDuration, ':value5' => $vaultCodeUses, ':value6' => $rewardID, ':timestamp' => time(), ':accountID' => $_SESSION['accountID']]); + $rewardID = $vaultCodeName = $vaultCodeRewards = $vaultCodeUses = $vaultCodeDuration = ''; + } else { + $rewardID = ExploitPatch::number($_POST['rewardID']); + $checkName = $db->prepare('SELECT count(*) FROM vaultcodes WHERE code LIKE :code AND rewardID != :rewardID'); + $checkName->execute([':code' => $vaultCodeName, ':rewardID' => $rewardID]); + $checkName = $checkName->fetchColumn(); + if($checkName) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("vaultCodeExists").'

+ +
+
', 'mod')); + $insertCode = $db->prepare('UPDATE vaultcodes SET code = :code, rewards = :rewards, duration = :duration, uses = :uses WHERE rewardID = :rewardID'); + $insertCode->execute([':code' => $vaultCodeName, ':rewards' => $vaultCodeRewards, ':duration' => $vaultCodeDuration, ':uses' => $vaultCodeUses, ':rewardID' => $rewardID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value4, value5, value6, timestamp, account) VALUES ('43', :value, :value2, :value4, :value5, :value6, :timestamp, :accountID)"); + $query->execute([':value' => $vaultCodeName, ':value2' => $vaultCodeRewards, ':value4' => $vaultCodeDuration, ':value5' => $vaultCodeUses, ':value6' => $rewardID, ':timestamp' => time(), ':accountID' => $_SESSION['accountID']]); + } +} +if(!empty($_POST['rewardID'])) { + $rewardID = ExploitPatch::number($_POST['rewardID']); + $getVaultCode = $db->prepare('SELECT * FROM vaultcodes WHERE rewardID = :rewardID'); + $getVaultCode->execute([':rewardID' => $rewardID]); + $getVaultCode = $getVaultCode->fetch(); + if($getVaultCode) { + $vaultCodeName = htmlspecialchars(base64_decode($getVaultCode['code'])); + $vaultCodeUses = $getVaultCode['uses']; + $vaultCodeRewards = $getVaultCode['rewards']; + $vaultCodeDuration = !empty($getVaultCode['duration']) ? date('Y-m-d\TH:i:s', $getVaultCode['duration']) : ''; + } +} +$rewardTypes = $gs->getRewardTypes(); +$vaultCodes = $db->prepare("SELECT * FROM vaultcodes ORDER BY rewardID ASC"); +$vaultCodes->execute(); +$vaultCodes = $vaultCodes->fetchAll(); +foreach($vaultCodes as &$vaultCode) { + $allVaultCodes .= ''; +} +$rewardTypesOptions = ''; +foreach($rewardTypes AS $rewardType => $rewardText) { + if(empty($rewardText)) continue; + $rewardTypesOptions .= ''; +} +$dl->printSong('
+
+
+ '.$allVaultCodes.' +
+
+
+

'.$dl->getLocalizedString((!empty($rewardID) ? 'vaultCodesEditTitle' : 'vaultCodesTitle')).'

+
+

'.$dl->getLocalizedString((!empty($rewardID) ? 'vaultCodesEditDesc' : 'vaultCodesDesc')).'

+ +
+ +
+ +
+
+ +

'.$dl->getLocalizedString('rewards').'

+ +
+
+ + +
+
+
+ + +
+ + '.Captcha::displayCaptcha(true).' + +
+
+ +
+
+
+', 'mod'); +?> \ No newline at end of file diff --git a/dashboard/login/activate.php b/dashboard/login/activate.php new file mode 100644 index 000000000..110eda865 --- /dev/null +++ b/dashboard/login/activate.php @@ -0,0 +1,147 @@ +title($dl->getLocalizedString("activateAccount")); +$dl->printFooter('../'); +if(!$preactivateAccounts) { +if(!isset($_SESSION["accountID"]) OR $_SESSION["accountID"] == 0){ +if($mailEnabled) { + if(isset($_GET["mail"])) { + $mail = ExploitPatch::remove(explode('/', $_GET["mail"])[count(explode('/', $_GET["mail"]))-1]); + $check = $db->prepare("SELECT * FROM accounts WHERE mail = :mail"); + $check->execute([':mail' => $mail]); + $check = $check->fetch(); + if(empty($check)) { + $gs->logAction(0, 4, 1); + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("nothingFound").'

+ +
'); + die(); + } else { + $query = $db->prepare("UPDATE accounts SET isActive = '1', mail = 'activated' WHERE accountID = :acc"); + $query->execute([':acc' => $check["accountID"]]); + $gs->logAction($check["accountID"], 3, 1); + $gs->sendLogsAccountChangeWebhook($check['accountID'], $check['accountID'], $check); + $dl->printSong('
+

'.$dl->getLocalizedString("activateAccount").'

+
+

'.$dl->getLocalizedString("activated").'

+ +
'); + die(); + } + } + die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("activateDisabled").'

+ +
+
')); +} +if(!empty($_POST["userName"]) && !empty($_POST["password"])){ + if(!Captcha::validateCaptcha()) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
'); + die(); + } + $userName = ExploitPatch::charclean($_POST["userName"]); + $password = $_POST["password"]; + $pass = GeneratePass::isValidUsrname($userName, $password); + $getAccountData = $db->prepare('SELECT * FROM accounts WHERE userName LIKE :userName'); + $getAccountData->execute([':userName' => $userName]); + $getAccountData = $getAccountData->fetch(); + if($pass == '-2') { + $query = $db->prepare("UPDATE accounts SET isActive = 1 WHERE userName LIKE :userName"); + $query->execute(['userName' => $userName]); + $gs->logAction($getAccountData["accountID"], 3, 1); + $gs->sendLogsAccountChangeWebhook($getAccountData['accountID'], $getAccountData['accountID'], $getAccountData); + $dl->printSong('
+

'.$dl->getLocalizedString("activateAccount").'

+
+

'.$dl->getLocalizedString("activated").'

+ +
'); + } + elseif ($pass == 1) { + $gs->logAction($getAccountData["accountID"], 4, 1); + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("alreadyActivated").'

+ +
'); + } else { + if($getAccountData) $gs->logAction($getAccountData["accountID"], 4, 2); + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("nothingFound").'

+ +
'); + } +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("activateAccount").'

+
+

'.$dl->getLocalizedString("activateDesc").'

+
+
+ '); + Captcha::displayCaptcha(); + echo ' + +
+ '; +} +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("loginAlready").'

+ +
+
'); +} +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("activateDisabled").'

+ +
+
'); +} +?> \ No newline at end of file diff --git a/dashboard/login/api.php b/dashboard/login/api.php new file mode 100644 index 000000000..badabc2b6 --- /dev/null +++ b/dashboard/login/api.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/dashboard/login/forgotPassword.php b/dashboard/login/forgotPassword.php new file mode 100644 index 000000000..88acdbd35 --- /dev/null +++ b/dashboard/login/forgotPassword.php @@ -0,0 +1,194 @@ +title($dl->getLocalizedString("forgotPasswordTitle")); +$dl->printFooter('../'); +if(isset($_SESSION["accountID"]) AND $_SESSION["accountID"] > 0) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("loginAlready").'

+ +
+
')); +} +if(!$mailEnabled) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
')); +} +if(!empty(ExploitPatch::charclean($_POST['code'])) AND !empty($_POST['password']) AND !empty($_POST['repeatpassword'])) { + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ + + + +
+
')); + } + $code = ExploitPatch::charclean($_POST['code']); + $accountID = $db->prepare("SELECT accountID FROM accounts WHERE passCode = :code"); + $accountID->execute([':code' => $code]); + $accountID = $accountID->fetchColumn(); + if(empty($accountID)) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("nothingFound").'

+ +
')); + } + $pass = $_POST['password']; + $repeatPass = $_POST['repeatpassword']; + if($pass !== $repeatPass) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("passDontMatch").'

+ + + + +
+
')); + } + $passhash = password_hash($pass, PASSWORD_DEFAULT); + $gjp2 = GeneratePass::GJP2hash($pass); + $auth = $gs->randomString(8); + $query = $db->prepare("UPDATE accounts SET password = :password, gjp2 = :gjp, auth = :auth, passCode = '' WHERE accountID = :id"); + $test = $query->execute([':auth' => $auth, ':password' => $passhash, ':id' => $accountID, ':gjp' => $gjp2]); + var_dump($test); + exit($dl->printSong('
+

'.$dl->getLocalizedString("forgotPasswordChangeTitle").'

+
+

'.$dl->getLocalizedString("successfullyChangedPass").'

+ +
')); +} +if(!empty($_GET['code'])) { + $code = ExploitPatch::charclean(explode('/', $_GET["code"])[count(explode('/', $_GET["code"]))-1]); + $check = $db->prepare("SELECT accountID FROM accounts WHERE passCode = :code"); + $check->execute([':code' => $code]); + $check = $check->fetch(); + if(empty($check) || empty($code)) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("nothingFound").'

+ +
')); + } else { + exit($dl->printSong('
+

'.$dl->getLocalizedString("forgotPasswordChangeTitle").'

+
+

'.$dl->getLocalizedString("changePassDesc").'

+
+ '.$dl->getLocalizedString("passDontMatch").' +
+ '.Captcha::displayCaptcha(true).' + + +
+
+ ')); + } +} +if(!empty($_POST['username']) && !empty($_POST['email'])) { + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
')); + } + $username = ExploitPatch::charclean($_POST["username"]); + $email = ExploitPatch::rucharclean($_POST["email"]); + $check = $db->prepare("SELECT accountID FROM accounts WHERE userName LIKE :username AND email LIKE :email"); + $check->execute([':username' => $username, ':email' => $email]); + $check = $check->fetch(); + if($check) { + $code = $gs->randomString(6); + $sendCode = $db->prepare("UPDATE accounts SET passCode = :code WHERE accountID = :id"); + $sendCode->execute([':code' => $code, ':id' => $check['accountID']]); + $gs->mail($email, $username, $code); + } + exit($dl->printSong('
+

'.$dl->getLocalizedString("forgotPasswordTitle").'

+
+

'.$dl->getLocalizedString("maybeSentAMessage").'

+ +
+
')); +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("forgotPasswordTitle").'

+
+

'.$dl->getLocalizedString("forgotPasswordDesc").'

+
+
+ '.Captcha::displayCaptcha(true).' + +
+
+ '); +} +?> \ No newline at end of file diff --git a/dashboard/login/login.php b/dashboard/login/login.php index 3af5bbe1a..c645c954a 100644 --- a/dashboard/login/login.php +++ b/dashboard/login/login.php @@ -1,50 +1,169 @@ printLoginBox("

You are already logged in. Click here to continue

"); - exit(); +if(isset($_SESSION["accountID"]) && $_SESSION["accountID"] != 0) header('Location: ../'); +if(isset($_POST["resendMailUserName"]) && isset($_POST["resendMailEmail"]) && $mailEnabled) { + $dl->title($dl->getLocalizedString("resendMailTitle")); + $dl->printFooter('../'); + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
')); + } + $userName = ExploitPatch::charclean($_POST["resendMailUserName"]); + $email = ExploitPatch::rucharclean($_POST["resendMailEmail"]); + $check = $db->prepare('SELECT count(*) FROM accounts WHERE userName = :username AND email = :email AND isActive = 0'); + $check->execute([':username' => $userName, ':email' => $email]); + $check = $check->fetchColumn(); + if($check) $gs->mail($email, $userName); + exit($dl->printSong('
+

'.$dl->getLocalizedString("resendMailTitle").'

+
+

'.$dl->getLocalizedString("maybeSentAMessage").'

+ +
+
')); } -if(isset($_POST["userName"]) AND isset($_POST["password"])){ - $userName = $_POST["userName"]; +if(isset($_POST["userName"]) && isset($_POST["password"])) { + $userName = ExploitPatch::charclean($_POST["userName"]); $password = $_POST["password"]; $valid = GeneratePass::isValidUsrname($userName, $password); - if($valid != 1){ - $dl->printLoginBoxInvalid(); - exit(); + if($valid != 1) { + $dl->title($dl->getLocalizedString("loginBox")); + $dl->printFooter('../'); + if($valid == -2) { + if($mailEnabled) $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("didntActivatedEmail").'

+

'.$dl->getLocalizedString("resendMailHint").'

+ +
+
'); + else $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

+ + +
'); + die(); + } + if($valid == -1) { + $accountID = $gs->getAccountIDFromName($userName); + $userID = $gs->getUserID($accountID, $userName); + $checkBan = $gs->getPersonBan($accountID, $userID, 4); + if($checkBan) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.sprintf($dl->getLocalizedString("youAreBanned"), htmlspecialchars(base64_decode($checkBan['reason'])), date("d.m.Y G:i", $checkBan['expires'])).'

+ +
+
')); + } + } + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("wrongNickOrPass").'

+ +
+
')); } $accountID = $gs->getAccountIDFromName($userName); - if($accountID == 0){ - $dl->printLoginBoxError("Invalid accountID"); - exit(); - } - $_SESSION["accountID"] = $accountID; - if(isset($_POST["ref"])){ - header('Location: ' . $_POST["ref"]); - }elseif(isset($_SERVER["HTTP_REFERER"])){ - header('Location: ' . $_SERVER["HTTP_REFERER"]); - } - $dl->printLoginBox("

You are now logged in. Please click here to continue.

"); -}else{ - $loginbox = '
-
- - -
-
- - -
'; - if(isset($_SERVER["HTTP_REFERER"])){ - $loginbox .= ''; + $_SESSION["accountID"] = $accountID; + $gs->logAction($accountID, 2); + $query = $db->prepare("SELECT auth FROM accounts WHERE accountID = :id"); + $query->execute([':id' => $accountID]); + $auth = $query->fetch(); + if($auth["auth"] == 'none') { + $auth = $gs->randomString(8); + $query = $db->prepare("UPDATE accounts SET auth = :auth WHERE accountID = :id"); + $query->execute([':auth' => $auth, ':id' => $accountID]); + setcookie('auth', $auth, 2147483647, '/'); + } else setcookie('auth', $auth["auth"], 2147483647, '/'); + if(!empty($_SERVER["HTTP_REFERER"])) header('Location: '.$_SERVER["HTTP_REFERER"]); + else header('Location: ../'); +} else { + $dl->printFooter('../'); + if(isset($_GET['resend_mail']) && $mailEnabled) { + $dl->title($dl->getLocalizedString("resendMailTitle")); + exit($dl->printSong('
+

'.$dl->getLocalizedString("resendMailTitle").'

+ +

'.$dl->getLocalizedString('resendMailDesc').'

+
+ +
+
+ +
+ '.Captcha::displayCaptcha(true).' + + + +
')); } - $loginbox .= ' - '; - $dl->printLoginBox($loginbox); + $dl->title($dl->getLocalizedString("loginBox")); + $dl->printSong('
+

'.$dl->getLocalizedString("loginBox").'

+
+

'.$dl->getLocalizedString('loginDesc').'

+
+ +
+
+ +
'.(!$preactivateAccounts ? ($mailEnabled ? '' : '') : '').' + +
+ +
'); } -?> +?> \ No newline at end of file diff --git a/dashboard/login/logout.php b/dashboard/login/logout.php index 0d5cc7f2a..6913e9ad3 100644 --- a/dashboard/login/logout.php +++ b/dashboard/login/logout.php @@ -1,10 +1,7 @@ printLoginBox("

You are now logged out. Click here to continue

"); +setcookie('auth', 'no', 2147483647, '/'); +if(!empty($_SERVER["HTTP_REFERER"])) header('Location: '.$_SERVER["HTTP_REFERER"]); +else header('Location: ../'); ?> \ No newline at end of file diff --git a/dashboard/login/register.php b/dashboard/login/register.php new file mode 100644 index 000000000..c4799fadc --- /dev/null +++ b/dashboard/login/register.php @@ -0,0 +1,230 @@ +title($dl->getLocalizedString("registerAcc")); +$dl->printFooter('../'); +if(Automod::isAccountsDisabled(0)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
')); +if(!isset($_SESSION["accountID"]) OR $_SESSION["accountID"] == 0) { +if(!isset($preactivateAccounts)) $preactivateAccounts = false; +if(!isset($filterUsernames)) global $filterUsernames; +// here begins the checks +if(!empty($_POST["username"]) AND !empty($_POST["email"]) AND !empty($_POST["repeatemail"]) AND !empty($_POST["password"]) AND !empty($_POST["repeatpassword"])){ + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
')); + } + $username = str_replace(' ', '', ExploitPatch::charclean($_POST["username"])); + $password = $_POST["password"]; + $repeat_password = $_POST["repeatpassword"]; + $email = ExploitPatch::rucharclean($_POST["email"]); + $repeat_email = ExploitPatch::rucharclean($_POST["repeatemail"]); + if($filterUsernames >= 1) { + $bannedUsernamesList = array_map('strtolower', $bannedUsernames); + switch($filterUsernames) { + case 1: + if(in_array(strtolower($username), $bannedUsernamesList)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badUsername").'

+ +
+
')); + break; + case 2: + foreach($bannedUsernamesList as $bannedUsername) { + if(!empty($bannedUsername) && mb_strpos(strtolower($username), $bannedUsername) !== false) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("badUsername").'

+ +
+
')); + } + } + } + if(strlen($username) < 3) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("smallNick").'

+ +
+
')); + } + if(strlen($username) > 20) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("bigNick").'

+ +
+
')); + } + if(strlen($password) < 6) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("smallPass").'

+ +
+
')); + } else { + $query = $db->prepare("SELECT count(*) FROM accounts WHERE userName LIKE :userName"); + $query->execute([':userName' => $username]); + $registred_users = $query->fetchColumn(); + if($registred_users > 0){ + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("alreadyUsedNick").'

+ +
+
')); + } else { + if($password != $repeat_password){ + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("passDontMatch").'

+ +
+
')); + } elseif($email != $repeat_email){ + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emailDontMatch").'

+ +
+
')); + }else{ + if($mailEnabled) { + $checkMail = $db->prepare("SELECT count(*) FROM accounts WHERE email LIKE :mail"); + $checkMail->execute([':mail' => $email]); + $checkMail = $checkMail->fetchColumn(); + if($checkMail > 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("mailExists").'

+ +
+
')); + } + $hashpass = password_hash($password, PASSWORD_DEFAULT); + $gjp2 = GeneratePass::GJP2hash($password); + $query2 = $db->prepare("INSERT INTO accounts (userName, password, email, registerDate, isActive, gjp2) + VALUES (:userName, :password, :email, :time, :isActive, :gjp)"); + $query2->execute([':userName' => $username, ':password' => $hashpass, ':email' => $email, ':time' => time(), ':isActive' => $preactivateAccounts ? 1 : 0, ':gjp' => $gjp2]); + $accountID = $db->lastInsertId(); + $gs->logAction($accountID, 1, $username, $email, $gs->getUserID($accountID, $username)); + $gs->sendLogsRegisterWebhook($accountID); + if($mailEnabled) { + $gs->mail($email, $username); + exit($dl->printSong('
+

'.$dl->getLocalizedString("registerAcc").'

+
+

'.$dl->getLocalizedString("registered").'

+

'.$dl->getLocalizedString("checkMail").'

+ +
+
')); + } else exit($dl->printSong('
+

'.$dl->getLocalizedString("registerAcc").'

+
+

'.$dl->getLocalizedString("registered").'

+ +
+
')); + } + } + } +}else{ + $dl->printSong('
+

'.$dl->getLocalizedString("registerAcc").'

+
+

'.$dl->getLocalizedString("registerDesc").'

+
+
+ '.$dl->getLocalizedString("passDontMatch").' +
+
+ '.$dl->getLocalizedString("emailDontMatch").' +
+ '.Captcha::displayCaptcha(true).' + +
+
+ '); +} +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("loginAlready").'

+ +
+
'); +} +?> \ No newline at end of file diff --git a/dashboard/messenger/.htaccess b/dashboard/messenger/.htaccess new file mode 100644 index 000000000..69b112001 --- /dev/null +++ b/dashboard/messenger/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ %{REQUEST_URI}/../../messenger/?id=%{REQUEST_URI} [L] diff --git a/dashboard/messenger/index.php b/dashboard/messenger/index.php new file mode 100644 index 000000000..9f50fb298 --- /dev/null +++ b/dashboard/messenger/index.php @@ -0,0 +1,316 @@ +printFooter('../'); +if(!isset($_POST["receiver"])) { + $getID = str_replace('%20', ' ', explode("/", $_GET["id"])[count(explode("/", $_GET["id"]))-1]); + $receiver = ExploitPatch::charclean($getID); + if(!empty($receiver)) { + if(is_numeric($receiver)) $_POST["receiver"] = ExploitPatch::number($receiver); + else $_POST["receiver"] = $gs->getAccountIDFromName(ExploitPatch::charclean($receiver)); + } +} +$allChats = '
+ +
'; +$allChatsEmpty = true; +$friendsChats = '
+ +
'; +$friendsChatsEmpty = true; +$alertScript = ''; +$chatBox = '
+ +

'.$dl->getLocalizedString('chooseChat').'

+
'; +$pageScript = 'function friendsList() { + if(window.currentMessengerTab == "chats") { + document.getElementById("lastChats").classList.add("hide"); + document.getElementById("friendsChats").classList.remove("hide"); + window.currentMessengerTab = "friends"; + if(friendsChatsEmpty) document.getElementById("itemoverflow").classList.add("empty-itemoverflow"); + else document.getElementById("itemoverflow").classList.remove("empty-itemoverflow"); + } else { + document.getElementById("lastChats").classList.remove("hide"); + document.getElementById("friendsChats").classList.add("hide"); + window.currentMessengerTab = "chats"; + if(allChatsEmpty) document.getElementById("itemoverflow").classList.add("empty-itemoverflow"); + else document.getElementById("itemoverflow").classList.remove("empty-itemoverflow"); + } +}'; +if($msgEnabled == 0) { + $dl->title($dl->getLocalizedString("messenger")); + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'msg')); +} +if(!isset($_SESSION["accountID"]) || $_SESSION["accountID"] == 0) { + $dl->title($dl->getLocalizedString("messenger")); + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'msg')); +} +if($_POST['receiver'] != 0 && ExploitPatch::number($_POST['receiver']) != $_SESSION['accountID'] && !empty($gs->getAccountName(ExploitPatch::number($_POST['receiver'])))) { + $receiver = ExploitPatch::number($_POST['receiver']); + $receiverUsername = $gs->getAccountName($receiver); + if(isset($_POST['subject']) && isset($_POST['body'])) { + $checkBan = $gs->getPersonBan($_SESSION['accountID'], $gs->getUserID($_SESSION['accountID']), 3); + if($checkBan) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.sprintf($dl->getLocalizedString("youAreBanned"), htmlspecialchars(base64_decode($checkBan['reason'])), date("d.m.Y G:i", $checkBan['expires'])).'

+ +
+
')); + $subject = ExploitPatch::url_base64_encode(trim(ExploitPatch::rucharclean($_POST["subject"]))); + $body = ExploitPatch::rucharclean($_POST["body"]); + if(Automod::isAccountsDisabled(3)) { + $alertScript = $dl->getLocalizedString('messagingIsDisabled'); + $subject = $body = ""; + } + if(is_numeric(mb_substr($body, -3)) && !is_numeric(mb_substr($body, -4))) $body .= ' '; + $body = base64_encode(trim($xor->cipher($body, 14251))); + $query = $db->prepare("SELECT timestamp FROM messages WHERE accID = :accountID AND toAccountID = :toAccountID ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':accountID' => $_SESSION['accountID'], ':toAccountID' => $receiver]); + $res = $query->fetch(); + $time = time() - 3; + if($res["timestamp"] > $time) { + $alertScript = $dl->getLocalizedString("tooFast"); + $subject = $body = ""; + } + $privacySettings = $db->prepare("SELECT mS FROM accounts WHERE accountID = :receiver"); + $privacySettings->execute([':receiver' => $receiver]); + $privacySettings = $privacySettings->fetchColumn(); + $block = $db->prepare("SELECT count(*) FROM blocks WHERE (person1 = :receiver AND person2 = :accountID) OR (person2 = :receiver AND person1 = :accountID)"); + $block->execute([':receiver' => $receiver, ':accountID' => $_SESSION['accountID']]); + $block = $block->fetchColumn(); + if(($privacySettings == 1 && !$gs->isFriends($receiver, $_SESSION["accountID"])) || $privacySettings == 2 || $block > 0) { + $alertScript = $dl->getLocalizedString("cantMessage"); + $subject = $body = ""; + } + if(!empty($subject) && !empty($body) && !empty($receiverUsername)) { + $query = $db->prepare("INSERT INTO messages (userID, userName, body, subject, accID, toAccountID, timestamp, secret, isNew) + VALUES (:userID, :userName, :body, :subject, :accountID, :receiver, :time, 'Wmfd2893gb7', '0')"); + $query->execute([':userID' => $gs->getUserID($_SESSION['accountID']), ':userName' => $gs->getAccountName($_SESSION['accountID']), ':body' => $body, ':subject' => $subject, ':accountID' => $_SESSION['accountID'], ':receiver' => $receiver, 'time' => time()]); + } + } + if($_POST['deleteMessage']) { + $deleteMessageID = ExploitPatch::number($_POST['deleteMessage']); + $messageCheck = $db->prepare("SELECT count(*) FROM messages WHERE toAccountID = :receiver AND accID = :accountID AND messageID = :messageID"); + $messageCheck->execute([':receiver' => $receiver, ':accountID' => $_SESSION['accountID'], ':messageID' => $deleteMessageID]); + $messageCheck = $messageCheck->fetchColumn(); + if($messageCheck) { + $deleteMessage = $db->prepare("DELETE FROM messages WHERE messageID = :messageID"); + $deleteMessage->execute([':messageID' => $deleteMessageID]); + } + } + $query = $db->prepare("SELECT * FROM messages WHERE (accID = :accountID AND toAccountID = :receiver) OR (accID = :receiver AND toAccountID = :accountID) ORDER BY timestamp ASC"); + $query->execute([':accountID' => $_SESSION['accountID'], ':receiver' => $receiver]); + $result = $query->fetchAll(); + foreach($result AS &$messages) { + $div = $messages["accID"] == $_SESSION['accountID'] ? 'you' : 'notyou'; + $subject = htmlspecialchars(ExploitPatch::url_base64_decode($messages["subject"])); + $body = $dl->parseMessage(htmlspecialchars($xor->plaintext(ExploitPatch::url_base64_decode($messages["body"]), 14251))); + $replyToMessageButton = $deleteMessageButton = $wasReadIcon = ''; + if($div == 'notyou') $replyToMessageButton = ''; + else { + $deleteMessageButton = ''; + $wasReadIcon = ' '; + } + $chatMessages .= '
+ '.$deleteMessageButton.' +
+

'.$subject.'

+

'.$body.'

+

'.$dl->convertToDate($messages["timestamp"], true).$wasReadIcon.'

+
+ '.$replyToMessageButton.' +
'; + } + if(empty($chatMessages)) $chatMessages = '
+ +

'.$dl->getLocalizedString('noMsgs').'

+
'; + $readAllMessages = $db->prepare("UPDATE messages SET isNew = 1, readTime = :readTime WHERE accID = :receiver AND toAccountID = :accountID AND readTime = 0"); + $readAllMessages->execute([':receiver' => $receiver, ':accountID' => $_SESSION['accountID'], ':readTime' => time()]); + $chatBox = '
+ +

'.$receiverUsername.'

+ +
+
'.$chatMessages.'
+
+
+
+ + +
'; + $dl->title($dl->getLocalizedString("messenger").", ".$receiverUsername); + $pageScript .= PHP_EOL.'var element = document.getElementById("chatMessages"); + element.scrollTop = element.scrollHeight; + var scrollAll = document.getElementById("chat-opened-all"); + if(scrollAll !== null) scrollAll.scrollIntoView(); + var scrollFriends = document.getElementById("chat-opened-friends"); + if(scrollFriends !== null) scrollFriends.scrollIntoView(); + $(document).on("keyup keypress change keydown", function() { + const p1 = document.getElementById("chatSubject"); + const p2 = document.getElementById("chatBody"); + const btn = document.getElementById("chatSubmit"); + if(!p1.value.trim().length || !p2.value.trim().length) { + btn.disabled = true; + btn.classList.add("btn-block"); + btn.classList.remove("btn-primary"); + } else { + btn.removeAttribute("disabled"); + btn.classList.remove("btn-block"); + btn.classList.remove("btn-size"); + btn.classList.add("btn-primary"); + } + }); + function replyToMessage(messageID) { + messageSubject = document.getElementById("messageSubject" + messageID).innerHTML; + if(!messageSubject.startsWith("Re:")) messageSubject = "Re: " + messageSubject; + document.getElementById("chatSubject").value = messageSubject; + document.getElementById("chatBody").focus(); + } + function deleteMessage(messageID) { + document.getElementById("deleteMessage").value = messageID; + a("messenger/'.$receiverUsername.'", true, true, "POST"); + } + '.(!empty($alertScript) ? 'alert("'.$alertScript.'");' : '').''; +} +$query = $db->prepare("SELECT * FROM messages, (SELECT max(messageID) messageIDs, (CASE WHEN accID = :accountID THEN toAccountID ELSE accID END) receiverID FROM messages WHERE accID = :accountID OR toAccountID = :accountID GROUP BY receiverID ORDER BY timestamp DESC) messageIDs WHERE messageID = messageIDs ORDER BY timestamp DESC"); +$query->execute([':accountID' => $_SESSION['accountID']]); +$result = $query->fetchAll(); +$playersLastMessage = []; +if(!empty($result)) { + $allChats = ''; + $allChatsEmpty = false; +} +foreach($result AS &$chat) { + $youreLast = $chat['accID'] != $_SESSION['accountID'] ? false : true; + $receiver = $chat['receiverID']; + $username = !$youreLast ? $chat['userName'] : $gs->getAccountName($chat['receiverID']); + $body = ($youreLast ? $dl->getLocalizedString("messengerYou").' ' : '').htmlspecialchars($xor->plaintext(ExploitPatch::url_base64_decode($chat["body"]), 14251)); + if(strlen($body) > 50) $body = mb_substr($body, 0, 50).'...'; + $playersLastMessage[$receiver] = $body; + // Avatar management + $queryAvatar = $db->prepare("SELECT iconType, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack, color1, color2, color3, accGlow FROM users WHERE extID = :accountID"); + $queryAvatar->execute([':accountID' => $receiver]); + if($action = $queryAvatar->fetch()) { + $iconType = ($action['iconType'] > 8) ? 0 : $action['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $action['accIcon']], 1 => ['type' => 'ship', 'value' => $action['accShip']], 2 => ['type' => 'ball', 'value' => $action['accBall']], 3 => ['type' => 'ufo', 'value' => $action['accBird']], 4 => ['type' => 'wave', 'value' => $action['accDart']], 5 => ['type' => 'robot', 'value' => $action['accRobot']], 6 => ['type' => 'spider', 'value' => $action['accSpider']], 7 => ['type' => 'swing', 'value' => $action['accSwing']], 8 => ['type' => 'jetpack', 'value' => $action['accJetpack']]]; + $iconValue = $iconTypeMap[$iconType]['value'] ?: 1; + $avatarImg = 'avatar'; + } + // Badge management + $badgeImg = ''; + $queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); + $queryRoleID->execute([':accountID' => $receiver]); + if($roleAssignData = $queryRoleID->fetch()) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } + } + $newMessage = $db->prepare("SELECT min(isNew) newMessage, count(*) messageCount FROM messages WHERE accID = :receiver AND toAccountID = :accountID"); + $newMessage->execute([':receiver' => $receiver, ':accountID' => $_SESSION['accountID']]); + $newMessage = $newMessage->fetch(); + $newMessages = ''; + if($newMessage['newMessage'] == 0 && $newMessage['messageCount'] > 0) { + $newMessagesCount = $db->prepare('SELECT count(*) FROM messages WHERE accID = :receiver AND toAccountID = :accountID AND isNew = 0'); + $newMessagesCount->execute([':receiver' => $receiver, ':accountID' => $_SESSION['accountID']]); + $newMessagesCount = $newMessagesCount->fetchColumn(); + $newMessages = ''.$newMessagesCount.''; + } + $allChats .= ' +
'; +} +$friends = $db->prepare("SELECT * FROM friendships INNER JOIN users ON users.extID = (CASE WHEN person1 = :accountID THEN person2 ELSE person1 END) WHERE person1 = :accountID OR person2 = :accountID ORDER BY userName ASC"); +$friends->execute([':accountID' => $_SESSION['accountID']]); +$friends = $friends->fetchAll(); +if(!empty($friends)) { + $friendsChats = ''; + $friendsChatsEmpty = false; +} +foreach($friends AS &$friend) { + $receiver = $friend['extID']; + $username = $friend['userName']; + $body = isset($playersLastMessage[$receiver]) ? $playersLastMessage[$receiver] : $dl->getLocalizedString('noMsgs'); + // Avatar management + $queryAvatar = $db->prepare("SELECT iconType, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack, color1, color2, color3, accGlow FROM users WHERE extID = :accountID"); + $queryAvatar->execute([':accountID' => $receiver]); + if($action = $queryAvatar->fetch()) { + $iconType = ($action['iconType'] > 8) ? 0 : $action['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $action['accIcon']], 1 => ['type' => 'ship', 'value' => $action['accShip']], 2 => ['type' => 'ball', 'value' => $action['accBall']], 3 => ['type' => 'ufo', 'value' => $action['accBird']], 4 => ['type' => 'wave', 'value' => $action['accDart']], 5 => ['type' => 'robot', 'value' => $action['accRobot']], 6 => ['type' => 'spider', 'value' => $action['accSpider']], 7 => ['type' => 'swing', 'value' => $action['accSwing']], 8 => ['type' => 'jetpack', 'value' => $action['accJetpack']]]; + $iconValue = $iconTypeMap[$iconType]['value'] ?: 1; + $avatarImg = 'avatar'; + } + // Badge management + $badgeImg = ''; + $queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); + $queryRoleID->execute([':accountID' => $receiver]); + if($roleAssignData = $queryRoleID->fetch()) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } + } + $friendsChats .= ' +
'; +} +$dl->printSong('
+
+
+
+ '.$allChats.' +
+
+ '.$friendsChats.' +
+
+ +
+
+
+ '.$chatBox.' +
+
+
+
', 'msg'); +?> \ No newline at end of file diff --git a/dashboard/profile/.htaccess b/dashboard/profile/.htaccess new file mode 100644 index 000000000..c3156bd6d --- /dev/null +++ b/dashboard/profile/.htaccess @@ -0,0 +1,5 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} !^replies +RewriteRule ^ %{REQUEST_URI}/../../profile/?id=%{REQUEST_URI} [L] diff --git a/dashboard/profile/index.php b/dashboard/profile/index.php new file mode 100644 index 000000000..c3efff908 --- /dev/null +++ b/dashboard/profile/index.php @@ -0,0 +1,463 @@ +getOffset(new DateTime); + } + + // sort timezone by offset + asort($timezone_offsets); + + $timezone_list = array(); + foreach( $timezone_offsets as $timezone => $offset ) + { + $offset_prefix = $offset < 0 ? '-' : '+'; + $offset_formatted = gmdate( 'H:i', abs($offset) ); + + $pretty_offset = "UTC${offset_prefix}${offset_formatted}"; + + $timezone_list .= ''; + } + + return $timezone_list; +} + +$clan = $none = ""; +if((!isset($_SESSION["accountID"]) OR $_SESSION["accountID"] == 0) AND (empty($_POST["accountID"]) AND empty($_GET["id"]))) { + $dl->title($dl->getLocalizedString("profile")); + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'profile'); + die(); +} +if(!empty($_POST["accountID"])) { + $accid = ExploitPatch::remove($_POST["accountID"]); + if(!is_numeric($accid)) $userID = $gs->getUserID($accid); +} +elseif(isset($_GET["id"])) { + $dl->printFooter('../../'); + $getID = explode("/", $_GET["id"])[count(explode("/", $_GET["id"]))-1]; + if($getID == "settings") { + $getID = explode("/", $_GET["id"])[count(explode("/", $_GET["id"]))-2]; + $_POST["settings"] = 1; + } + $accid = ExploitPatch::charclean($getID); + if(!is_numeric($accid)) $accid = $gs->getAccountIDFromName(str_replace('%20', ' ', $accid)); + if(!$accid) { + $userCheck = $db->prepare("SELECT * FROM users WHERE userName = :name LIMIT 1"); + $userCheck->execute([":name" => ExploitPatch::remove($getID)]); + $userCheck = $userCheck->fetch(); + if($userCheck) { + $userID = $userCheck['userID']; + $accid = $userCheck['extID']; + } else { + $userCheck = $db->prepare("SELECT * FROM users WHERE extID = :id"); + $userCheck->execute([":id" => ExploitPatch::remove($getID)]); + $userCheck = $userCheck->fetch(); + if($userCheck) { + $userID = $userCheck['userID']; + $accid = $userCheck['extID']; + } + } + } +} +else { + $accid = $_SESSION["accountID"]; + $dl->printFooter('../'); +} +if(!$accid) $accid = $_SESSION["accountID"]; +if(!$userID) $userID = $gs->getUserID($accid); +if(is_numeric($accid)) $accname = $gs->getAccountName($accid); +else $accname = $gs->getUserName($userID); +$dl->title($dl->getLocalizedString("profile").', '.$accname); +if($accid != $_SESSION["accountID"] && is_numeric($accid)) { + $block = $db->prepare("SELECT * FROM blocks WHERE person1 = :p1 AND person2 = :p2"); + $block->execute([':p1' => $accid, ':p2' => $_SESSION["accountID"]]); + $block = $block->fetch(); + if(!empty($block)) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("youBlocked").'

+ +
+
')); +} +if(!empty($_POST["msg"])) { + if(Automod::isAccountsDisabled(1)) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("postingIsDisabled").'

+ +
+
', 'profile')); + $checkBan = $gs->getPersonBan($accid, $userID, 3); + if($checkBan) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.sprintf($dl->getLocalizedString("youAreBanned"), htmlspecialchars(base64_decode($checkBan['reason'])), date("d.m.Y G:i", $checkBan['expires'])).'

+ +
+
')); + $query = $db->prepare("SELECT timestamp FROM acccomments WHERE userID=:accid ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':accid' => $userID]); + $res = $query->fetch(); + $time = time() - 5; + if($res["timestamp"] > $time) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("tooFast").'

+ +
+
', 'profile')); + $msg = ExploitPatch::url_base64_encode(ExploitPatch::rucharclean($_POST["msg"])); + if($enableCommentLengthLimiter && strlen(ExploitPatch::url_base64_decode($msg)) > $maxAccountCommentLength) $msgTooLong = true; + else { + $query = $db->prepare("INSERT INTO acccomments (userID, userName, comment, timestamp) VALUES (:id, :name, :msg, :time)"); + $query->execute([':id' => $userID, ':name' => $accname, ':msg' => $msg, ':time' => time()]); + $gs->logAction($_SESSION['accountID'], 14, $accname, $msg, $db->lastInsertId()); + Automod::checkAccountPostsSpamming($userID); + } +} +if(isset($_POST["settings"]) AND $_POST["settings"] == 1 AND $accid == $_SESSION["accountID"]) { + if(!isset($_POST["ichangedsmth"]) OR $_POST["ichangedsmth"] != 1) { + echo ''; + $query = $db->prepare("SELECT mS, frS, cS, youtubeurl, twitter, twitch, timezone FROM accounts WHERE accountID=:id"); + $query->execute([':id' => $accid]); + $query = $query->fetch(); + $query["youtubeurl"] = mb_ereg_replace("(?!^@)[^a-zA-Z0-9_]", "", $query["youtubeurl"]); + $query["twitter"] = mb_ereg_replace("[^a-zA-Z0-9_]", "", $query["twitter"]); + $query["twitch"] = mb_ereg_replace("[^a-zA-Z0-9_]", "", $query["twitch"]); + exit($dl->printSong('
+
+
+

'.$dl->getLocalizedString("settings").'

+
+
+
+

'.$dl->getLocalizedString("allowMessagesFrom").'

+ +
+
+

'.$dl->getLocalizedString("allowFriendReqsFrom").'

+ +
+
+

'.$dl->getLocalizedString("showCommentHistory").'

+ +
+
+

'.$dl->getLocalizedString("timezoneChoose").'

+ +
+ +
+
+
+

'.$dl->getLocalizedString("yourYouTube").'

+ +
+
+

'.$dl->getLocalizedString("yourTwitter").'

+ +
+
+

'.$dl->getLocalizedString("yourTwitch").'

+ +
+
+ + +
+ +
+
')); + } else { + $getAccountData = $db->prepare("SELECT * FROM accounts WHERE accountID = :accountID"); + $getAccountData->execute([':accountID' => $accid]); + $getAccountData = $getAccountData->fetch(); + if(ExploitPatch::number($_POST["messages"]) > 2 OR ExploitPatch::number($_POST["messages"]) < 0 OR empty(ExploitPatch::number($_POST["messages"]))) $_POST["messages"] = 0; + if(ExploitPatch::number($_POST["friendreqs"]) > 1 OR ExploitPatch::number($_POST["friendreqs"]) < 0 OR empty(ExploitPatch::number($_POST["friendreqs"]))) $_POST["friendreqs"] = 0; + if(ExploitPatch::number($_POST["comments"]) > 2 OR ExploitPatch::number($_POST["comments"]) < 0 OR empty(ExploitPatch::number($_POST["comments"]))) $_POST["comments"] = 0; + $_POST["youtube"] = mb_ereg_replace("(?!^@)[^a-zA-Z0-9_]", "", $_POST["youtube"]); + $_POST["twitter"] = mb_ereg_replace("[^a-zA-Z0-9_]", "", $_POST["twitter"]); + $_POST["twitch"] = mb_ereg_replace("[^a-zA-Z0-9_]", "", $_POST["twitch"]); + $query = $db->prepare("UPDATE accounts SET mS = :ms, frS = :frs, cS = :cs, youtubeurl = :yt, twitter = :twt, twitch = :ttv, timezone = :tz WHERE accountID=:id"); + $query->execute([':id' => $accid, ':ms' => ExploitPatch::number($_POST["messages"]), ':frs' => ExploitPatch::number($_POST["friendreqs"]), ':cs' => ExploitPatch::number($_POST["comments"]), ':yt' => ExploitPatch::remove($_POST["youtube"]), ':twt' => ExploitPatch::remove($_POST["twitter"]), ':ttv' => ExploitPatch::remove($_POST["twitch"]), ':tz' => ExploitPatch::rucharclean($_POST["timezone"])]); + $gs->sendLogsAccountChangeWebhook($accid, $accid, $getAccountData); + } +} +$query = $db->prepare("SELECT * FROM users WHERE userID=:id"); +$query->execute([':id' => $userID]); +$res = $query->fetch(); +$badgeImg = ''; +$queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); +$queryRoleID->execute([':accountID' => $accid]); +if($roleAssignData = $queryRoleID->fetch(PDO::FETCH_ASSOC)) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } +} +$maybeban = '

'.$accname.$badgeImg.'

'; +if(isset($_SERVER["HTTP_REFERER"])) $back = '
'; else $back = ''; +$all = $dl->createProfileStats($res['stars'], $res['moons'], $res['diamonds'], $res['coins'], $res['userCoins'], $res['demons'], $res['creatorPoints'], 0); +$msgs = $db->prepare("SELECT * FROM acccomments WHERE userID = :uid ORDER BY commentID DESC"); +$msgs->execute([':uid' => $userID]); +$msgs = $msgs->fetchAll(); +$comments = $send = ''; +foreach($msgs AS &$msg) { + $none = ''; + $reply = $db->prepare("SELECT count(*) FROM replies WHERE commentID = :id"); + $reply->execute([':id' => $msg["commentID"]]); + $reply = $reply->fetchColumn(); + $message = $dl->parseMessage(htmlspecialchars(ExploitPatch::url_base64_decode($msg["comment"]))); + if($enableCommentLengthLimiter) $message = substr($message, 0, $maxAccountCommentLength); + $time = $msg["timestamp"]; + $likes = ''.$msg["likes"].''; + $dislikes = ''.$msg["dislikes"].''; + $stats = ' '.$likes.' '.(isset($msg['dislikes']) ? ' '.$dislikes : ''); + if($reply < 1) $none = 'display:none'; + $replies = ''; + if($_SESSION["accountID"] != 0) $input = '
'; + $comments .= '
+

'.$accname.'

'.$stats.'

+

'.$message.'

+

'.$replies.'
'.$input.''.$dl->convertToDate($time, true).'

+
+
'; +} +if(empty($comments)) $comments = '

'.$dl->getLocalizedString("empty").'

'; +$msgtopl = '
'; +if($accid == $_SESSION["accountID"]) { + if(empty($comments)) $comments = '

'.$dl->getLocalizedString("writeSomething").'

'; + $send = '
+
+
+
'; + $msgtopl = '
'; +} else { + $privacySettings = $db->prepare("SELECT mS FROM accounts WHERE accountID = :receiver"); + $privacySettings->execute([':receiver' => $accid]); + $privacySettings = $privacySettings->fetchColumn(); + $block = $db->prepare("SELECT count(*) FROM blocks WHERE (person1 = :receiver AND person2 = :accountID) OR (person2 = :receiver AND person1 = :accountID)"); + $block->execute([':receiver' => $accid, ':accountID' => $_SESSION['accountID']]); + $block = $block->fetchColumn(); + if(($privacySettings == 1 && !$gs->isFriends($accid, $_SESSION["accountID"])) || $privacySettings == 2 || $block > 0) $msgtopl = ''; +} +if($_SESSION["accountID"] == 0) $msgtopl = ''; +if($res["dlPoints"] != 0) $points = ' '.$res["dlPoints"].''; +if($gs->isPlayerInClan($accid)) { + $claninfo = $gs->getClanInfo($res["clan"]); + if($claninfo["clanOwner"] == $accid) $own = ''; + $clan = ''; +} +$kit = '
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
'; + +$dl->printSong('
+
+ '.$back.' +
'.$maybeban.$clan.'
'.$msgtopl.$points.' +
+
'.$all.'
+
'.$kit.'
+
+ '.$comments.' +
+ '.$send.' +
+', 'profile'); +?> \ No newline at end of file diff --git a/dashboard/profile/replies.php b/dashboard/profile/replies.php new file mode 100644 index 000000000..eaf32893a --- /dev/null +++ b/dashboard/profile/replies.php @@ -0,0 +1,60 @@ +prepare("SELECT * FROM replies WHERE replyID = :id"); + $reply->execute([':id' => $id]); + $reply = $reply->fetch(); + if($reply["accountID"] == $_SESSION["accountID"]) { + $reply = $db->prepare("DELETE FROM replies WHERE replyID = :id"); + $reply->execute([':id' => $id]); + exit('1'); + } + } + if(empty($_POST["body"])) { + $reply = $db->prepare("SELECT * FROM replies WHERE commentID = :id ORDER BY replyID DESC"); + $reply->execute([':id' => $id]); + $reply = $reply->fetchAll(); + foreach($reply as &$rep) { + if($x > 1) echo ' | '; + $body = $rep["body"]; + if($enableCommentLengthLimiter) $body = base64_encode(substr(base64_decode($body), 0, $maxCommentLength)); + echo $rep["replyID"].', '.$id.', '.$gs->getAccountName($rep["accountID"]).', '.$body.', '.$dl->convertToDate($rep["timestamp"], true).', '.count($reply); + $x++; + } + } else { + if(Automod::isAccountsDisabled(1)) exit('-2'); + $userID = $gs->getUserID($_SESSION["accountID"], $gs->getAccountName($_SESSION["accountID"])); + $checkBan = $gs->getPersonBan($_SESSION["accountID"], $userID, 3); + if($checkBan) exit('-3'); + $body = base64_encode(strip_tags(ExploitPatch::rucharclean($_POST["body"]))); + if($enableCommentLengthLimiter && strlen(base64_decode($body)) > $maxCommentLength) exit("-1"); + $reply = $db->prepare("INSERT INTO replies (commentID, accountID, body, timestamp) VALUES (:cid, :acc, :body, :time)"); + $reply->execute([':cid' => $id, ':acc' => $_SESSION["accountID"], ':body' => $body, ':time' => time()]); + echo 1; + } +} else { + $reply = $db->prepare("SELECT * FROM replies WHERE commentID = :id ORDER BY replyID DESC"); + $reply->execute([':id' => $id]); + $reply = $reply->fetchAll(); + foreach($reply as &$rep) { + if($x > 1) echo ' | '; + $body = $rep["body"]; + if($enableCommentLengthLimiter) $body = base64_encode(substr(base64_decode($body), 0, $maxCommentLength)); + echo $rep["replyID"].', '.$id.', '.$gs->getAccountName($rep["accountID"]).', '.$body.', '.$dl->convertToDate($rep["timestamp"], true).', '.count($reply); + $x++; + } +} +?> \ No newline at end of file diff --git a/dashboard/reupload/songAdd.php b/dashboard/reupload/songAdd.php index 049bf161e..9ca9ea992 100644 --- a/dashboard/reupload/songAdd.php +++ b/dashboard/reupload/songAdd.php @@ -1,31 +1,135 @@ songReupload($_POST["url"]); - if($songID < 0){ - $errorDesc = $dl->getLocalizedString("songAddError$songID"); - $dl->printBox('

'.$dl->getLocalizedString("songAdd")."

-

".$dl->getLocalizedString("errorGeneric")." $songID ($errorDesc)

- ".$dl->getLocalizedString("tryAgainBTN")."","reupload"); +$dl->title($dl->getLocalizedString("songLink")); +$dl->printFooter('../'); +if(strpos($songEnabled, '2') === false) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'reupload')); +} +if(isset($_SESSION["accountID"]) AND $_SESSION["accountID"] != 0) { + if(!empty($_POST["url"])) { + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'reupload')); + } + $song = $_POST['url']; + $author = strip_tags(ExploitPatch::rucharclean($_POST["author"], 30)) ?: 'Reupload'; + $name = strip_tags(ExploitPatch::rucharclean($_POST["name"], 40)) ?: 'Unnamed'; + $songID = $gs->songReupload($song, $author, $name, $_SESSION["accountID"]); + if($songID == '-4' && strpos($songEnabled, '1') !== false) { + $songFile = $gs->getDownloadLinkWithCobalt($song); + if($songFile) { + $cobaltSongSize = strlen($songFile); + if($cobaltSongSize >= $songSize * 1024 * 1024) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("songAddError-5").'

+ +
+
', 'reupload')); + $freeID = false; + while(!$freeID) { + $songID = rand(99, 7999999); + $checkID = $db->prepare('SELECT count(*) FROM songs WHERE ID = :id'); // If randomized ID picks existing song ID + $checkID->execute([':id' => $songID]); + if($checkID->fetchColumn() == 0) $freeID = true; + } + file_put_contents(__DIR__.'/../songs/'.$songID.'.mp3', $songFile); + $duration = $gs->getAudioDuration(realpath(__DIR__.'/../songs/'.$songID.'.mp3')); + $duration = empty($duration) ? 0 : $duration; + $dirPath = dirname(dirname($_SERVER["REQUEST_URI"])); + if($dirPath == '/') $dirPath = ''; + $songUrl = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://".$_SERVER["HTTP_HOST"].$dirPath.'/songs/'.$songID.".mp3"; + $query = $db->prepare("INSERT INTO songs (ID, name, authorID, authorName, size, duration, download, hash, reuploadTime, reuploadID, isDisabled) VALUES (:id, :name, '9', :author, :size, :duration, :download, :hash, :reuploadTime, :reuploadID, :isDisabled)"); + $query->execute([':id' => $songID, ':name' => $name, ':download' => $songUrl, ':author' => $author, ':size' => round($cobaltSongSize / 1048576, 2), ':duration' => $duration, ':hash' => '', ':reuploadTime' => time(), ':reuploadID' => $_SESSION['accountID'], ':isDisabled' => ($preenableSongs ? 0 : 1)]); + } + } + if($songID < 0) { + $existed = str_replace('-3', '', $songID); + if($songID != $existed) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("songAddError-3").' '.$existed.'

+ +
+
', 'reupload')); + } + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("songAddError$songID").'

+ +
+
', 'reupload'); + } else { + $songArray = $db->prepare('SELECT * FROM songs WHERE ID = :ID'); + $songArray->execute([':ID' => $songID]); + $songArray = $songArray->fetch(); + $wholiked = '

0

'; + $whoused = '

0

'; + $songs = $dl->generateSongCard($songArray, $wholiked.$whoused, false); + $dl->printSong('
+

'.$dl->getLocalizedString("songAdded").'

+
+

'.$dl->getLocalizedString("yourNewSong").'

+
'.$songs.'
+ +
', 'reupload'); + } }else{ - $dl->printBox("

".$dl->getLocalizedString("songAdd")."

-

Song Reuploaded: $songID

- ".$dl->getLocalizedString("songAddAnotherBTN")."","reupload"); + $dl->printSong('
+

'.$dl->getLocalizedString("songAdd").'

+
+

'.$dl->getLocalizedString("songAddDesc").'

+
+
+
+ '.Captcha::displayCaptcha(true).' +
+
+ ', 'reupload'); } -}else{ - $dl->printBox('

'.$dl->getLocalizedString("songAdd").'

-
-
- - -
- -
',"reupload"); +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'reupload'); } ?> \ No newline at end of file diff --git a/dashboard/sfxs/index.php b/dashboard/sfxs/index.php new file mode 100644 index 000000000..d7de65d7b --- /dev/null +++ b/dashboard/sfxs/index.php @@ -0,0 +1,175 @@ +title($dl->getLocalizedString("sfxAdd")); +$dl->printFooter('../'); +if(!$sfxEnabled) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'reupload'); + die(); +} +if(isset($_SESSION["accountID"]) AND $_SESSION["accountID"] != 0) { +if($_FILES && $_FILES['filename']['error'] == UPLOAD_ERR_OK) { + if(!Captcha::validateCaptcha()) { + exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'reupload')); + } + $info = new finfo(FILEINFO_MIME); + $file_type = explode(';', $info->buffer(file_get_contents($_FILES['filename']['tmp_name'])))[0]; + $allowed = array("audio/mpeg", "audio/ogg", "audio/mp3"); + $db_fid = 1; + if(!in_array($file_type, $allowed)) $db_fid = -7; + else { + if($_FILES['filename']['size'] == 0) $db_fid = -6; + else { + if($_FILES['filename']['size'] >= $sfxSize * 1024 * 1024) $db_fid = -5; + else { + $server = (((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https" : "http")."://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; + $time = time(); + $types = array('.mp3', '.ogg', '.mpeg'); + $name = !empty(mb_substr(ExploitPatch::rucharclean($_POST['name']), 0, 40)) ? mb_substr(ExploitPatch::rucharclean($_POST['name']), 0, 40) : 'Unnamed SFX'; + $author = $gs->getAccountName($_SESSION['accountID']); + $size = $_FILES['filename']['size']; + $query = $db->prepare("INSERT INTO sfxs (name, authorName, size, download, reuploadTime, reuploadID) VALUES (:name, :author, :size, '', :reuploadTime, :reuploadID)"); + $query->execute([':name' => $name, ':author' => $author, ':size' => $size, ':reuploadTime' => $time, ':reuploadID' => $_SESSION['accountID']]); + $db_fid = $db->lastInsertId(); + $token = $convertEnabled ? $gs->randomString(16) : ''; + $convert = $gs->convertSFX($_FILES['filename'], $server, $name, $token); + $temp = $convert ? '_temp' : ''; + $song = $server.$db_fid.$temp.".ogg"; + move_uploaded_file($_FILES['filename']['tmp_name'], $db_fid.$temp.".ogg"); + $duration = $gs->getAudioDuration(realpath($db_fid.$temp.".ogg")); + $duration = empty($duration) ? 0 : $duration * 1000; + $query = $db->prepare('UPDATE sfxs SET download = :dl, milliseconds = :ms, token = :token, isDisabled = :isDisabled WHERE ID = :id'); + $query->execute([':dl' => $song, ':ms' => $duration, ':id' => $db_fid, ':token' => $token, ':isDisabled' => ($preenableSFXs ? 0 : 1)]); + $songArray = $db->prepare('SELECT * FROM sfxs WHERE ID = :ID'); + $songArray->execute([':ID' => $db_fid]); + $songArray = $songArray->fetch(); + $songs = $dl->generateSongCard($songArray, '', false); + } + } + } + if($db_fid < 0) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("sfxAddError$db_fid").'

+ +
+
', 'reupload'); + } else { + $dl->printSong('
+

'.$dl->getLocalizedString("sfxAdded").'

+
+

'.$dl->getLocalizedString("yourNewSFX").'

+
'.$songs.'
+ +
', 'reupload'); + } + } else { + $dl->printSong(' +
+

'.$dl->getLocalizedString("sfxAdd").'

+
+

'.$dl->getLocalizedString("sfxAddDesc").'

+
+ + +
+
+ '.Captcha::displayCaptcha(true).' +
+ ', 'reupload'); +} +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'reupload'); +} \ No newline at end of file diff --git a/dashboard/sfxs/update.php b/dashboard/sfxs/update.php new file mode 100644 index 000000000..53bbeddc0 --- /dev/null +++ b/dashboard/sfxs/update.php @@ -0,0 +1,28 @@ + false, 'code' => 0, 'error' => 'Invalid params.'])); + $check = $db->prepare('SELECT ID FROM sfxs WHERE token = :token'); + $check->execute([':token' => $token]); + $check = $check->fetchColumn(); + if(!$check) exit(json_encode(['success' => false, 'code' => 1, 'error' => 'Invalid token.'])); + $info = new finfo(FILEINFO_MIME); + $file_type = explode(';', $info->buffer(file_get_contents($_FILES['file']['tmp_name'])))[0]; + if($file_type != 'audio/ogg' || $_FILES['file']['size'] >= $sfxSize * 1024 * 1024 || $_FILES['file']['size'] == 0) exit(json_encode(['success' => false, 'code' => 2, 'error' => 'Invalid file.'])); + move_uploaded_file($_FILES['file']['tmp_name'], $check.'.ogg'); + if(file_exists($check.'_temp.ogg')) unlink($check.'_temp.ogg'); + $song = $server.$check.".ogg"; + $change = $db->prepare('UPDATE sfxs SET token = "", download = :dl WHERE ID = :id'); + if($change->execute([':dl' => $song, ':id' => $check])) exit(json_encode(['success' => true])); + exit(json_encode(['success' => false, 'code' => 3, 'error' => 'Something went wrong.'])); +} +exit(json_encode(['success' => false, 'code' => 0, 'error' => 'Invalid params.'])); +?> \ No newline at end of file diff --git a/dashboard/songs/index.php b/dashboard/songs/index.php new file mode 100644 index 000000000..4c3ebfd34 --- /dev/null +++ b/dashboard/songs/index.php @@ -0,0 +1,195 @@ +title($dl->getLocalizedString("songAdd")); +$dl->printFooter('../'); +if(strpos($songEnabled, '1') === false) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("pageDisabled").'

+ +
+
', 'reupload'); + die(); +} +if(isset($_SESSION["accountID"]) AND $_SESSION["accountID"] != 0) { +if($_FILES && $_FILES['filename']['error'] == UPLOAD_ERR_OK) { + if(!Captcha::validateCaptcha()) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'reupload'); + die(); + } else { + $info = new finfo(FILEINFO_MIME); + $file_type = explode(';', $info->buffer(file_get_contents($_FILES['filename']['tmp_name'])))[0]; + $allowed = array("audio/mpeg", "audio/ogg", "audio/mp3"); + if(!in_array($file_type, $allowed)) $db_fid = -7; + else { + if($_FILES['filename']['size'] == 0) $db_fid = -6; + else { + $maxsize = $songSize * 1024 * 1024; + if($_FILES['filename']['size'] >= $maxsize) $db_fid = -5; + else { + $freeID = false; + while(!$freeID) { + $db_fid = rand(99, 7999999); + $checkID = $db->prepare('SELECT count(*) FROM songs WHERE ID = :id'); // If randomized ID picks existing song ID + $checkID->execute([':id' => $db_fid]); + if($checkID->fetchColumn() == 0) $freeID = true; + } + move_uploaded_file($_FILES['filename']['tmp_name'], "$db_fid.mp3"); + $duration = $gs->getAudioDuration(realpath("$db_fid.mp3")); + $duration = empty($duration) ? 0 : $duration; + $size = ($_FILES['filename']['size'] / 1048576); + $size = round($size, 2); + $hash = ""; + $types = array('.mp3', '.ogg', '.mpeg'); + $nAu = explode(' - ', str_replace($types, '', $_FILES['filename']['name']), 2); + if(empty($nAu[1])) $nAu = explode(' — ', str_replace($types, '', $_FILES['filename']['name']), 2); + if(!empty($nAu[1])) { + if(!empty($_POST['name'])) $name = ExploitPatch::rucharclean($_POST['name']); + else $name = trim(ExploitPatch::rucharclean($nAu[1])); + if(!empty($_POST['author'])) $author = ExploitPatch::rucharclean($_POST['author']); + else $author = trim(ExploitPatch::rucharclean($nAu[0])); + } else { + $name = ExploitPatch::rucharclean($_POST['name']); + $author = ExploitPatch::rucharclean($_POST['author']); + } + if(empty($name)) $name = "Unnamed"; + if(empty($author)) $author = "Reupload"; + $accountID = $_SESSION["accountID"]; + $name = mb_substr($name, 0, 40); + $author = mb_substr($author, 0, 40); + $song = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]".$db_fid.".mp3"; + $query = $db->prepare("INSERT INTO songs (ID, name, authorID, authorName, size, duration, download, hash, reuploadTime, reuploadID, isDisabled) VALUES (:id, :name, '9', :author, :size, :duration, :download, :hash, :reuploadTime, :reuploadID, :isDisabled)"); + $query->execute([':id' => $db_fid, ':name' => $name, ':download' => $song, ':author' => $author, ':size' => $size, ':duration' => $duration, ':hash' => $hash, ':reuploadTime' => time(), ':reuploadID' => $accountID, ':isDisabled' => ($preenableSongs ? 0 : 1)]); + $songArray = $db->prepare('SELECT * FROM songs WHERE ID = :ID'); + $songArray->execute([':ID' => $db_fid]); + $songArray = $songArray->fetch(); + $wholiked = '

0

'; + $whoused = '

0

'; + $songs = $dl->generateSongCard($songArray, $wholiked.$whoused, false); + } + } + } + if($db_fid < 0) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("songAddError$db_fid").'

+ +
+
', 'reupload'); + } else { + $dl->printSong('
+

'.$dl->getLocalizedString("songAdded").'

+
+

'.$dl->getLocalizedString("yourNewSong").'

+
'.$songs.'
+ +
', 'reupload'); + } + } +} else { + $dl->printSong(' +
+

'.$dl->getLocalizedString("songAdd").'

+
+

'.$dl->getLocalizedString("songAddDesc").'

+
+ + +
+
+
+ '.Captcha::displayCaptcha(true).' +
+ ', 'reupload'); +} +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'reupload'); +} +?> \ No newline at end of file diff --git a/dashboard/stats/SFXList.php b/dashboard/stats/SFXList.php new file mode 100644 index 000000000..a49cdbec6 --- /dev/null +++ b/dashboard/stats/SFXList.php @@ -0,0 +1,78 @@ +title($dl->getLocalizedString("sfxs")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +$srcbtn = $favs = $meta = ""; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) { + $q = is_numeric(trim(ExploitPatch::rucharclean($_GET["search"]))) ? "ID LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'" : "(name LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%' OR authorName LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%')"; + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM sfxs WHERE isDisabled = 0 AND $q ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM sfxs WHERE isDisabled = 0 ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); +} +$x = 0; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +foreach($result as &$action) { + $x++; + $songs .= $dl->generateSFXCard($action); +} +$pagel = '
+

'.$dl->getLocalizedString("sfxs").'

+
+ '.$songs.' +
+
+ + + '.$srcbtn.' +
+
'; +if(!empty(trim($_GET["search"]))) $query = $db->prepare("SELECT count(*) FROM sfxs WHERE isDisabled = 0 AND $q"); +else $query = $db->prepare("SELECT count(*) FROM sfxs WHERE isDisabled = 0"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/accountsList.php b/dashboard/stats/accountsList.php new file mode 100644 index 000000000..cb805a3f9 --- /dev/null +++ b/dashboard/stats/accountsList.php @@ -0,0 +1,138 @@ +title($dl->getLocalizedString("accounts")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $members = ""; +if(!isset($_GET["search"]) OR empty(trim(ExploitPatch::remove($_GET["search"])))) { + $query = $db->prepare("SELECT * FROM accounts INNER JOIN users WHERE isActive = 1 AND accounts.accountID = users.extID ORDER BY accountID ASC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
'); + die(); + } +} else { + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM accounts INNER JOIN users WHERE accounts.userName LIKE '%".trim(ExploitPatch::remove($_GET["search"]))."%' AND isActive = 1 AND accounts.accountID = users.extID ORDER BY accountID ASC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} +$x = $page + 1; +foreach($result as &$action){ + $clan = $own = ''; + $accUserID = $action["userID"]; + $accountID = $action["accountID"].' | '.$accUserID; + if($action["accountID"] == $accUserID) $accountID = $action["accountID"]; + $query = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accid"); + $query->execute([':accid' => $accountID]); + $resultPls = $query->fetch(); + if(!$resultPls) $resultRole = $dl->getLocalizedString("player"); + else { + $resultRole = $resultPls["roleID"]; + if(empty($resultRole)) { + $resultRole = $dl->getLocalizedString("player"); + } else { + $query = $db->prepare("SELECT roleName FROM roles WHERE roleID = :id"); + $query->execute([':id' => $resultRole]); + $resultRole = $query->fetch()["roleName"]; + } + } + if($action["clan"] != 0) { + $claninfo = $gs->getClanInfo($action["clan"]); + if($claninfo["clanOwner"] == $action["accountID"]) $own = ''; + $clan = '

'.$claninfo["clan"].$own.'

'; + } + // Avatar management + $iconType = ($action['iconType'] > 8) ? 0 : $action['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $action['accIcon']], 1 => ['type' => 'ship', 'value' => $action['accShip']], 2 => ['type' => 'ball', 'value' => $action['accBall']], 3 => ['type' => 'ufo', 'value' => $action['accBird']], 4 => ['type' => 'wave', 'value' => $action['accDart']], 5 => ['type' => 'robot', 'value' => $action['accRobot']], 6 => ['type' => 'spider', 'value' => $action['accSpider']], 7 => ['type' => 'swing', 'value' => $action['accSwing']], 8 => ['type' => 'jetpack', 'value' => $action['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'avatar'; + // Badge management + $badgeImg = ''; + $queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); + $queryRoleID->execute([':accountID' => $accountID]); + if($roleAssignData = $queryRoleID->fetch(PDO::FETCH_ASSOC)) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } + } + $stats = $dl->createProfileStats($action['stars'], $action['moons'], $action['diamonds'], $action['coins'], $action['userCoins'], $action['demons'], $action['creatorPoints'], 0); + $registerDate = date("d.m.Y", $action["registerDate"]); + if($action['userName'] == "Undefined") $action['userName'] = $gs->getAccountName($action["accountID"]); + $members .= '
+
+
+ +
+
'.$stats.'
+
+

'.$dl->getLocalizedString("accountID").': '.$accountID.'

+

'.$dl->getLocalizedString("registerDate").': '.$registerDate.'

+
+
'; + $x++; +} +$pagel = '
+

'.$dl->getLocalizedString("accounts").'

+
+ '.$members.' +
+
+ + + '.$srcbtn.' +
+
'; +if(empty(trim(ExploitPatch::remove($_GET["search"])))) $query = $db->prepare("SELECT count(*) FROM accounts INNER JOIN users WHERE isActive = 1 AND accounts.accountID = users.extID"); +else $query = $db->prepare("SELECT count(*) FROM accounts INNER JOIN users WHERE accounts.userName LIKE '%".trim(ExploitPatch::remove($_GET["search"]))."%' AND isActive = 1 AND accounts.accountID = users.extID"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/addQuests.php b/dashboard/stats/addQuests.php new file mode 100644 index 000000000..b433f300b --- /dev/null +++ b/dashboard/stats/addQuests.php @@ -0,0 +1,216 @@ +title($dl->getLocalizedString("addQuest")); +$dl->printFooter('../'); +$allQuests = ''; +if($gs->checkPermission($_SESSION["accountID"], "toolQuestsCreate")) { +if(!empty($_POST["type"]) AND !empty($_POST["amount"]) AND !empty($_POST["reward"]) AND !empty($_POST["names"])){ + if(!Captcha::validateCaptcha()) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidCaptcha").'

+ +
+
', 'mod'); + die(); + } + $type = ExploitPatch::number($_POST["type"]); + $amount = ExploitPatch::number($_POST["amount"]); + $reward = ExploitPatch::number($_POST["reward"]); + $name = ExploitPatch::remove($_POST["names"]); + $accountID = $_SESSION["accountID"]; + if(!is_numeric($type) OR !is_numeric($amount) OR !is_numeric($reward) OR $type > 3 OR $type < 1){ + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("invalidPost").'

+ +
+
', 'mod'); + die(); + } + $query = $db->prepare("INSERT INTO quests (type, amount, reward, name) VALUES (:type,:amount,:reward,:name)"); + $query->execute([':type' => $type, ':amount' => $amount, ':reward' => $reward, ':name' => $name]); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3, value4) VALUES ('25',:value,:timestamp,:account,:amount,:reward,:name)"); + $query->execute([':value' => $type, ':timestamp' => time(), ':account' => $accountID, ':amount' => $amount, ':reward' => $reward, ':name' => $name]); + $success = $dl->getLocalizedString("questsSuccess").' '. $name. '!'; + if($db->lastInsertId() < 3) { + $dl->printSong('
+

'.$dl->getLocalizedString("addQuest").'

+
+

'.$success.'

+

'.$dl->getLocalizedString("fewMoreQuests").'

+ +
+
', 'mod'); + } else { + $dl->printSong('
+

'.$dl->getLocalizedString("addQuest").'

+
+

'.$success.'

+ +
+
', 'mod'); + } + } else { + $quests = $db->prepare("SELECT * FROM quests ORDER BY ID ASC"); + $quests->execute(); + $quests = $quests->fetchAll(); + foreach($quests as &$quest) { + switch($quest["type"]) { + case 1: + $questType = ' '.$dl->getLocalizedString("orbs"); + break; + case 2: + $questType = ' '.$dl->getLocalizedString("coins"); + break; + case 3: + $questType = ' '.$dl->getLocalizedString("stars"); + break; + } + $allQuests .= ''; + } + $dl->printSong('
+
+ '.$allQuests.' +
+
+

'.$dl->getLocalizedString("addQuest").'

+
+

'.$dl->getLocalizedString("addQuestDesc").'

+
+
+
+
+
+ + +
+ ', 'mod'); + Captcha::displayCaptcha(); + echo ' +
+
+'; +} +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod'); +} +?> \ No newline at end of file diff --git a/dashboard/stats/banList.php b/dashboard/stats/banList.php new file mode 100644 index 000000000..f5139a54f --- /dev/null +++ b/dashboard/stats/banList.php @@ -0,0 +1,116 @@ +title($dl->getLocalizedString("banList")); +$dl->printFooter('../'); +if(!$gs->checkPermission($_SESSION["accountID"], "dashboardModTools")) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod')); +if(!empty($_POST['banID'])) { + $banID = ExploitPatch::number($_POST['banID']); + if(!empty($_POST['unban'])) $gs->unbanPerson($banID, $_SESSION['accountID']); + else { + $reason = trim(ExploitPatch::rucharclean($_POST['reason'])); + $expires = (new DateTime($_POST['expires']))->getTimestamp(); + if(time() < $expires) $gs->changeBan($banID, $_SESSION['accountID'], $reason, $expires); + } +} +$query = $db->prepare('SELECT * FROM bans WHERE isActive != 0 ORDER BY timestamp DESC'); +$query->execute(); +$result = $query->fetchAll(); +$x = $page + 1; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'mod'); + die(); +} +foreach($result as &$action) { + $person = htmlspecialchars($action['person']); + switch($action['personType']) { + case 0: + $personUsername = $gs->getAccountName($person); + $personText = ''; + break; + case 1: + $personUsername = $gs->getUserName($person); + $personText = '

'.$personUsername.'

'; + break; + case 2: + $personText = '

'.$person.'

'; + break; + } + $personTypes = ['accountID', 'userID', 'IP']; + $personTypeText = $dl->getLocalizedString($personTypes[$action['personType']]); + $banTypes = ['playerTop', 'creatorTop', 'levelUploading', 'commentBan', 'account']; + $banTypeText = $dl->getLocalizedString($banTypes[$action['banType']]); + $reason = !empty($action['reason']) ? htmlspecialchars(base64_decode($action['reason'])) : ''.$dl->getLocalizedString('noReason').''; + $expires = $dl->convertToDate($action['expires'], true); + if($action['modID'] != 0) { + $modUsername = $gs->getAccountName($action['modID']); + $modUsernameButton = ''; + } else $modUsernameButton = $dl->getLocalizedString('system'); + $stats = '

'.$modUsernameButton.'

+

'.$personTypeText.'

+

'.$banTypeText.'

+

'.$expires.'

'; + $manage = ''; + $bans .= '
+
+
+
+ '.$personText.$manage.' +
+

'.$reason.'

+
+
'.$stats.'
+

'.$dl->getLocalizedString("ID").': '.$action['banID'].'

'.$dl->getLocalizedString("date").': '.$dl->convertToDate($action['timestamp'], true).'

+
+
'; + $x++; +} +$pagel = '
+

'.$dl->getLocalizedString("banList").'

+
+ '.$bans.' +
+
'; +$dl->printPage($pagel, true, "mod"); +?> \ No newline at end of file diff --git a/dashboard/stats/dailyTable.php b/dashboard/stats/dailyTable.php index 34ba3d4c5..19e85badf 100644 --- a/dashboard/stats/dailyTable.php +++ b/dashboard/stats/dailyTable.php @@ -2,75 +2,57 @@ session_start(); require "../incl/dashboardLib.php"; $dl = new dashboardLib(); -require "../../incl/lib/mainLib.php"; +require_once "../".$dbPath."incl/lib/mainLib.php"; $gs = new mainLib(); -require "../../incl/lib/connection.php"; -/* - generating dailytable -*/ +require "../".$dbPath."incl/lib/connection.php"; +require "../".$dbPath."incl/lib/exploitPatch.php"; +$dl->title($dl->getLocalizedString("dailyTable")); +$dl->printFooter('../'); if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ $page = ($_GET["page"] - 1) * 10; $actualpage = $_GET["page"]; -}else{ +} else { $page = 0; $actualpage = 1; } -$dailytable = ""; -//getting data -$query = $db->prepare("SELECT feaID, levelID, timestamp FROM dailyfeatures WHERE timestamp < :time ORDER BY feaID DESC LIMIT 10 OFFSET $page"); +$query = $db->prepare("SELECT * FROM dailyfeatures WHERE timestamp < :time ORDER BY feaID DESC LIMIT 10 OFFSET $page"); $query->execute([':time' => time()]); $result = $query->fetchAll(); $query = $db->prepare("SELECT count(*) FROM dailyfeatures WHERE timestamp < :time"); $query->execute([':time' => time()]); $dailycount = $query->fetchColumn(); $x = $dailycount - $page; -//printing data +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'stats'); + die(); +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); foreach($result as &$daily){ - //getting level data - $query = $db->prepare("SELECT levelName,userID,starStars,coins FROM levels WHERE levelID = :levelID"); + $typeArray = ['Daily', 'Weekly']; + $type = $typeArray[$daily["type"]]; + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); $query->execute([':levelID' => $daily["levelID"]]); $level = $query->fetch(); - if($query->rowCount() == 0){ - $level["levelName"] = $dl->getLocalizedString("deletedLevel"); - $level["userID"] = 0; - $level["starStars"] = -1; - $level["coins"] = -1; - } - $dailytable .= ' - '.$x.' - '.$daily["levelID"].' - '.$level["levelName"].' - '.$gs->getUserName($level["userID"]).' - '.$level["starStars"].' - '.$level["coins"].' - '.$dl->convertToDate($daily["timestamp"]).' - '; - $x--; - echo ""; + $dtt = $dl->convertToDate($daily['timestamp'], true); + if(!empty($level)) { + $dailyl = '

'.$type.'

'; + $dt = '

'.$dtt.'

'; + $levels .= $dl->generateLevelsCard($level, $modcheck, $dailyl.$dt); + } else $levels .= '

'.$dl->getLocalizedString("deletedLevel").'

'; } -/* - bottom row -*/ +$pagel = '
+

'.$dl->getLocalizedString("dailyTable").'

+
+ '.$levels.' +
+
'; $pagecount = ceil($dailycount / 10); $bottomrow = $dl->generateBottomRow($pagecount, $actualpage); -/* - printing -*/ -$dl->printPage(' - - - - - - - - - - - - - '.$dailytable.' - -
#'.$dl->getLocalizedString("ID").''.$dl->getLocalizedString("name").''.$dl->getLocalizedString("author").''.$dl->getLocalizedString("stars").''.$dl->getLocalizedString("userCoins").''.$dl->getLocalizedString("time").'
' -.$bottomrow, true, "stats"); +$dl->printPage($pagel.$bottomrow, true, "stats"); ?> \ No newline at end of file diff --git a/dashboard/stats/deleteSong.php b/dashboard/stats/deleteSong.php new file mode 100644 index 000000000..0361d98ba --- /dev/null +++ b/dashboard/stats/deleteSong.php @@ -0,0 +1,38 @@ + false, 'error' => '0'])); +else { + if($songid == 0) die(json_encode(['success' => false, 'error' => '-1'])); + $query = $db->prepare("SELECT reuploadID, isDisabled FROM ".$type." WHERE ID = :sid"); + $query->execute([':sid' => $songid]); + $song = $query->fetch(); + if(!$song) die(json_encode(['success' => false, 'error' => '-2'])); + else { + $check = $gs->checkPermission($accID, "dashboardManageSongs") ?: $accID == $song['reuploadID']; + if(!$check) die(json_encode(['success' => false, 'error' => '-3'])); + if(!isset($_GET['disable'])) { + $query = $db->prepare("DELETE FROM ".$type." WHERE ID = :sid"); + $query->execute([':sid' => $songid]); + if(file_exists("../".$type."/".$songid.".".$format)) unlink("../".$type."/".$songid.".".$format); + if(file_exists("../".$type."/".$songid."_temp.".$format)) unlink("../".$type."/".$songid."_temp.".$format); + } else { + $query = $db->prepare("UPDATE ".$type." SET isDisabled = :isDisabled WHERE ID = :sid"); + $query->execute([':sid' => $songid, ':isDisabled' => ($song['isDisabled'] == 0 ? 1 : 0)]); + } + die(json_encode(['success' => true])); + } +} +?> \ No newline at end of file diff --git a/dashboard/stats/disabledSFXsList.php b/dashboard/stats/disabledSFXsList.php new file mode 100644 index 000000000..6b2c1202e --- /dev/null +++ b/dashboard/stats/disabledSFXsList.php @@ -0,0 +1,82 @@ +title($dl->getLocalizedString("disabledSFXs")); +$dl->printFooter('../'); +if(!$gs->checkPermission($_SESSION["accountID"], "dashboardManageSongs")) $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $favs = $meta = ""; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) { + $q = is_numeric(trim(ExploitPatch::rucharclean($_GET["search"]))) ? "ID LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'" : "name LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'"; + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM sfxs WHERE isDisabled != 0 AND $q ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM sfxs WHERE isDisabled != 0 ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); +} +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +foreach($result as &$action) $songs .= $dl->generateSFXCard($action); +$pagel = '
+

'.$dl->getLocalizedString("disabledSFXs").'

+
+ '.$songs.' +
+
+ + + '.$srcbtn.' +
+
'; +if(!empty(trim($_GET["search"]))) $query = $db->prepare("SELECT count(*) FROM sfxs WHERE isDisabled != 0 AND $q"); +else $query = $db->prepare("SELECT count(*) FROM sfxs WHERE isDisabled != 0"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/disabledSongsList.php b/dashboard/stats/disabledSongsList.php new file mode 100644 index 000000000..ea2b9aac4 --- /dev/null +++ b/dashboard/stats/disabledSongsList.php @@ -0,0 +1,84 @@ +title($dl->getLocalizedString("disabledSongs")); +$dl->printFooter('../'); +if(!$gs->checkPermission($_SESSION["accountID"], "dashboardManageSongs")) $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $favs = $meta = ""; +$ngw = $_GET["ng"] == 1 ? '' : 'AND reuploadID > 0'; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) { + $q = is_numeric(trim(ExploitPatch::rucharclean($_GET["search"]))) ? "ID LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'" : "(name LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%' OR authorName LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%')"; + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM songs WHERE isDisabled != 0 AND $q $ngw ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM songs WHERE isDisabled != 0 $ngw ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); +} +$x = 0; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +foreach($result as &$action) $songs .= $dl->generateSongCard($action); +$pagel = '
+

'.$dl->getLocalizedString("disabledSongs").'

+
+ '.$songs.' +
+
+ + + '.$srcbtn.' +
+
'; +if(!empty(trim($_GET["search"]))) $query = $db->prepare("SELECT count(*) FROM songs WHERE isDisabled != 0 AND $q $ngw"); +else $query = $db->prepare("SELECT count(*) FROM songs WHERE isDisabled != 0 $ngw"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/favourite.php b/dashboard/stats/favourite.php new file mode 100644 index 000000000..e835ee742 --- /dev/null +++ b/dashboard/stats/favourite.php @@ -0,0 +1,25 @@ +prepare('SELECT * FROM songs WHERE ID = :ID'); +$check->execute([':ID' => $id]); +$check = $check->fetchColumn(); +if(!empty($id) AND $_SESSION["accountID"] != 0 AND $check AND $check['isDisabled'] == 0) { + $favourites = $db->prepare("SELECT * FROM favsongs WHERE songID = :id AND accountID = :aid"); + $favourites->execute([':id' => $id, ':aid' => $_SESSION["accountID"]]); + $favourites = $favourites->fetch(); + if(!empty($favourites)) { + $favourites = $db->prepare("DELETE FROM favsongs WHERE songID = :id AND accountID = :aid"); + $favourites->execute([':id' => $id, ':aid' => $_SESSION["accountID"]]); + exit("1"); + } else { + $favourites = $db->prepare("INSERT INTO favsongs (songID, accountID, timestamp) VALUES (:id, :aid, :time)"); + $favourites->execute([':id' => $id, ':aid' => $_SESSION["accountID"], ':time' => time()]); + exit("1"); + } +} else exit("-1"); +?> \ No newline at end of file diff --git a/dashboard/stats/favouriteSongs.php b/dashboard/stats/favouriteSongs.php new file mode 100644 index 000000000..34de543de --- /dev/null +++ b/dashboard/stats/favouriteSongs.php @@ -0,0 +1,53 @@ +title($dl->getLocalizedString("favouriteSongs")); +$dl->printFooter('../'); +if(!isset($_SESSION["accountID"]) || $_SESSION["accountID"] == 0) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$dailytable = $songs = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$query = $db->prepare("SELECT * FROM favsongs INNER JOIN songs on favsongs.songID = songs.ID WHERE favsongs.accountID = :id ORDER BY favsongs.ID DESC LIMIT 10 OFFSET $page"); +$query->execute([':id' => $_SESSION["accountID"]]); +$result = $query->fetchAll(); +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'account'); + die(); +} +foreach($result as &$action) $songs .= $dl->generateSongCard($action); +$pagel = '
+

'.$dl->getLocalizedString("favouriteSongs").'

+
+ '.$songs.' +
'; +$query = $db->prepare("SELECT * FROM favsongs INNER JOIN songs on favsongs.songID = songs.ID WHERE favsongs.accountID = :id ORDER BY favsongs.ID DESC"); +$query->execute([':id' => $_SESSION["accountID"]]); +$result = $query->fetchAll(); +$pagecount = ceil(count($result) / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "account"); +?> \ No newline at end of file diff --git a/dashboard/stats/gauntletTable.php b/dashboard/stats/gauntletTable.php index a0bb765ca..59534f47b 100644 --- a/dashboard/stats/gauntletTable.php +++ b/dashboard/stats/gauntletTable.php @@ -2,92 +2,73 @@ session_start(); require "../incl/dashboardLib.php"; $dl = new dashboardLib(); -require "../../incl/lib/mainLib.php"; +require_once "../".$dbPath."incl/lib/mainLib.php"; $gs = new mainLib(); -/* - generating packtable -*/ -include "../../incl/lib/connection.php"; +$dl->title($dl->getLocalizedString("gauntletTable")); +$dl->printFooter('../'); +require "../".$dbPath."incl/lib/connection.php"; +require "../".$dbPath."incl/lib/exploitPatch.php"; if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ $page = ($_GET["page"] - 1) * 10; $actualpage = $_GET["page"]; -}else{ +} else { $page = 0; $actualpage = 1; } -$x = $page + 1; -$gauntlettable = ""; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$x = 1; +$packtable = ""; $query = $db->prepare("SELECT * FROM gauntlets ORDER BY ID ASC LIMIT 10 OFFSET $page"); $query->execute(); $result = $query->fetchAll(); -foreach($result as &$gauntlet){ - $lvlarray = array(); - for ($y = 1; $y < 6; $y++) { - $lvlarray[] = $gauntlet["level".$y]; - } +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +foreach($result as &$pack) { + $lvlarray = [$pack["level1"], $pack["level2"], $pack["level3"], $pack["level4"], $pack["level5"]]; $lvltable = ""; - foreach($lvlarray as &$lvl){ - $query = $db->prepare("SELECT levelID,levelName,starStars,userID,coins FROM levels WHERE levelID = :levelID"); + foreach($lvlarray as &$lvl) { + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); $query->execute([':levelID' => $lvl]); - $level = $query->fetch(); - $lvltable .= " - ".$level["levelID"]." - ".$level["levelName"]." - ".$gs->getUserName($level["userID"])." - ".$level["starStars"]." - ".$level["coins"]." - "; + $action = $query->fetch(); + if(!$action) continue; + $lvltable .= $dl->generateLevelsCard($action, $modcheck); } - $gauntlettable .= " - $x - ".$gs->getGauntletName($gauntlet["ID"]).' - - - - '; + $packtable .= '
+
+
+

'.$gs->getGauntletName($pack["ID"]).' Gauntlet

+
+
+ '.$lvltable.' +
+
+

ID: '.$pack["ID"].'

+ '.($pack["timestamp"] != 0 ? '

'.$dl->getLocalizedString('date').': '.$dl->convertToDate($pack["timestamp"], true).'

' : '').' +
+
+
'; $x++; - echo ""; } -/* - bottom row -*/ -//getting count -$query = $db->prepare("SELECT count(*) FROM gauntlets"); -$query->execute(); -$gauntletcount = $query->fetchColumn(); -$pagecount = ceil($gauntletcount / 10); +$pagecount = ceil($dailycount / 10); $bottomrow = $dl->generateBottomRow($pagecount, $actualpage); -/* - printing -*/ -$dl->printPage(' - - - - - - - - - '.$gauntlettable.' - -
#'.$dl->getLocalizedString("name").''.$dl->getLocalizedString("levels").'
' -.$bottomrow, true, "stats"); +$dl->printPage('

'.$dl->getLocalizedString('gauntletTable').'

+
+ '.$packtable.' +
+
'.$bottomrow, 'browse'); ?> \ No newline at end of file diff --git a/dashboard/stats/index.php b/dashboard/stats/index.php new file mode 100644 index 000000000..742d3251d --- /dev/null +++ b/dashboard/stats/index.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/dashboard/stats/levelComments.php b/dashboard/stats/levelComments.php new file mode 100644 index 000000000..c4ff04606 --- /dev/null +++ b/dashboard/stats/levelComments.php @@ -0,0 +1,111 @@ +title($dl->getLocalizedString("levelComments")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["levelID"])) $_GET["levelID"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$levelID = ExploitPatch::number($_GET['levelID']); +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +$commentDeleteCheck = $gs->checkPermission($_SESSION["accountID"], "actionDeleteComment"); +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID".(!$modcheck ? ' AND unlisted = 0' : '')); +$query->execute([':levelID' => $levelID]); +$level = $query->fetch(); +if(empty($level)) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse')); +if($_SESSION['accountID'] != 0) { + if(isset($_POST['deleteCommentID'])) { + $commentID = ExploitPatch::number($_POST['deleteCommentID']); + $query = $db->prepare("SELECT * FROM comments WHERE commentID = :deleteCommentID"); + $query->execute([':deleteCommentID' => $commentID]); + $comment = $query->fetch(); + $creatorAccID = $gs->getExtID($comment['userID']); + if($comment && ($commentDeleteCheck || $_SESSION['accountID'] == $creatorAccID)) { + $query = $db->prepare("DELETE FROM comments WHERE commentID = :deleteCommentID"); + $query->execute([':deleteCommentID' => $commentID]); + $gs->logAction($_SESSION['accountID'], 13, $comment['userName'], $comment['comment'], $creatorAccID, $commentID, ($comment['likes'] - $comment['dislikes']), $comment['levelID']); + } + } + if(isset($_POST['commentText'])) { + if(!Automod::isLevelsDisabled(1)) { + $commentText = trim(ExploitPatch::rucharclean($_POST['commentText'])); + $commentLength = mb_strlen($commentText); + if($enableCommentLengthLimiter && $commentLength > $maxCommentLength) $alertText = sprintf($dl->getLocalizedString('cantPostCommentsAboveChars'), $maxCommentLength); + elseif($commentText) { + $commentText = ExploitPatch::url_base64_encode($commentText); + $userName = $gs->getAccountName($_SESSION['accountID']); + $query = $db->prepare("INSERT INTO comments (userName, comment, levelID, userID, timeStamp, percent) VALUES (:userName, :comment, :levelID, :userID, :uploadDate, :percent)"); + $query->execute([':userName' => $userName, ':comment' => $commentText, ':levelID' => $levelID, ':userID' => $gs->getUserID($_SESSION['accountID'], $userName), ':uploadDate' => time(), ':percent' => 0]); + $gs->logAction($_SESSION['accountID'], 15, $userName, $commentText, $db->lastInsertId(), $levelID); + } + } else $alertText = $dl->getLocalizedString('commentingIsDisabled'); + } +} +$query = $db->prepare("SELECT * FROM comments WHERE levelID = :levelID ORDER BY timestamp DESC LIMIT 10 OFFSET $page"); +$query->execute([':levelID' => $levelID]); +$result = $query->fetchAll(); +if(isset($_SERVER["HTTP_REFERER"])) $back = '
+ +
'; +else $back = ''; +foreach($result as &$action) $comments .= $dl->generateCommentsCard($action, $commentDeleteCheck); +if(empty($comments)) $comments = '
+ +

'.$dl->getLocalizedString('noComments').'

+
'; +$pagel = '
+

'.$back.$dl->getLocalizedString("levelComments").'

+
+ '.$dl->generateLevelsCard($level, $modcheck).' +
+
+ '.$comments.' +
+
+'.($_SESSION['accountID'] != 0 ? '
+
+ +
+ +
' : '').' +'; +/* + bottom row +*/ +//getting count +$query = $db->prepare("SELECT count(*) FROM comments WHERE levelID = :levelID"); +$query->execute([':levelID' => $levelID]); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/levelLeaderboards.php b/dashboard/stats/levelLeaderboards.php new file mode 100644 index 000000000..7fca67a6f --- /dev/null +++ b/dashboard/stats/levelLeaderboards.php @@ -0,0 +1,131 @@ +title($dl->getLocalizedString("levelLeaderboards")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["levelID"])) $_GET["levelID"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$levelID = ExploitPatch::number($_GET['levelID']); +$type = ExploitPatch::number($_GET['type']) ?: ($_SESSION['accountID'] != 0 ? 0 : 1); +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +$leaderboardDeleteCheck = $gs->checkPermission($_SESSION["accountID"], "dashboardDeleteLeaderboards"); +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID".(!$modcheck ? ' AND unlisted = 0' : '')); +$query->execute([':levelID' => $levelID]); +$level = $query->fetch(); +if(empty($level) || $level['levelLength'] == 5) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse')); +if($_SESSION['accountID'] != 0) { + if(isset($_POST['deleteLeaderboardID'])) { + $deleteLeaderboardID = ExploitPatch::number($_POST['deleteLeaderboardID']); + $query = $db->prepare("SELECT count(*) FROM levelscores WHERE scoreID = :deleteLeaderboardID"); + $query->execute([':deleteLeaderboardID' => $deleteLeaderboardID]); + $leaderboard = $query->fetch(); + if($leaderboard && $leaderboardDeleteCheck) { + $query = $db->prepare("DELETE FROM levelscores WHERE scoreID = :deleteLeaderboardID"); + $query->execute([':deleteLeaderboardID' => $deleteLeaderboardID]); + } + } +} +switch($type) { + case 1: + $query2 = $db->prepare("SELECT * FROM levelscores WHERE dailyID = 0 AND levelID = :levelID ORDER BY percent DESC, uploadDate ASC LIMIT 10 OFFSET $page"); + $countQuery = "SELECT count(*) FROM levelscores WHERE dailyID = 0 AND levelID = :levelID"; + $query2args = [':levelID' => $levelID]; + break; + case 2: + $query2 = $db->prepare("SELECT * FROM levelscores WHERE dailyID = 0 AND levelID = :levelID AND uploadDate > :time ORDER BY percent DESC, uploadDate ASC LIMIT 10 OFFSET $page"); + $countQuery = "SELECT count(*) FROM levelscores WHERE dailyID = 0 AND levelID = :levelID AND uploadDate > :time"; + $query2args = [':levelID' => $levelID, ':time' => time() - 604800]; + break; + default: + $friends = $gs->getFriends($_SESSION['accountID']); + $friends[] = $_SESSION['accountID']; + $friends = implode(",", $friends); + $query2 = $db->prepare("SELECT * FROM levelscores WHERE dailyID = 0 AND levelID = :levelID AND accountID IN ($friends) ORDER BY percent DESC, uploadDate ASC LIMIT 10 OFFSET $page"); + $countQuery = "SELECT count(*) FROM levelscores WHERE dailyID = 0 AND levelID = :levelID AND accountID IN ($friends)"; + $query2args = [':levelID' => $levelID]; + break; +} +$query2->execute($query2args); +$result = $query2->fetchAll(); +if(isset($_SERVER["HTTP_REFERER"])) $back = '
+ +
'; +else $back = ''; +$typeMenu = ' +'; +$x = 0; +foreach($result as &$leaderboard) { + $x++; + $query = $db->prepare("SELECT userName, extID, userID, icon, color1, color2, color3, iconType, special, clan, IP FROM users WHERE extID = :extID"); + $query->execute([':extID' => $leaderboard['accountID']]); + $action = $query->fetch(); + $percent = '

'.$leaderboard["percent"].'

'; + $attempts = '

'.$leaderboard["attempts"].'

'; + $coins = '

'.$leaderboard["coins"].'

'; + $leaderboards .= $dl->generateLeaderboardsCard($x, $leaderboard, $action, $leaderboardDeleteCheck, $percent.$attempts.$coins); +} +if(empty($leaderboards)) $leaderboards = '
+ +

'.$dl->getLocalizedString('noLeaderboards').'

+
'; +$pagel = '
+

'.$back.$dl->getLocalizedString("levelLeaderboards").$typeMenu.'

+
+ '.$dl->generateLevelsCard($level, $modcheck).' +
+
+ '.$leaderboards.' +
+
'; +/* + bottom row +*/ +//getting count +$query = $db->prepare($countQuery); +$query->execute($query2args); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/levelsList.php b/dashboard/stats/levelsList.php new file mode 100644 index 000000000..e8dde45b5 --- /dev/null +++ b/dashboard/stats/levelsList.php @@ -0,0 +1,64 @@ +title($dl->getLocalizedString("levels")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +$searchValue = trim(ExploitPatch::rucharclean($_GET["search"])); +if(!empty($searchValue)) { + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM levels WHERE unlisted = 0 AND ".(is_numeric($searchValue) ? 'levelID' : 'levelName')." LIKE '%".$searchValue."%' ORDER BY uploadDate DESC LIMIT 10 OFFSET $page"); +} else $query = $db->prepare("SELECT * FROM levels WHERE unlisted = 0 ORDER BY uploadDate DESC LIMIT 10 OFFSET $page"); +$query->execute(); +$result = $query->fetchAll(); +if(empty($result)) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse')); +foreach($result as &$action) $levels .= $dl->generateLevelsCard($action, $modcheck); +$pagel = '
+

'.$dl->getLocalizedString("levels").'

+
+ '.$levels.' +
+
+ + + '.$srcbtn.' +
+
'; +/* + bottom row +*/ +//getting count +if(!empty($searchValue)) $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted = 0 AND ".(is_numeric($searchValue) ? 'levelID' : 'levelName')." LIKE '%".$searchValue."%'"); +else $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted = 0"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/listsTable.php b/dashboard/stats/listsTable.php new file mode 100644 index 000000000..bd1deca15 --- /dev/null +++ b/dashboard/stats/listsTable.php @@ -0,0 +1,101 @@ +printFooter('../'); +$dl->title($dl->getLocalizedString("listTable")); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$x = 1; +$packtable = ""; +$query = $db->prepare("SELECT * FROM lists WHERE unlisted = 0 ORDER BY listID DESC LIMIT 10 OFFSET $page"); +$query->execute(); +$result = $query->fetchAll(); +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +foreach($result as &$pack){ + $lvlarray = explode(",", $pack["listlevels"]); + $lvltable = ""; + $listDesc = htmlspecialchars(ExploitPatch::url_base64_decode($pack['listDesc'])); + if(empty($listDesc)) $listDesc = ''.$dl->getLocalizedString("noDesc").''; + $starspack = $pack["starStars"]; + if($pack["starStars"] == 0) $starspack = '0'; + $coinspack = $pack["countForReward"]; + $pst = '

'.$starspack.'

'; + if($pack["countForReward"] != 0) $pcc = '

'.$coinspack.'

'; else $pcc = ''; + $pd = '

'.$gs->getListDiffName($pack['starDifficulty']).'

'; + $lk = '

'.abs($pack['likes'] - $pack['dislikes']).'

'; + $dload = '

'.$pack['downloads'].'

'; + $packall = $dload.$lk.$pst.$pd.$pcc; + foreach($lvlarray as &$lvl) { + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $lvl]); + $action = $query->fetch(); + if(!$action) continue; + $lvltable .= $dl->generateLevelsCard($action, $modcheck); + } + // Avatar management + $avatarImg = ''; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $pack["accountID"]]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; + } + $packtable .= '
+
+
+

'.sprintf($dl->getLocalizedString("demonlistLevel"), htmlspecialchars($pack["listName"]), 0, htmlspecialchars($gs->getAccountName($pack['accountID'])), $avatarImg).'

+

'.$listDesc.'

+
+
+ '.$packall.' +
+
+ '.$lvltable.' +
+
+

ID: '.$pack["listID"].'

+

'.$dl->getLocalizedString('date').': '.$dl->convertToDate($pack["uploadDate"], true).'

+
+
+
'; + $x++; +} +$pagecount = ceil($dailycount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage('

'.$dl->getLocalizedString('listTable').'

+
+ '.$packtable.' +
+
'.$bottomrow, 'browse'); +?> \ No newline at end of file diff --git a/dashboard/stats/listsTableMod.php b/dashboard/stats/listsTableMod.php new file mode 100644 index 000000000..d708ef850 --- /dev/null +++ b/dashboard/stats/listsTableMod.php @@ -0,0 +1,109 @@ +printFooter('../'); +$dl->title($dl->getLocalizedString("listTableMod")); +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +if(!$modcheck) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$x = 1; +$packtable = ""; +$query = $db->prepare("SELECT * FROM lists WHERE unlisted != 0 ORDER BY listID DESC LIMIT 10 OFFSET $page"); +$query->execute(); +$result = $query->fetchAll(); +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'mod'); + die(); +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +foreach($result as &$pack){ + $lvlarray = explode(",", $pack["listlevels"]); + $lvltable = ""; + $listDesc = htmlspecialchars(ExploitPatch::url_base64_decode($pack['listDesc'])); + if(empty($listDesc)) $listDesc = ''.$dl->getLocalizedString("noDesc").''; + $starspack = $pack["starStars"]; + if($pack["starStars"] == 0) $starspack = '0'; + $coinspack = $pack["countForReward"]; + $pst = '

'.$starspack.'

'; + if($pack["countForReward"] != 0) $pcc = '

'.$coinspack.'

'; else $pcc = ''; + $pd = '

'.$gs->getListDiffName($pack['starDifficulty']).'

'; + $lk = '

'.abs($pack['likes'] - $pack['dislikes']).'

'; + $dload = '

'.$pack['downloads'].'

'; + $packall = $dload.$lk.$pst.$pd.$pcc; + foreach($lvlarray as &$lvl) { + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $lvl]); + $action = $query->fetch(); + if(!$action) continue; + $lvltable .= $dl->generateLevelsCard($action, $modcheck); + } + // Avatar management + $avatarImg = ''; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $pack["accountID"]]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; + } + $packtable .= '
+
+
+

'.sprintf($dl->getLocalizedString("demonlistLevel"), htmlspecialchars($pack["listName"]), 0, htmlspecialchars($gs->getAccountName($pack['accountID'])), $avatarImg).'

+

'.$listDesc.'

+
+
+ '.$packall.' +
+
+ '.$lvltable.' +
+
+

ID: '.$pack["listID"].'

+

'.$dl->getLocalizedString('date').': '.$dl->convertToDate($pack["uploadDate"], true).'

+
+
+
'; + $x++; +} +$pagecount = ceil($dailycount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage('

'.$dl->getLocalizedString('listTableMod').'

+
+ '.$packtable.' +
+
'.$bottomrow, 'browse'); +?> \ No newline at end of file diff --git a/dashboard/stats/listsTableYour.php b/dashboard/stats/listsTableYour.php new file mode 100644 index 000000000..15b587145 --- /dev/null +++ b/dashboard/stats/listsTableYour.php @@ -0,0 +1,108 @@ +printFooter('../'); +$dl->title($dl->getLocalizedString("listTableYour")); +if(!isset($_SESSION["accountID"]) || $_SESSION["accountID"] == 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$x = 1; +$packtable = ""; +$query = $db->prepare("SELECT * FROM lists WHERE unlisted != 0 AND accountID = :accID ORDER BY listID DESC LIMIT 10 OFFSET $page"); +$query->execute([':accID' => $_SESSION['accountID']]); +$result = $query->fetchAll(); +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'account'); + die(); +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +foreach($result as &$pack){ + $lvlarray = explode(",", $pack["listlevels"]); + $lvltable = ""; + $listDesc = htmlspecialchars(ExploitPatch::url_base64_decode($pack['listDesc'])); + if(empty($listDesc)) $listDesc = ''.$dl->getLocalizedString("noDesc").''; + $starspack = $pack["starStars"]; + if($pack["starStars"] == 0) $starspack = '0'; + $coinspack = $pack["countForReward"]; + $pst = '

'.$starspack.'

'; + if($pack["countForReward"] != 0) $pcc = '

'.$coinspack.'

'; else $pcc = ''; + $pd = '

'.$gs->getListDiffName($pack['starDifficulty']).'

'; + $lk = '

'.abs($pack['likes'] - $pack['dislikes']).'

'; + $dload = '

'.$pack['downloads'].'

'; + $packall = $dload.$lk.$pst.$pd.$pcc; + foreach($lvlarray as &$lvl) { + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $lvl]); + $action = $query->fetch(); + if(!$action) continue; + $lvltable .= $dl->generateLevelsCard($action, $modcheck); + } + // Avatar management + $avatarImg = ''; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $pack["accountID"]]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; + } + $packtable .= '
+
+
+

'.sprintf($dl->getLocalizedString("demonlistLevel"), htmlspecialchars($pack["listName"]), 0, htmlspecialchars($gs->getAccountName($pack['accountID'])), $avatarImg).'

+

'.$listDesc.'

+
+
+ '.$packall.' +
+
+ '.$lvltable.' +
+
+

ID: '.$pack["listID"].'

+

'.$dl->getLocalizedString('date').': '.$dl->convertToDate($pack["uploadDate"], true).'

+
+
+
'; + $x++; +} +$pagecount = ceil($dailycount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage('

'.$dl->getLocalizedString('listTableYour').'

+
+ '.$packtable.' +
+
'.$bottomrow, 'browse'); +?> \ No newline at end of file diff --git a/dashboard/stats/manageSFX.php b/dashboard/stats/manageSFX.php new file mode 100644 index 000000000..6ec5ca6f1 --- /dev/null +++ b/dashboard/stats/manageSFX.php @@ -0,0 +1,88 @@ +title($dl->getLocalizedString("manageSFX")); +$dl->printFooter('../'); +if(isset($_SESSION["accountID"]) AND $_SESSION["accountID"] != 0){ +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +}else{ + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$accountID = $_SESSION["accountID"]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +$srcbtn = ""; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) { + $q = is_numeric(trim(ExploitPatch::rucharclean($_GET["search"]))) ? "ID LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'" : "(name LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%' OR authorName LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%')"; + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM sfxs WHERE reuploadID = $accountID AND $q ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
', "account"); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM sfxs WHERE reuploadID = $accountID ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); +} +$x = $page + 1; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'account'); + die(); +} +foreach($result as &$action) { + $whoused = '

'.$action['levelsCount'].'

'; + $songs .= $dl->generateSFXCard($action, $whoused, false); +} +$pagel = '
+

'.$dl->getLocalizedString("manageSFX").'

+
+ '.$songs.' +
+
+ + + '.$srcbtn.' +
+
'; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) $query = $db->prepare("SELECT count(*) FROM sfxs WHERE reuploadID=:id AND $q"); +else $query = $db->prepare("SELECT count(*) FROM sfxs WHERE reuploadID=:id"); +$query->execute([':id' => $accountID]); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "account"); +} else { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account'); +} +?> \ No newline at end of file diff --git a/dashboard/stats/manageSongs.php b/dashboard/stats/manageSongs.php new file mode 100644 index 000000000..f16e117a0 --- /dev/null +++ b/dashboard/stats/manageSongs.php @@ -0,0 +1,89 @@ +title($dl->getLocalizedString("manageSongs")); +$dl->printFooter('../'); +if(!isset($_SESSION["accountID"]) || $_SESSION["accountID"] == 0) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$accountID = $_SESSION["accountID"]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +$srcbtn = ""; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) { + $q = is_numeric(trim(ExploitPatch::rucharclean($_GET["search"]))) ? "ID LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'" : "(name LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%' OR authorName LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%')"; + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM songs WHERE reuploadID = $accountID AND $q ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
', "account"); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM songs WHERE reuploadID = $accountID ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); +} +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'account'); + die(); +} +foreach($result as &$action) { + $wholiked = $db->prepare("SELECT count(*) FROM favsongs WHERE songID = :id"); + $wholiked->execute([':id' => $songsid]); + $wholiked = $wholiked->fetchColumn(); + $whoused = $action['levelsCount']; + $wholiked = '

'.$wholiked.'

'; + $whoused = '

'.$whoused.'

'; + $songs .= $dl->generateSongCard($action, $wholiked.$whoused, false); +} +$pagel = '
+

'.$dl->getLocalizedString("manageSongs").'

+
+ '.$songs.' +
+
+ + + '.$srcbtn.' +
+
'; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) $query = $db->prepare("SELECT count(*) FROM songs WHERE reuploadID=:id AND $q"); +else $query = $db->prepare("SELECT count(*) FROM songs WHERE reuploadID = :id"); +$query->execute([':id' => $accountID]); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "account"); +?> \ No newline at end of file diff --git a/dashboard/stats/modActions.php b/dashboard/stats/modActions.php deleted file mode 100644 index d2b1d3777..000000000 --- a/dashboard/stats/modActions.php +++ /dev/null @@ -1,52 +0,0 @@ -getAccountsWithPermission("toolModactions")); -if($accounts == ""){ - $dl->printBox(sprintf($dl->getLocalizedString("errorNoAccWithPerm"), "toolsModactions")); - exit(); -} -$query = $db->prepare("SELECT accountID, userName FROM accounts WHERE accountID IN ($accounts) ORDER BY userName ASC"); -$query->execute(); -$result = $query->fetchAll(); -$row = 0; -foreach($result as &$mod){ - $row++; - $query = $db->prepare("SELECT lastPlayed FROM users WHERE extID = :id"); - $query->execute([':id' => $mod["accountID"]]); - $time = $dl->convertToDate($query->fetchColumn()); - $query = $db->prepare("SELECT count(*) FROM modactions WHERE account = :id"); - $query->execute([':id' => $mod["accountID"]]); - $actionscount = $query->fetchColumn(); - $query = $db->prepare("SELECT count(*) FROM modactions WHERE account = :id AND type = '1'"); - $query->execute([':id' => $mod["accountID"]]); - $lvlcount = $query->fetchColumn(); - $modtable .= "".$row."".$mod["userName"]."".$actionscount."".$lvlcount."".$time.""; -} - -/* - printing -*/ -$dl->printPage(' - - - - - - - - - - - '.$modtable.' - -
#'.$dl->getLocalizedString("mod").''.$dl->getLocalizedString("count").''.$dl->getLocalizedString("ratedLevels").''.$dl->getLocalizedString("lastSeen").'
', true, "stats"); -?> \ No newline at end of file diff --git a/dashboard/stats/modActionsList.php b/dashboard/stats/modActionsList.php index 6b342c607..e274ddc87 100644 --- a/dashboard/stats/modActionsList.php +++ b/dashboard/stats/modActionsList.php @@ -1,73 +1,349 @@ 0){ +$dl = new dashboardLib(); +$dl->title($dl->getLocalizedString("modActionsList")); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { $page = ($_GET["page"] - 1) * 10; $actualpage = $_GET["page"]; -}else{ +} else { $page = 0; $actualpage = 1; } -$table = ''; - -$query = $db->prepare("SELECT * FROM modactions ORDER BY ID DESC LIMIT 10 OFFSET $page"); +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +if(!isset($_GET["who"])) $_GET["who"] = ""; +$srcbtn = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$seltype = !empty($_GET["type"]) ? ExploitPatch::number($_GET["type"]) : 0; +$selname = !empty($_GET["who"]) ? ExploitPatch::number($_GET["who"]) : 0; +if(!empty($_GET["type"]) OR !empty($_GET["who"])) { + $where = 'WHERE'; + $srcbtn = ''; +} +else $where = ''; +$requesttype = !empty($_GET["type"]) ? 'type = '.ExploitPatch::number($_GET["type"]) : ''; +$requestwho = !empty($_GET["who"]) ? 'account = '.ExploitPatch::number($_GET["who"]) : ''; +if(!empty($_GET["type"]) AND !empty($_GET["who"])) $requesttype .= ' AND'; +$query = $db->prepare("SELECT * FROM modactions $where $requesttype $requestwho ORDER BY ID DESC LIMIT 10 OFFSET $page"); $query->execute(); $result = $query->fetchAll(); $x = $page + 1; -foreach($result as &$action){ - //detecting mod - $account = $action["account"]; - $account = $gs->getAccountName($account); - //detecting action - $value = $action["value"]; - $value2 = $action["value2"]; - $value3 = $action["value3"]; - if($action["type"] == 5){ - if(is_numeric($value2)){ - $value2 = date("d/m/Y G:i:s", $value2); - } - } +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ + +
', 'stats'); + die(); +} +foreach($result as &$action) { + $account = $gs->getAccountName($action["account"]); + $value = htmlspecialchars($action["value"]); + $value2 = htmlspecialchars($action["value2"]); + $value3 = htmlspecialchars($action["value3"]); + $value4 = htmlspecialchars($action["value4"]); + $value5 = htmlspecialchars($action["value5"]); + $value6 = htmlspecialchars($action["value6"]); $actionname = $dl->getLocalizedString("modAction".$action["type"]); - if($action["type"] == 2 OR $action["type"] == 3 OR $action["type"] == 4){ - if($action["value"] == 1){ - $value = "True"; - }else{ - $value = "False"; - } - } - if($action["type"] == 5 OR $action["type"] == 6){ - $value = ""; - } - if($action["type"] == 13){ - $value = base64_decode($value); - } - if(strlen($value) > 18){ - $value = "
Spoiler$value
"; //todo: finish - } - if(strlen($value2) > 18){ - $value2 = "
Spoiler$value2
"; //todo: finish - } - $time = date("d/m/Y G:i:s", $action["timestamp"]); - if($action["type"] == 5 AND $action["value2"] > time()){ - $value3 = "future"; + switch($action["type"]) { + case 1: + if($value2 == 1) $star = 0; elseif($value2 < 5 AND $value2 != 0) $star = 1; else $star = 2; + $value2 = $value2.' '.$dl->getLocalizedString("starsLevel$star"); + if($value == 0) $value = ''.$dl->getLocalizedString("isAdminNo").''; + break; + case 2: + case 3: + case 4: + case 12: + case 14: + case 29: + case 38: + if($action["value"] == 1) $value = ''.$dl->getLocalizedString("isAdminYes").''; + else $value = ''.$dl->getLocalizedString("isAdminNo").''; + $value2 = $gs->getLevelName($value3); + break; + case 5: + $value = $value3; + if(is_numeric($value2)) $value3 = date("d.m.Y", $value2); + $value2 = $gs->getLevelName($value); + break; + case 6: + case 34: + $value = $value3; + if(empty($value2)) $value2 = ''.$dl->getLocalizedString('empty').''; + $value3 = date("d.m.Y", $action["timestamp"]); + break; + case 8: + $value2 = $value; + $value = htmlspecialchars($action['value2']); + if(empty($action['value2'])) $value = ''.$dl->getLocalizedString('empty').''; + break; + case 9: + if(!$gs->checkPermission($_SESSION['accountID'], "dashboardModTools")) $value = ''.$dl->getLocalizedString('empty').''; + else $value = substr($value, 1); + break; + case 7: + case 10: + case 11: + case 16: + $value2 = $gs->getLevelName($value3); + break; + case 13: + $value = ExploitPatch::url_base64_decode($value); + break; + case 15: + $value = $gs->getAccountName($value); + switch($value4) { + case 'isBanned': + $value4 = $dl->getLocalizedString('playerTop'); + break; + case 'isCreatorBanned': + $value4 = $dl->getLocalizedString('creatorTop'); + break; + case 'isUploadBanned': + $value4 = $dl->getLocalizedString('levelUploading'); + break; + case 'isCommentBanned': + $value4 = $dl->getLocalizedString('commentBan'); + break; + } + if($value3 == 0) $value3 = $value4.', '.$dl->getLocalizedString("unban").''; + else $value3 = $value4.', '.$dl->getLocalizedString("isBan").''; + if($value2 == 'banned' OR $value2 == 'none') $value2 = ''.$dl->getLocalizedString("noReason").''; + break; + case 18: + case 22: + $value2 = $value; + $action['value2'] = $action['value']; + $value = $action['value'] = $gs->getGauntletName($value3); + break; + case 17: + case 21: + if($value3 == 1) $star = 0; elseif($value3 < 5) $star = 1; else $star = 2; + if($action["value4"] == 1) $coin = 0; elseif($action["value4"] != 0) $coin = 1; else $coin = 2; + $value = ''.$value.''; + $value3 = $value3.' '.$dl->getLocalizedString("starsLevel$star").', '.$action["value4"].' '.$dl->getLocalizedString("coins$coin"); + break; + case 20: + case 24: + $value = ''; + if(!empty($value3) && $value3 != "-1") { + $clr = $db->prepare("SELECT commentColor, roleName FROM roles WHERE roleID = :id"); + $clr->execute([':id' => $value3]); + $clr = $clr->fetch(); + } else { + $clr['roleName'] = $dl->getLocalizedString('demoted'); + $clr['commentColor'] = '227, 81, 81'; + } + $value3 = ''.$clr['roleName'].''; + break; + case 23: + case 25: + $value = $value4; + $questTypes = ['Unknown', $dl->getLocalizedString("orbs"), $dl->getLocalizedString("coins"), $dl->getLocalizedString("stars")]; + $value2 = $questTypes[$action['value']]; + $value3 = $action["value2"].' | '.$action["value3"]; + break; + case 26: + $username26 = $gs->getAccountName($action["value"]); + $value = ''; + $value2 = $action["value"]; + if($value2 == 'Password') $value3 = $dl->getLocalizedString("password"); + else $value3 = $dl->getLocalizedString("username"); + break; + case 28: + switch($value3) { + case 0: + $username28 = $gs->getAccountName($action["value"]); + $value = ''; + break; + case 1: + $value = $gs->getUserName($value); + break; + case 2: + $value = $dl->getLocalizedString('IP'); + break; + } + $value2 = $action['value2'] = base64_decode($action['value2']); + $banTextArray = ['unban', 'isBan', 'banChange']; + $banColorArray = ['BBFFBB', 'FFBBBB', 'FFEEBB']; + if(empty($value2)) $value2 = "".$dl->getLocalizedString('noReason').""; + $value2 .= " |".$dl->getLocalizedString($banTextArray[$value6]).''; + $value3 = date("d.m.Y G:i", $value5); + break; + case 30: + case 31: + $value = $gs->getListName($action["value3"]).', '.$action['value3']; + $value2 = $gs->getListDiffName($action["value2"]); + $value3 = $action['value']; + break; + case 32: + case 33: + $value = $gs->getListName($action["value3"]); + $value2 = $action['value']; + break; + case 35: + $value = $gs->getListName($action["value3"]); + $value2 = $gs->getAccountName($action['value']); + break; + case 37: + $value = ExploitPatch::url_base64_decode($action['value']); + $value2 = $gs->getListName($action["value3"]); + break; + case 39: + if($action["value"] == 1) $value = ''.$dl->getLocalizedString("isAdminYes").''; + else $value = ''.$dl->getLocalizedString("isAdminNo").''; + $value2 = $gs->getListName($value3); + break; + case 40: + $value = $value3; + $value2 = $gs->getLevelName($value3); + $value3 = date("d.m.Y", $action["timestamp"]); + break; + case 41: + $value2 = $value.' '.$dl->getLocalizedString("starsLevel1"); + $value = $value3; + $value3 = $gs->getLevelName($value3); + break; + case 42: + case 43: + $rewardTypes = ['Nothing', 'Fire Shard', 'Ice Shard', 'Poison Shard', 'Shadow Shard', 'Lava Shard', 'Demon Key', 'Orbs', 'Diamond', 'Nothing', 'Earth Shard', 'Blood Shard', 'Metal Shard', 'Light Shard', 'Soul Shard', 'Gold Key']; + $value = $rewardTypes[$value2].', '.$value3; + $value2 = $value5; + $value3 = $dl->convertToDate($value4, true); + break; + case 44: + $rewardTypes = ['Nothing', 'Fire Shard', 'Ice Shard', 'Poison Shard', 'Shadow Shard', 'Lava Shard', 'Demon Key', 'Orbs', 'Diamond', 'Nothing', 'Earth Shard', 'Blood Shard', 'Metal Shard', 'Light Shard', 'Soul Shard', 'Gold Key']; + $value = $dl->convertToDate($value, true); + $value2 = $rewardTypes[$value2].', '.$value4; + $value3 = $gs->getLevelName($value3); + break; } - $table .= ""; + if(mb_strlen($action["value"]) > 18) $value = "
".$dl->getLocalizedString("spoiler")."$value
"; + if(mb_strlen($action["value2"]) > 18) $value2 = "
".$dl->getLocalizedString("spoiler")."$value2
"; + $time = $dl->convertToDate($action["timestamp"], true); + $v1 = '
'.$value.'
'; + $v2 = '
'.$value2.'
'; + $v3 = '
'.$value3.'
'; + $stats = $v1.$v2.$v3; + // Avatar management + $queryUserDetails = $db->prepare("SELECT iconType, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack, color1, color2, color3, accGlow FROM users WHERE extID = :accountID"); + $queryUserDetails->execute([':accountID' => $action['account']]); + if($userDetails = $queryUserDetails->fetch(PDO::FETCH_ASSOC)) { + $iconType = ($userDetails['iconType'] > 8) ? 0 : $userDetails['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userDetails['accIcon']], 1 => ['type' => 'ship', 'value' => $userDetails['accShip']], 2 => ['type' => 'ball', 'value' => $userDetails['accBall']], 3 => ['type' => 'ufo', 'value' => $userDetails['accBird']], 4 => ['type' => 'wave', 'value' => $userDetails['accDart']], 5 => ['type' => 'robot', 'value' => $userDetails['accRobot']], 6 => ['type' => 'spider', 'value' => $userDetails['accSpider']], 7 => ['type' => 'swing', 'value' => $userDetails['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userDetails['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'avatar'; + } + $members .= '
+
+
+
+ '.$avatarImg.' +
+

+ '.(!empty($account) ? '' : $dl->getLocalizedString("system")).' + '.$actionname.' +

+
+
+
+
'.$stats.'
+
+

'.$dl->getLocalizedString("ID").': '.$action["ID"].'

+

'.$dl->getLocalizedString("date").': '.$time.'

+
+
+
'; $x++; } -$table .= "
#'.$dl->getLocalizedString("mod").''.$dl->getLocalizedString("action").''.$dl->getLocalizedString("value").''.$dl->getLocalizedString("value2").''.$dl->getLocalizedString("level").''.$dl->getLocalizedString("time").'
".$x."".$account."".$actionname."".$value."".$value2."".$value3."".$time."
"; -/* - bottom row -*/ -//getting count -$query = $db->prepare("SELECT count(*) FROM modactions"); +$mods = $db->prepare("SELECT * FROM roleassign GROUP BY accountID"); +$mods->execute(); +$mods = $mods->fetchAll(); +$options = ''; +foreach($mods as &$mod) { + $name = $gs->getAccountName($mod["accountID"]); + $options .= ''; +}; +$pagel = '
+

'.$dl->getLocalizedString("modActionsList").'

+
+ '.$members.' +
+
+ + + + '.$srcbtn.' +
+
'; +$query = $db->prepare("SELECT count(*) FROM modactions $where $requesttype $requestwho"); $query->execute(); $packcount = $query->fetchColumn(); $pagecount = ceil($packcount / 10); $bottomrow = $dl->generateBottomRow($pagecount, $actualpage); -$dl->printPage($table . $bottomrow, true, "browse"); +$dl->printPage($pagel.$bottomrow.'', true, "stats"); ?> \ No newline at end of file diff --git a/dashboard/stats/modList.php b/dashboard/stats/modList.php new file mode 100644 index 000000000..b87aa5408 --- /dev/null +++ b/dashboard/stats/modList.php @@ -0,0 +1,100 @@ +title($dl->getLocalizedString("modActions")); +$dl->printFooter('../'); +$modtable = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +$srcbtn = ""; +if(!empty($_GET["search"])) { + $query = $db->prepare("SELECT * FROM accounts INNER JOIN users INNER JOIN roleassign WHERE isActive = 1 AND accounts.accountID = users.extID AND accounts.accountID = roleassign.accountID AND accounts.userName LIKE '%".ExploitPatch::remove($_GET["search"])."%' ORDER BY roleassign.roleID ASC, accounts.userName ASC"); + $srcbtn = ''; +} else $query = $db->prepare("SELECT * FROM accounts INNER JOIN users INNER JOIN roleassign WHERE isActive = 1 AND accounts.accountID = users.extID AND accounts.accountID = roleassign.accountID ORDER BY roleassign.roleID ASC, accounts.userName ASC"); +$query->execute(); +$result = $query->fetchAll(); +$row = 0; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'stats'); + die(); +} +foreach($result as &$action) { + $clan = $own = ''; + $query = $db->prepare("SELECT * FROM (SELECT count(*) AS actionCount FROM modactions WHERE account = :id) actionCount JOIN (SELECT count(*) AS levelsRated FROM modactions WHERE account = :id AND type = 1) levelsRated"); + $query->execute([':id' => $action["accountID"]]); + $counts = $query->fetch(); + $accUserID = $gs->getUserID($action["accountID"]); + $accountID = $action["accountID"].' | '.$accUserID; + if($action["accountID"] == $accUserID) $accountID = $action["accountID"]; + $resultRole = $action["roleID"]; + $query = $db->prepare("SELECT roleName FROM roles WHERE roleID = :id"); + $query->execute([':id' => $resultRole]); + $resultRole = $query->fetch()["roleName"]; + if($action["clan"] != 0) { + $claninfo = $gs->getClanInfo($action["clan"]); + if($claninfo["clanOwner"] == $action["accountID"]) $own = ''; + $clan = '

'.$claninfo["clan"].$own.'

'; + } + // Avatar management + $iconType = ($action['iconType'] > 8) ? 0 : $action['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $action['accIcon']], 1 => ['type' => 'ship', 'value' => $action['accShip']], 2 => ['type' => 'ball', 'value' => $action['accBall']], 3 => ['type' => 'ufo', 'value' => $action['accBird']], 4 => ['type' => 'wave', 'value' => $action['accDart']], 5 => ['type' => 'robot', 'value' => $action['accRobot']], 6 => ['type' => 'spider', 'value' => $action['accSpider']], 7 => ['type' => 'swing', 'value' => $action['accSwing']], 8 => ['type' => 'jetpack', 'value' => $action['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'avatar'; + // Badge management + $badgeImg = ''; + $queryRoleID = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); + $queryRoleID->execute([':accountID' => $accountID]); + if($roleAssignData = $queryRoleID->fetch(PDO::FETCH_ASSOC)) { + $queryBadgeLevel = $db->prepare("SELECT modBadgeLevel FROM roles WHERE roleID = :roleID"); + $queryBadgeLevel->execute([':roleID' => $roleAssignData['roleID']]); + if(($modBadgeLevel = $queryBadgeLevel->fetchColumn() ?? 0) >= 1 && $modBadgeLevel <= 3) { + $badgeImg = 'badge'; + } + } + $ac = '

'.$counts["actionCount"].'

'; + $lr = '

'.$counts["levelsRated"].'

'; + $stats = $dl->createProfileStats($action['stars'], $action['moons'], $action['diamonds'], $action['coins'], $action['userCoins'], $action['demons'], $action['creatorPoints'], 0, false).$ac.$lr; + $registerDate = $dl->convertToDate($action["registerDate"], true); + $members .= '
+
+ +
+
'.$stats.'
+

'.$dl->getLocalizedString("accountID").': '.$accountID.'

'.$dl->getLocalizedString("registerDate").': '.$registerDate.'

+
'; + $x++; +} +$pagel = '
+

'.$dl->getLocalizedString("modActions").'

+
+ '.$members.' +
+
+ + + '.$srcbtn.' +
+
'; +$dl->printPage($pagel.$bottomrow, true, "stats"); +?> \ No newline at end of file diff --git a/dashboard/stats/packTable.php b/dashboard/stats/packTable.php index 43dc55d0f..e3a212d4d 100644 --- a/dashboard/stats/packTable.php +++ b/dashboard/stats/packTable.php @@ -2,92 +2,86 @@ session_start(); require "../incl/dashboardLib.php"; $dl = new dashboardLib(); -require "../../incl/lib/mainLib.php"; +require_once "../".$dbPath."incl/lib/mainLib.php"; $gs = new mainLib(); -/* - generating packtable -*/ -include "../../incl/lib/connection.php"; +$dl->title($dl->getLocalizedString("packTable")); +$dl->printFooter('../'); +require "../".$dbPath."incl/lib/connection.php"; +require "../".$dbPath."incl/lib/exploitPatch.php"; if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ $page = ($_GET["page"] - 1) * 10; $actualpage = $_GET["page"]; -}else{ +} else { $page = 0; $actualpage = 1; } -$x = $page + 1; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +$x = 1; $packtable = ""; -$query = $db->prepare("SELECT levels,name,stars,coins FROM mappacks ORDER BY ID ASC LIMIT 10 OFFSET $page"); +$query = $db->prepare("SELECT * FROM mappacks ORDER BY ID ASC LIMIT 10 OFFSET $page"); $query->execute(); $result = $query->fetchAll(); +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); foreach($result as &$pack){ $lvlarray = explode(",", $pack["levels"]); $lvltable = ""; - foreach($lvlarray as &$lvl){ - $query = $db->prepare("SELECT levelID,levelName,starStars,userID,coins FROM levels WHERE levelID = :levelID"); + $color = $pack['rgbcolors']; + $starspack = $pack["stars"]; + if($pack["stars"] == 0) $starspack = '0'; + $coinspack = $pack["coins"]; + if($pack["coins"] == 0) $coinspack = '0'; + $pst = '

'.$starspack.'

'; + $pcc = '

'.$coinspack.'

'; + $diffarray = ['Auto', 'Easy', 'Normal', 'Hard', 'Harder', 'Insane', 'Demon']; + $pd = '

'.$diffarray[$pack['difficulty']].'

'; + $packall = $pst.$pd.$pcc; + foreach($lvlarray as &$lvl) { + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); $query->execute([':levelID' => $lvl]); - $level = $query->fetch(); - $lvltable .= " - ".$level["levelID"]." - ".$level["levelName"]." - ".$gs->getUserName($level["userID"])." - ".$level["starStars"]." - ".$level["coins"]." - "; + $action = $query->fetch(); + if(!$action) continue; + $lvltable .= $dl->generateLevelsCard($action, $modcheck); } - $packtable .= " - $x - ".htmlspecialchars($pack["name"],ENT_QUOTES)." - ".$pack["stars"]." - ".$pack["coins"].' - - - - '; + $packtable .= '
+
+
+

'.$pack["name"].'

+
+
+ '.$packall.' +
+
+ '.$lvltable.' +
+
+

ID: '.$pack["ID"].'

+ '.($pack["timestamp"] != 0 ? '

'.$dl->getLocalizedString('date').': '.$dl->convertToDate($pack["timestamp"], true).'

' : '').' +
+
+
'; $x++; } -/* - bottom row -*/ -//getting count -$query = $db->prepare("SELECT count(*) FROM mappacks"); -$query->execute(); -$packcount = $query->fetchColumn(); -$pagecount = ceil($packcount / 10); +$pagecount = ceil($dailycount / 10); $bottomrow = $dl->generateBottomRow($pagecount, $actualpage); -/* - printing -*/ -$dl->printPage(' - - - - - - - - - - - '.$packtable.' - -
#'.$dl->getLocalizedString("name").''.$dl->getLocalizedString("stars").''.$dl->getLocalizedString("coins").''.$dl->getLocalizedString("levels").'
' -.$bottomrow, true, "stats"); +$dl->printPage('

'.$dl->getLocalizedString('packTable').'

+
+ '.$packtable.' +
+
'.$bottomrow, 'browse'); ?> \ No newline at end of file diff --git a/dashboard/stats/platformerLeaderboards.php b/dashboard/stats/platformerLeaderboards.php new file mode 100644 index 000000000..fcb3ae0dd --- /dev/null +++ b/dashboard/stats/platformerLeaderboards.php @@ -0,0 +1,136 @@ +title($dl->getLocalizedString("levelLeaderboards")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["levelID"])) $_GET["levelID"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$levelID = ExploitPatch::number($_GET['levelID']); +$type = ExploitPatch::number($_GET['type']) ?: ($_SESSION['accountID'] != 0 ? 0 : 1); +$mode = ExploitPatch::charclean($_GET['mode']) ?: 'time'; +$order = $mode == 'time' ? 'ASC' : 'DESC'; +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +$leaderboardDeleteCheck = $gs->checkPermission($_SESSION["accountID"], "dashboardDeleteLeaderboards"); +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID".(!$modcheck ? ' AND unlisted = 0' : '')); +$query->execute([':levelID' => $levelID]); +$level = $query->fetch(); +if(empty($level) || $level['levelLength'] != 5) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse')); +if($_SESSION['accountID'] != 0) { + if(isset($_POST['deleteLeaderboardID'])) { + $deleteLeaderboardID = ExploitPatch::number($_POST['deleteLeaderboardID']); + $query = $db->prepare("SELECT count(*) FROM platscores WHERE ID = :deleteLeaderboardID"); + $query->execute([':deleteLeaderboardID' => $deleteLeaderboardID]); + $leaderboard = $query->fetch(); + if($leaderboard && $leaderboardDeleteCheck) { + $query = $db->prepare("DELETE FROM platscores WHERE ID = :deleteLeaderboardID"); + $query->execute([':deleteLeaderboardID' => $deleteLeaderboardID]); + } + } +} +switch($type) { + case 1: + $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND time > 0 ORDER BY {$mode} {$order} LIMIT 10 OFFSET $page"); + $countQuery = "SELECT count(*) FROM platscores WHERE levelID = :levelID AND time > 0"; + $query2args = [':levelID' => $levelID]; + break; + case 2: + $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND timestamp > :time AND time > 0 ORDER BY {$mode} {$order} LIMIT 10 OFFSET $page"); + $countQuery = "SELECT count(*) FROM platscores WHERE levelID = :levelID AND timestamp > :time AND time > 0"; + $query2args = [':levelID' => $levelID, ':time' => time() - 604800]; + break; + default: + $friends = $gs->getFriends($_SESSION['accountID']); + $friends[] = $_SESSION['accountID']; + $friends = implode(",", $friends); + $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND accountID IN ($friends) AND time > 0 ORDER BY {$mode} {$order} LIMIT 10 OFFSET $page"); + $countQuery = "SELECT count(*) FROM platscores WHERE levelID = :levelID AND accountID IN ($friends) AND time > 0"; + $query2args = [':levelID' => $levelID]; + break; +} +$query2->execute($query2args); +$result = $query2->fetchAll(); +if(isset($_SERVER["HTTP_REFERER"])) $back = '
+ +
'; +else $back = ''; +$typeMenu = ' +'; +$x = 0; +foreach($result as &$leaderboard) { + $x++; + $query = $db->prepare("SELECT * FROM users WHERE extID = :extID"); + $query->execute([':extID' => $leaderboard['accountID']]); + $action = $query->fetch(); + $query = $db->prepare("SELECT levelLength FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID]); + $action['levelLength'] = $query->fetchColumn(); + $time = '

'.($leaderboard["time"] / 1000).'

'; + $points = '

'.$leaderboard["points"].'

'; + $leaderboard['uploadDate'] = $leaderboard['timestamp']; + $leaderboards .= $dl->generateLeaderboardsCard($x, $leaderboard, $action, $leaderboardDeleteCheck, $time.$points); +} +if(empty($leaderboards)) $leaderboards = '
+ +

'.$dl->getLocalizedString('noLeaderboards').'

+
'; +$pagel = '
+

'.$back.$dl->getLocalizedString("levelLeaderboards").$typeMenu.'

+
+ '.$dl->generateLevelsCard($level, $modcheck).' +
+
+ '.$leaderboards.' +
+
'; +/* + bottom row +*/ +//getting count +$query = $db->prepare($countQuery); +$query->execute($query2args); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/quests.php b/dashboard/stats/quests.php new file mode 100644 index 000000000..8dbcd5c22 --- /dev/null +++ b/dashboard/stats/quests.php @@ -0,0 +1,27 @@ +checkPermission($_SESSION["accountID"], 'toolQuestsCreate')) { + $name = ExploitPatch::charclean($_GET["name"]); + if(!is_numeric($_GET["type"]) OR !is_numeric($_GET["amount"]) OR !is_numeric($_GET["reward"])) die("-1"); + if($_GET["type"] > 3) $type = 3; elseif($_GET["type"] < 1) $type = 1; else $type = ExploitPatch::number($_GET["type"]); + $amount = ExploitPatch::number($_GET["amount"]); + $reward = ExploitPatch::number($_GET["reward"]); + $change = $db->prepare("UPDATE quests SET name = :n, type = :t, amount = :a, reward = :r WHERE ID = :i"); + if($change->execute([':n' => $name, ':t' => $type, ':a' => $amount, ':r' => $reward, ':i' => $id])) echo 1; else die("-1"); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3, value4) VALUES ('23',:value,:timestamp,:account,:amount,:reward,:name)"); + $query->execute([':value' => $type, ':timestamp' => time(), ':account' => $_SESSION["accountID"], ':amount' => $amount, ':reward' => $reward, ':name' => $name]); + } else { + $pck = $db->prepare("SELECT * FROM quests WHERE ID = :id"); + $pck->execute([':id' => $id]); + $map = $pck->fetch(); + echo $map["ID"].' | '.$map["name"].' | '.$map["type"].' | '.$map["amount"].' | '.$map["reward"]; + } +} +?> \ No newline at end of file diff --git a/dashboard/stats/renameSong.php b/dashboard/stats/renameSong.php new file mode 100644 index 000000000..286c9b00f --- /dev/null +++ b/dashboard/stats/renameSong.php @@ -0,0 +1,30 @@ +prepare('SELECT reuploadID FROM '.$audioType.' WHERE ID = :ID'); +$check->execute([':ID' => $sid]); +$check = $check->fetchColumn(); +if($gs->checkPermission($_SESSION["accountID"], "dashboardManageSongs") || ($_SESSION["accountID"] != 0 && $_SESSION["accountID"] == $check)) { + $author = mb_substr(ExploitPatch::rucharclean($_POST["author"]), 0, 23); + $name = mb_substr(ExploitPatch::rucharclean($_POST["name"]), 0, 30); + if($audioType == 'sfxs' AND !empty($name) AND !empty($sid)) { + $query = $db->prepare("UPDATE sfxs SET name = :n WHERE ID = :id"); + $query->execute([':n' => $name, ':id' => $sid]); + $query = $db->prepare("INSERT INTO modactions (type, value2, value3, timestamp, account) VALUES ('27', :n, :id, :timestamp, :account)"); + $query->execute([':n' => $name, ':id' => $sid, ':timestamp' => time(), ':account' => $_SESSION["accountID"]]); + die(json_encode(['success' => true])); + } elseif(!empty($author) AND !empty($name) AND !empty($sid)) { + $query = $db->prepare("UPDATE songs SET name = :n, authorName = :a WHERE ID = :id"); + $query->execute([':n' => $name, ':a' => $author, ':id' => $sid]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('19', :a, :n, :id, :timestamp, :account)"); + $query->execute([':n' => $name, ':a' => $author, ':id' => $sid, ':timestamp' => time(), ':account' => $_SESSION["accountID"]]); + die(json_encode(['success' => true])); + } else die(json_encode(['success' => false, 'error' => '-2'])); +} else die(json_encode(['success' => false, 'error' => '-1'])); +?> \ No newline at end of file diff --git a/dashboard/stats/reportMod.php b/dashboard/stats/reportMod.php new file mode 100644 index 000000000..91d25a85d --- /dev/null +++ b/dashboard/stats/reportMod.php @@ -0,0 +1,64 @@ +title($dl->getLocalizedString("reportMod")); +$dl->printFooter('../'); +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +if($modcheck) { +$reported = ''; +$query = $db->prepare("SELECT levels.*, count(*) AS reportsCount FROM reports INNER JOIN levels ON reports.levelID = levels.levelID GROUP BY levels.levelID ORDER BY reportsCount DESC"); +$query->execute(); +$result = $query->fetchAll(); +$x = 1; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'mod'); + die(); +} +foreach($result as &$level){ + $reports = $level["reportsCount"]; + $repstr = (string)$reports; // bruh + $report = $repstr[strlen($repstr)-1]; + if($report == 1) $action = 0; elseif(($report < 5 AND $report != 0) AND !($reports > 9 AND $reports < 20)) $action = 1; else $action = "s"; + $reportsText = $reports.' '.$dl->getLocalizedString("time".$action); + $levels = $dl->generateLevelsCard($level, $modcheck); + $reported .= '
+
+
+
+
+

'.sprintf($dl->getLocalizedString("reportedName"), $level["levelName"], $reportsText).'

+
+
+
+ '.$levels.' +
+
+
'; + $x++; +} +$pagel = '
+

'.$dl->getLocalizedString("reportMod").'

+
+ '.$reported.' +
+
'; +$dl->printPage($pagel, true, "mod"); +} else $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod'); +?> \ No newline at end of file diff --git a/dashboard/stats/songList.php b/dashboard/stats/songList.php new file mode 100644 index 000000000..2d576ccc6 --- /dev/null +++ b/dashboard/stats/songList.php @@ -0,0 +1,80 @@ +title($dl->getLocalizedString("songs")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $favs = $meta = ""; +$ngw = $_GET["ng"] == 1 ? '' : 'AND reuploadID > 0'; +if(!empty(trim(ExploitPatch::rucharclean($_GET["search"])))) { + $q = is_numeric(trim(ExploitPatch::rucharclean($_GET["search"]))) ? "ID LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%'" : "(name LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%' OR authorName LIKE '%".trim(ExploitPatch::rucharclean($_GET["search"]))."%')"; + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM songs WHERE isDisabled = 0 AND $q $ngw ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM songs WHERE isDisabled = 0 $ngw ORDER BY reuploadTime DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); +} +$x = 0; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); +} +foreach($result as &$action) { + $x++; + $songs .= $dl->generateSongCard($action); +} +$pagel = '
+

'.$dl->getLocalizedString("songs").'

+
+ '.$songs.' +
+
+ + + '.$srcbtn.' +
+
'; +if(!empty(trim($_GET["search"]))) $query = $db->prepare("SELECT count(*) FROM songs WHERE isDisabled = 0 AND $q $ngw"); +else $query = $db->prepare("SELECT count(*) FROM songs WHERE isDisabled = 0 $ngw"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel . $bottomrow, true, "browse"); +?> \ No newline at end of file diff --git a/dashboard/stats/suggestList.php b/dashboard/stats/suggestList.php new file mode 100644 index 000000000..d3493e03d --- /dev/null +++ b/dashboard/stats/suggestList.php @@ -0,0 +1,89 @@ +title($dl->getLocalizedString("suggestLevels")); +$dl->printFooter('../'); +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +if(!$modcheck) die($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +$query = $db->prepare("SELECT * FROM suggest WHERE suggestLevelId > 0 ORDER BY ID DESC LIMIT 10 OFFSET $page"); +$query->execute(); +$result = $query->fetchAll(); +$x = $page + 1; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'mod'); + die(); +} +foreach($result as &$action) { + $suggestid = $action["suggestLevelId"]; + $suggestby = $gs->getAccountName($action["suggestBy"]); + $stars = $action["suggestStars"]; + if($stars < 5) $star = 1; elseif($stars > 4) $star = 2; else $star = 0; + $suggestStars = $stars.' '.$dl->getLocalizedString("starsLevel$star"); + $suggestFeatured = $action["suggestFeatured"]; + $suggestTime = $dl->convertToDate($action["timestamp"], true); + $feat = ['', 'Featured, ', 'Epic, ']; + $level = $db->prepare("SELECT * FROM levels WHERE levelID = :id"); + $level->execute([':id' => $action["suggestLevelId"]]); + $level = $level->fetch(); + if(!empty($level)) $levels = $dl->generateLevelsCard($level, $modcheck); + else $levels = '

'.$dl->getLocalizedString("deletedLevel").'

'; + $suggested .= '
+
+
+
+
+

'.sprintf($dl->getLocalizedString("suggestedName"), $suggestby, $level["levelName"], $suggestStars, $feat[$suggestFeatured]).'

+
+
+
+ '.$levels.' +
+
+

'.$dl->getLocalizedString("ID").': '.$action["ID"].'

'.$dl->getLocalizedString("date").': '.$suggestTime.'

+
'; + $x++; +} +$pagel = '
+

'.$dl->getLocalizedString("suggestLevels").'

+
+ '.$suggested.' +
+
'; +/* + bottom row +*/ +$query = $db->prepare("SELECT count(*) FROM suggest"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "mod"); +?> \ No newline at end of file diff --git a/dashboard/stats/top24h.php b/dashboard/stats/top24h.php new file mode 100644 index 000000000..b72d29013 --- /dev/null +++ b/dashboard/stats/top24h.php @@ -0,0 +1,117 @@ +title($dl->getLocalizedString("leaderboardTime")); +$dl->printFooter('../'); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0){ + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +}else{ + $page = 0; + $actualpage = 1; +} +$time = time() - 86400; +$bans = $gs->getAllBansOfBanType(0); +$extIDs = $userIDs = $bannedIPs = []; +foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } +} +$extIDsString = implode("','", $extIDs); +$userIDsString = implode("','", $userIDs); +$bannedIPsString = implode("|", $bannedIPs); +$queryArray = []; +if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; +if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; +if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; +$queryText = !empty($queryArray) ? '('.implode(' AND ', $queryArray).') AND' : ''; +$query = $db->prepare("SELECT users.extID, SUM(actions.value) AS stars, users.userName FROM actions INNER JOIN users ON actions.account = users.extID WHERE type = '9' AND timestamp > :time AND ".$queryText." actions.value > 0 GROUP BY (stars) DESC ORDER BY stars DESC LIMIT 10 OFFSET $page"); +$query->execute([':time' => $time]); +$result = $query->fetchAll(); +$x = $page + 1; +if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'stats'); + die(); +} +foreach($result as &$action){ + $userid = $action["extID"]; + $stars = $action["stars"]; + $strs = $stars[strlen($stars)-1]; + if($strs == 1) $star = 0; elseif($strs < 5 AND $strs != 0 AND ($stars > 20 OR $stars < 10)) $star = 1; else $star = 2; + $coin = $db->prepare("SELECT SUM(coins) FROM levelscores WHERE accountID = :id AND uploadDate > :time"); + $coin->execute([':id' => $userid, ':time' => $time]); + $coins = $coin->fetchColumn(); + if(empty($coins)) $coins = 0; + $cns = $coins[strlen($coins)-1]; + if($cns == 1) $lvl = 0; elseif($cns < 5 AND $cns > 0 AND ($cns > 20 OR $cns < 10)) $lvl = 1; else $lvl = 2; + if(empty($action["userCoins"])) $action["userCoins"] = 0; + $st = '

'.$action["stars"].'

'; + $uc = '

'.$action["userCoins"].'

'; + $stats = $dl->createProfileStats($action["stars"], 0, 0, 0, $action['userCoins'], 0, 0, 0); + switch($x) { + case 1: + $place = ' 1'; + break; + case 2: + $place = ' 2'; + break; + case 3: + $place = ' 3'; + break; + default: + $place = '# '.$x.''; + break; + } + // Avatar management + $avatarImg = ''; + $extIDvalue = $action['extID']; + $query = $db->prepare('SELECT userName, iconType, color1, color2, color3, accGlow, accIcon, accShip, accBall, accBird, accDart, accRobot, accSpider, accSwing, accJetpack FROM users WHERE extID = :extID'); + $query->execute(['extID' => $extIDvalue]); + $userData = $query->fetch(PDO::FETCH_ASSOC); + if($userData) { + $iconType = ($userData['iconType'] > 8) ? 0 : $userData['iconType']; + $iconTypeMap = [0 => ['type' => 'cube', 'value' => $userData['accIcon']], 1 => ['type' => 'ship', 'value' => $userData['accShip']], 2 => ['type' => 'ball', 'value' => $userData['accBall']], 3 => ['type' => 'ufo', 'value' => $userData['accBird']], 4 => ['type' => 'wave', 'value' => $userData['accDart']], 5 => ['type' => 'robot', 'value' => $userData['accRobot']], 6 => ['type' => 'spider', 'value' => $userData['accSpider']], 7 => ['type' => 'swing', 'value' => $userData['accSwing']], 8 => ['type' => 'jetpack', 'value' => $userData['accJetpack']]]; + $iconValue = (isset($iconTypeMap[$iconType]) && $iconTypeMap[$iconType]['value'] > 0) ? $iconTypeMap[$iconType]['value'] : 1; + $avatarImg = 'Avatar'; + } + $members .= '
+
+
'.$stats.'
+

'.$dl->getLocalizedString("accountID").': '.(is_numeric($userid) ? $userid : 0).'

+
'; + $x++; +} +$pagel = '
+

'.$dl->getLocalizedString("leaderboardTime").'

+
+ '.$members.' +
'; +$query = $db->prepare("SELECT users.extID, SUM(actions.value) AS stars, users.userName FROM actions INNER JOIN users ON actions.account = users.userID WHERE type = '9' AND ".$queryText." timestamp > :time GROUP BY (stars) DESC ORDER BY stars DESC"); +$query->execute([':time' => $time]); +$packcount = 0; +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "stats"); +?> \ No newline at end of file diff --git a/dashboard/stats/unlisted.php b/dashboard/stats/unlisted.php new file mode 100644 index 000000000..03d54558d --- /dev/null +++ b/dashboard/stats/unlisted.php @@ -0,0 +1,87 @@ +title($dl->getLocalizedString("unlistedLevels")); +$dl->printFooter('../'); +if(!isset($_SESSION["accountID"]) || $_SESSION["accountID"] == 0) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noLogin?").'

+ +
+
', 'account')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!empty(trim(ExploitPatch::remove($_GET["search"])))) { + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM levels WHERE unlisted != 0 AND extID = :extid AND levelName LIKE '%".trim(ExploitPatch::remove($_GET["search"]))."%' LIMIT 10 OFFSET $page"); + $query->execute([':extid' => $_SESSION['accountID']]); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM levels WHERE unlisted != 0 AND extID = :extid ORDER BY levelID DESC LIMIT 10 OFFSET $page"); + $query->execute([':extid' => $_SESSION['accountID']]); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); + } +} +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +foreach($result as &$action) $levels .= $dl->generateLevelsCard($action, $modcheck); +$pagel = '
+

'.$dl->getLocalizedString("unlistedLevels").'

+
+ '.$levels.' +
+
+ + + '.$srcbtn.' +
+
'; +/* + bottom row +*/ +//getting count +if(!empty(trim(ExploitPatch::remove($_GET["search"])))) $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted != 0 AND extID = :extid levelName LIKE '%".trim(ExploitPatch::remove($_GET["search"]))."%'"); +else $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted != 0 AND extID = :extid"); +$query->execute([':extid' => $_SESSION['accountID']]); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "account"); +?> \ No newline at end of file diff --git a/dashboard/stats/unlistedMod.php b/dashboard/stats/unlistedMod.php new file mode 100644 index 000000000..aee802d37 --- /dev/null +++ b/dashboard/stats/unlistedMod.php @@ -0,0 +1,87 @@ +title($dl->getLocalizedString("unlistedMod")); +$dl->printFooter('../'); +$modcheck = $gs->checkPermission($_SESSION["accountID"], "dashboardModTools"); +if(!$modcheck) exit($dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("noPermission").'

+ +
+
', 'mod')); +if(isset($_GET["page"]) AND is_numeric($_GET["page"]) AND $_GET["page"] > 0) { + $page = ($_GET["page"] - 1) * 10; + $actualpage = $_GET["page"]; +} else { + $page = 0; + $actualpage = 1; +} +if(!isset($_GET["search"])) $_GET["search"] = ""; +if(!isset($_GET["type"])) $_GET["type"] = ""; +if(!isset($_GET["ng"])) $_GET["ng"] = ""; +$srcbtn = $levels = ""; +$pagelol = explode("/", $_SERVER["REQUEST_URI"]); +$pagelol = $pagelol[count($pagelol)-2]."/".$pagelol[count($pagelol)-1]; +$pagelol = explode("?", $pagelol)[0]; +if(!empty(trim(ExploitPatch::remove($_GET["search"])))) { + $srcbtn = ''; + $query = $db->prepare("SELECT * FROM levels WHERE unlisted != 0 AND levelName LIKE '%".trim(ExploitPatch::remove($_GET["search"]))."%' LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptySearch").'

+ +
+
'); + die(); + } +} else { + $query = $db->prepare("SELECT * FROM levels WHERE unlisted != 0 ORDER BY levelID DESC LIMIT 10 OFFSET $page"); + $query->execute(); + $result = $query->fetchAll(); + if(empty($result)) { + $dl->printSong('
+

'.$dl->getLocalizedString("errorGeneric").'

+
+

'.$dl->getLocalizedString("emptyPage").'

+ +
+
', 'browse'); + die(); + } +} +foreach($result as &$action) $levels .= $dl->generateLevelsCard($action, $modcheck); +$pagel = '
+

'.$dl->getLocalizedString("unlistedMod").'

+
+ '.$levels.' +
+
+ + + '.$srcbtn.' +
+
'; +/* + bottom row +*/ +//getting count +if(!empty(trim(ExploitPatch::remove($_GET["search"])))) $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted != 0 AND levelName LIKE '%".trim(ExploitPatch::remove($_GET["search"]))."%'"); +else $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted != 0"); +$query->execute(); +$packcount = $query->fetchColumn(); +$pagecount = ceil($packcount / 10); +$bottomrow = $dl->generateBottomRow($pagecount, $actualpage); +$dl->printPage($pagel.$bottomrow, true, "mod"); +?> \ No newline at end of file diff --git a/dashboard/uploadGJComment21.php b/dashboard/uploadGJComment21.php deleted file mode 100644 index 2f97f7225..000000000 --- a/dashboard/uploadGJComment21.php +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/tools/logs/cplog.txt b/data/info/filler.txt similarity index 100% rename from tools/logs/cplog.txt rename to data/info/filler.txt diff --git a/database.sql b/database.sql index ccce5149b..236522106 100644 --- a/database.sql +++ b/database.sql @@ -279,7 +279,7 @@ CREATE TABLE `levels` ( `wt` int(11) NOT NULL DEFAULT '0', `wt2` int(11) NOT NULL DEFAULT '0', `ts` int(11) NOT NULL DEFAULT '0', - `settingsString` mediumtext COLLATE utf8_unicode_ci NOT NULL + `settingsString` mediumtext COLLATE utf8_unicode_ci NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- -------------------------------------------------------- diff --git a/database/accounts/backupGJAccountNew.php b/database/accounts/backupGJAccountNew.php index c118a0765..5b7e964c6 100644 --- a/database/accounts/backupGJAccountNew.php +++ b/database/accounts/backupGJAccountNew.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/database/accounts/syncGJAccountNew.php b/database/accounts/syncGJAccountNew.php index e7de5574c..c2156dfff 100644 --- a/database/accounts/syncGJAccountNew.php +++ b/database/accounts/syncGJAccountNew.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/deleteGJAccComment20.php b/deleteGJAccComment20.php index aa7283c4a..5a1c724c1 100644 --- a/deleteGJAccComment20.php +++ b/deleteGJAccComment20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/deleteGJComment.php b/deleteGJComment.php new file mode 100644 index 000000000..5e9806a4f --- /dev/null +++ b/deleteGJComment.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/deleteGJComment19.php b/deleteGJComment19.php new file mode 100644 index 000000000..250cdf2df --- /dev/null +++ b/deleteGJComment19.php @@ -0,0 +1,3 @@ + diff --git a/deleteGJComment20.php b/deleteGJComment20.php index 88be0a28a..5e9806a4f 100644 --- a/deleteGJComment20.php +++ b/deleteGJComment20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/deleteGJFriendRequests20.php b/deleteGJFriendRequests20.php index f33ae2b44..106412c35 100644 --- a/deleteGJFriendRequests20.php +++ b/deleteGJFriendRequests20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/deleteGJLevelList.php b/deleteGJLevelList.php index 0cb850d86..b0f20ef2f 100644 --- a/deleteGJLevelList.php +++ b/deleteGJLevelList.php @@ -1,3 +1,3 @@ - \ No newline at end of file diff --git a/deleteGJLevelUser20.php b/deleteGJLevelUser20.php index 45fe25f11..9c6dcc362 100644 --- a/deleteGJLevelUser20.php +++ b/deleteGJLevelUser20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/deleteGJMessages20.php b/deleteGJMessages20.php index 7e8687617..35386e807 100644 --- a/deleteGJMessages20.php +++ b/deleteGJMessages20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/downloadGJLevel.php b/downloadGJLevel.php index 8af2e82fc..e7045c886 100644 --- a/downloadGJLevel.php +++ b/downloadGJLevel.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/downloadGJLevel19.php b/downloadGJLevel19.php index 8af2e82fc..e7045c886 100644 --- a/downloadGJLevel19.php +++ b/downloadGJLevel19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/downloadGJLevel20.php b/downloadGJLevel20.php index 8af2e82fc..e7045c886 100644 --- a/downloadGJLevel20.php +++ b/downloadGJLevel20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/downloadGJLevel21.php b/downloadGJLevel21.php index 8af2e82fc..e7045c886 100644 --- a/downloadGJLevel21.php +++ b/downloadGJLevel21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/downloadGJLevel22.php b/downloadGJLevel22.php index 8af2e82fc..e7045c886 100644 --- a/downloadGJLevel22.php +++ b/downloadGJLevel22.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/downloadGJMessage20.php b/downloadGJMessage20.php index 241cd9801..c0e4e135a 100644 --- a/downloadGJMessage20.php +++ b/downloadGJMessage20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getAccountURL.php b/getAccountURL.php index aeae6e282..71a5fca82 100644 --- a/getAccountURL.php +++ b/getAccountURL.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getCustomContentURL.php b/getCustomContentURL.php index 5e29bb1d1..71a5fca82 100644 --- a/getCustomContentURL.php +++ b/getCustomContentURL.php @@ -1 +1,3 @@ -https://geometrydashfiles.b-cdn.net \ No newline at end of file + \ No newline at end of file diff --git a/getGJAccountComments20.php b/getGJAccountComments20.php index b15c9d6be..395454c46 100644 --- a/getGJAccountComments20.php +++ b/getGJAccountComments20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJChallenges.php b/getGJChallenges.php index f8e203ec8..47b6c5658 100644 --- a/getGJChallenges.php +++ b/getGJChallenges.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJCommentHistory.php b/getGJCommentHistory.php index 02b0a0f59..749a45a91 100644 --- a/getGJCommentHistory.php +++ b/getGJCommentHistory.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJComments.php b/getGJComments.php index 02b0a0f59..749a45a91 100644 --- a/getGJComments.php +++ b/getGJComments.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJComments19.php b/getGJComments19.php index 02b0a0f59..749a45a91 100644 --- a/getGJComments19.php +++ b/getGJComments19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJComments20.php b/getGJComments20.php index 02b0a0f59..749a45a91 100644 --- a/getGJComments20.php +++ b/getGJComments20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJComments21.php b/getGJComments21.php index 02b0a0f59..749a45a91 100644 --- a/getGJComments21.php +++ b/getGJComments21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJCreators.php b/getGJCreators.php index 1aedc9046..128ee6764 100644 --- a/getGJCreators.php +++ b/getGJCreators.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJCreators19.php b/getGJCreators19.php index 1aedc9046..128ee6764 100644 --- a/getGJCreators19.php +++ b/getGJCreators19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJDailyLevel.php b/getGJDailyLevel.php index 97375067b..63e810c04 100644 --- a/getGJDailyLevel.php +++ b/getGJDailyLevel.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJFriendRequests20.php b/getGJFriendRequests20.php index d68492746..d54aa3686 100644 --- a/getGJFriendRequests20.php +++ b/getGJFriendRequests20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJGauntlets.php b/getGJGauntlets.php index 7f5ae7392..b202dd2a8 100644 --- a/getGJGauntlets.php +++ b/getGJGauntlets.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJGauntlets21.php b/getGJGauntlets21.php index 7f5ae7392..b202dd2a8 100644 --- a/getGJGauntlets21.php +++ b/getGJGauntlets21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJLevelLists.php b/getGJLevelLists.php index 80a7489dc..7d6df05fb 100644 --- a/getGJLevelLists.php +++ b/getGJLevelLists.php @@ -1,3 +1,3 @@ - \ No newline at end of file diff --git a/getGJLevelScores.php b/getGJLevelScores.php index 8de008be7..7e2ea47ca 100644 --- a/getGJLevelScores.php +++ b/getGJLevelScores.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJLevelScores211.php b/getGJLevelScores211.php index 8de008be7..7e2ea47ca 100644 --- a/getGJLevelScores211.php +++ b/getGJLevelScores211.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJLevelScoresPlat.php b/getGJLevelScoresPlat.php index bd7afdccd..223a4d96f 100644 --- a/getGJLevelScoresPlat.php +++ b/getGJLevelScoresPlat.php @@ -1,3 +1,3 @@ - \ No newline at end of file diff --git a/getGJLevels.php b/getGJLevels.php index 1cfbdf5ee..6f07f84d2 100644 --- a/getGJLevels.php +++ b/getGJLevels.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJLevels19.php b/getGJLevels19.php index 1cfbdf5ee..6f07f84d2 100644 --- a/getGJLevels19.php +++ b/getGJLevels19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJLevels20.php b/getGJLevels20.php index 1cfbdf5ee..6f07f84d2 100644 --- a/getGJLevels20.php +++ b/getGJLevels20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJLevels21.php b/getGJLevels21.php index 1cfbdf5ee..6f07f84d2 100644 --- a/getGJLevels21.php +++ b/getGJLevels21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJMapPacks.php b/getGJMapPacks.php index cf4e0cc71..94cdec5c4 100644 --- a/getGJMapPacks.php +++ b/getGJMapPacks.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJMapPacks20.php b/getGJMapPacks20.php index cf4e0cc71..94cdec5c4 100644 --- a/getGJMapPacks20.php +++ b/getGJMapPacks20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJMapPacks21.php b/getGJMapPacks21.php index cf4e0cc71..94cdec5c4 100644 --- a/getGJMapPacks21.php +++ b/getGJMapPacks21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJMessages20.php b/getGJMessages20.php index 948de31f7..62f571718 100644 --- a/getGJMessages20.php +++ b/getGJMessages20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJRewards.php b/getGJRewards.php index 068dd18b1..d657cf61e 100644 --- a/getGJRewards.php +++ b/getGJRewards.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJScores.php b/getGJScores.php index 8a3251b4b..5933900de 100644 --- a/getGJScores.php +++ b/getGJScores.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJScores19.php b/getGJScores19.php index 8a3251b4b..5933900de 100644 --- a/getGJScores19.php +++ b/getGJScores19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJScores20.php b/getGJScores20.php index 8a3251b4b..5933900de 100644 --- a/getGJScores20.php +++ b/getGJScores20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJSecretReward.php b/getGJSecretReward.php new file mode 100644 index 000000000..57e97c47d --- /dev/null +++ b/getGJSecretReward.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/getGJSongInfo.php b/getGJSongInfo.php index 73b3b8799..afcf0d0e9 100644 --- a/getGJSongInfo.php +++ b/getGJSongInfo.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJTopArtists.php b/getGJTopArtists.php index f65374aab..d26285c84 100644 --- a/getGJTopArtists.php +++ b/getGJTopArtists.php @@ -1,3 +1,3 @@ diff --git a/getGJUserInfo20.php b/getGJUserInfo20.php index 747991bbd..0c6b6aee7 100644 --- a/getGJUserInfo20.php +++ b/getGJUserInfo20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJUserList20.php b/getGJUserList20.php index bade29746..cc8aa329b 100644 --- a/getGJUserList20.php +++ b/getGJUserList20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/getGJUsers20.php b/getGJUsers20.php index 13b97371f..cd9d0765e 100644 --- a/getGJUsers20.php +++ b/getGJUsers20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/incl/comments/deleteGJAccComment.php b/incl/comments/deleteGJAccComment.php index a97a84727..5ee277906 100644 --- a/incl/comments/deleteGJAccComment.php +++ b/incl/comments/deleteGJAccComment.php @@ -1,19 +1,23 @@ getUserID($accountID); +$getCommentData = $db->prepare('SELECT * FROM acccomments WHERE commentID = :commentID'); +$getCommentData->execute([':commentID' => $commentID]); +$getCommentData = $getCommentData->fetch(); +if(!$getCommentData) exit("-1"); if($gs->checkPermission($accountID, "actionDeleteComment") == 1) { $query = $db->prepare("DELETE FROM acccomments WHERE commentID = :commentID LIMIT 1"); - $query->execute([':commentID' => $commentID]); -}else{ + if($query->execute([':commentID' => $commentID])) $gs->logAction($accountID, 12, $getCommentData['userName'], $getCommentData['comment'], $accountID, $commentID, ($getCommentData['likes'] - $getCommentData['dislikes'])); +} else { $query = $db->prepare("DELETE FROM acccomments WHERE commentID=:commentID AND userID=:userID LIMIT 1"); - $query->execute([':userID' => $userID, ':commentID' => $commentID]); + if($query->execute([':userID' => $userID, ':commentID' => $commentID])) $gs->logAction($accountID, 12, $getCommentData['userName'], $getCommentData['comment'], $gs->getExtID($userID), $commentID, ($getCommentData['likes'] - $getCommentData['dislikes'])); } echo "1"; \ No newline at end of file diff --git a/incl/comments/deleteGJComment.php b/incl/comments/deleteGJComment.php index aca68a296..3dbf42979 100644 --- a/incl/comments/deleteGJComment.php +++ b/incl/comments/deleteGJComment.php @@ -1,23 +1,26 @@ getIDFromPost(); $userID = $gs->getUserID($accountID); -$query = $db->prepare("DELETE FROM comments WHERE commentID=:commentID AND userID=:userID LIMIT 1"); +$getCommentData = $db->prepare('SELECT * FROM comments WHERE commentID = :commentID'); +$getCommentData->execute([':commentID' => $commentID]); +$getCommentData = $getCommentData->fetch(); +$query = $db->prepare("DELETE FROM comments WHERE commentID = :commentID AND userID = :userID LIMIT 1"); $query->execute([':commentID' => $commentID, ':userID' => $userID]); -if($query->rowCount() == 0){ +if($query->rowCount() == 0) { $query = $db->prepare("SELECT users.extID FROM comments INNER JOIN levels ON levels.levelID = comments.levelID INNER JOIN users ON levels.userID = users.userID WHERE commentID = :commentID"); $query->execute([':commentID' => $commentID]); $creatorAccID = $query->fetchColumn(); - if($creatorAccID == $accountID || $gs->checkPermission($accountID, "actionDeleteComment") == 1){ - $query = $db->prepare("DELETE FROM comments WHERE commentID=:commentID LIMIT 1"); - $query->execute([':commentID' => $commentID]); + if($creatorAccID == $accountID || $gs->checkPermission($accountID, "actionDeleteComment") == 1) { + $query = $db->prepare("DELETE FROM comments WHERE commentID = :commentID LIMIT 1"); + if($query->execute([':commentID' => $commentID])) $gs->logAction($accountID, 13, $getCommentData['userName'], $getCommentData['comment'], $creatorAccID, $commentID, ($getCommentData['likes'] - $getCommentData['dislikes']), $getCommentData['levelID']); } -} -echo "1"; +} else $gs->logAction($accountID, 13, $getCommentData['userName'], $getCommentData['comment'], $accountID, $commentID, ($getCommentData['likes'] - $getCommentData['dislikes']), $getCommentData['levelID']); +echo "1"; \ No newline at end of file diff --git a/incl/comments/getGJAccountComments.php b/incl/comments/getGJAccountComments.php index 1e52b5406..b2285fc41 100644 --- a/incl/comments/getGJAccountComments.php +++ b/incl/comments/getGJAccountComments.php @@ -1,13 +1,13 @@ getUserID($accountid); $query = "SELECT comment, userID, likes, isSpam, commentID, timestamp FROM acccomments WHERE userID = :userID ORDER BY timeStamp DESC LIMIT 10 OFFSET $commentpage"; @@ -21,9 +21,19 @@ $countquery->execute([':userID' => $userID]); $commentcount = $countquery->fetchColumn(); foreach($result as &$comment1) { - if($comment1["commentID"]!=""){ - $uploadDate = date("d/m/Y G:i", $comment1["timestamp"]); - $commentstring .= "2~".$comment1["comment"]."~3~".$comment1["userID"]."~4~".$comment1["likes"]."~5~0~7~".$comment1["isSpam"]."~9~".$uploadDate."~6~".$comment1["commentID"]."|"; + if($comment1["commentID"]!="") { + $uploadDate = $gs->makeTime($comment1["timestamp"]); + $likes = $comment1["likes"]; // - $comment1["dislikes"]; + $reply = $db->prepare("SELECT count(*) FROM replies WHERE commentID = :id"); + $reply->execute([':id' => $comment1["commentID"]]); + $reply = $reply->fetchColumn(); + if($reply > 0) { + $rep = $reply > 1 ? 'replies)' : 'reply)'; + $comment1["comment"] = ExploitPatch::url_base64_encode(ExploitPatch::url_base64_decode($comment1["comment"]).' ('.$reply.' '.$rep); + } + $comment1['comment'] = ExploitPatch::url_base64_encode(trim(ExploitPatch::translit(ExploitPatch::url_base64_decode($comment1['comment'])))); + if($enableCommentLengthLimiter) $comment1['comment'] = ExploitPatch::url_base64_encode(substr(ExploitPatch::url_base64_decode($comment1['comment']), 0, $maxAccountCommentLength)); + $commentstring .= "2~".$comment1["comment"]."~3~".$comment1["userID"]."~4~".$likes."~5~0~7~".$comment1["isSpam"]."~9~".$uploadDate."~6~".$comment1["commentID"]."|"; } } $commentstring = substr($commentstring, 0, -1); diff --git a/incl/comments/getGJComments.php b/incl/comments/getGJComments.php index 142bb26f1..3c5b4e1a3 100644 --- a/incl/comments/getGJComments.php +++ b/incl/comments/getGJComments.php @@ -1,78 +1,90 @@ 0) { + $levelExists = $db->prepare("SELECT COUNT(*) FROM levels WHERE levelID = :levelID"); + $levelExists->execute([':levelID' => $filterID]); + } else { + $levelExists = $db->prepare("SELECT COUNT(*) FROM lists WHERE listID = :levelID"); + $levelExists->execute([':levelID' => ExploitPatch::number($filterID)]); + } + if($levelExists->fetchColumn() == 0) exit("-2"); // Don't return comments from nonexistent levels + $userListJoin = ""; +} elseif(isset($_POST['userID'])) { $filterColumn = 'userID'; $filterToFilter = 'comments.'; $displayLevelID = true; - $filterID = ExploitPatch::remove($_POST["userID"]); + $filterID = ExploitPatch::number($_POST["userID"]); $userListColumns = ", levels.unlisted"; - $userListJoin = "INNER JOIN levels ON comments.levelID = levels.levelID"; - $userListWhere = "AND levels.unlisted = 0"; -} -else - exit(-1); + $userListJoin = " LEFT JOIN levels ON comments.levelID = levels.levelID LEFT JOIN lists ON (comments.levelID * -1) = lists.listID"; + $userListWhere = "AND (levels.unlisted = 0 OR lists.unlisted = 0)"; + $accountID = !empty($_POST['accountID']) ? GJPCheck::getAccountIDOrDie() : 0; + $targetAccountID = $gs->getExtID($filterID); + $cS = $db->prepare("SELECT cS FROM accounts WHERE accountID = :targetAccountID"); + $cS->execute([':targetAccountID' => $targetAccountID]); + $cS = $cS->fetchColumn(); + if($accountID != $targetAccountID && (($cS == 1 && !$gs->isFriends($accountID, $targetAccountID)) || $cS > 1)) exit("-2"); +} else exit("-1"); -$countquery = "SELECT count(*) FROM comments $userListJoin WHERE ${filterToFilter}${filterColumn} = :filterID $userListWhere"; +$countquery = "SELECT count(*) FROM comments {$userListJoin} WHERE ${filterToFilter}${filterColumn} = :filterID {$userListWhere}"; $countquery = $db->prepare($countquery); $countquery->execute([':filterID' => $filterID]); $commentcount = $countquery->fetchColumn(); -if($commentcount == 0){ - exit("-2"); -} - +if($commentcount == 0) exit("-2"); -$query = "SELECT comments.levelID, comments.commentID, comments.timestamp, comments.comment, comments.userID, comments.likes, comments.isSpam, comments.percent, users.userName, users.icon, users.color1, users.color2, users.iconType, users.special, users.extID FROM comments LEFT JOIN users ON comments.userID = users.userID ${userListJoin} WHERE comments.${filterColumn} = :filterID ${userListWhere} ORDER BY comments.${modeColumn} DESC LIMIT ${count} OFFSET ${commentpage}"; +$query = "SELECT comments.levelID, comments.commentID, comments.timestamp, comments.comment, comments.userID, comments.likes, comments.isSpam, comments.percent, users.userName, users.clan, users.icon, users.color1, users.color2, users.iconType, users.special, users.extID FROM comments LEFT JOIN users ON comments.userID = users.userID {$userListJoin} WHERE comments.${filterColumn} = :filterID {$userListWhere} ORDER BY comments.${modeColumn} DESC LIMIT ${count} OFFSET ${commentpage}"; $query = $db->prepare($query); $query->execute([':filterID' => $filterID]); $result = $query->fetchAll(); $visiblecount = $query->rowCount(); foreach($result as &$comment1) { - if($comment1["commentID"]!=""){ - $uploadDate = date("d/m/Y G.i", $comment1["timestamp"]); - $commentText = ($gameVersion < 20) ? base64_decode($comment1["comment"]) : $comment1["comment"]; + if(!empty($comment1["commentID"])) { + $uploadDate = $gs->makeTime($comment1["timestamp"]); + $comment1['comment'] = ExploitPatch::translit(ExploitPatch::url_base64_decode($comment1["comment"])); + if($enableCommentLengthLimiter) $commentText = mb_substr($comment1['comment'], 0, $maxCommentLength); + $commentText = ($gameVersion < 20) ? ExploitPatch::gd_escape($comment1["comment"]) : ExploitPatch::url_base64_encode($comment1["comment"]); if($displayLevelID) $commentstring .= "1~".$comment1["levelID"]."~"; - $commentstring .= "2~".$commentText."~3~".$comment1["userID"]."~4~".$comment1["likes"]."~5~0~7~".$comment1["isSpam"]."~9~".$uploadDate."~6~".$comment1["commentID"]."~10~".$comment1["percent"]; - if ($comment1['userName']) { //TODO: get rid of queries caused by getMaxValuePermission and getAccountCommentColor - $extID = is_numeric($comment1["extID"]) ? $comment1["extID"] : 0; - if($binaryVersion > 31){ - $badge = $gs->getMaxValuePermission($extID, "modBadgeLevel"); - $colorString = $badge > 0 ? "~12~".$gs->getAccountCommentColor($extID) : ""; - - $commentstring .= "~11~${badge}${colorString}:1~".$comment1["userName"]."~7~1~9~".$comment1["icon"]."~10~".$comment1["color1"]."~11~".$comment1["color2"]."~14~".$comment1["iconType"]."~15~".$comment1["special"]."~16~".$extID; - }elseif(!in_array($comment1["userID"], $users)){ + if($commentAutoLike && array_key_exists($comment1["commentID"], $specialCommentLikes)) $likes = $comment1["likes"] * $specialCommentLikes[$comment1["commentID"]]; // Multiply by the specified value + else $likes = $comment1["likes"]; // Normal like value + if($likes < -2) $comment1["isSpam"] = 1; + $commentstring .= "2~".$commentText."~3~".$comment1["userID"]."~4~".$likes."~5~0~7~".$comment1["isSpam"]."~9~".$uploadDate."~6~".$comment1["commentID"]."~10~".$comment1["percent"]; + if($comment1['userName']) { + $comment1["extID"] = is_numeric($comment1["extID"]) ? $comment1["extID"] : 0; + $comment1['userName'] = $gs->makeClanUsername($comment1); + if($binaryVersion > 31) { + $badge = $gs->getMaxValuePermission($comment1["extID"], "modBadgeLevel"); + $colorString = $badge > 0 ? "~12~".$gs->getAccountCommentColor($comment1["extID"]) : ""; + $commentstring .= "~11~${badge}${colorString}:1~".$comment1["userName"]."~7~1~9~".$comment1["icon"]."~10~".$comment1["color1"]."~11~".$comment1["color2"]."~14~".$comment1["iconType"]."~15~".$comment1["special"]."~16~".$comment1["extID"]; + } elseif(!in_array($comment1["userID"], $users)) { $users[] = $comment1["userID"]; - $userstring .= $comment1["userID"] . ":" . $comment1["userName"] . ":" . $extID . "|"; + $userstring .= $comment1["userID"] . ":" . $comment1["userName"] . ":" . $comment1["extID"] . "|"; } $commentstring .= "|"; } @@ -81,9 +93,9 @@ $commentstring = substr($commentstring, 0, -1); echo $commentstring; -if($binaryVersion < 32){ +if($binaryVersion < 32) { $userstring = substr($userstring, 0, -1); echo "#$userstring"; } echo "#${commentcount}:${commentpage}:${visiblecount}"; -?> +?> \ No newline at end of file diff --git a/incl/comments/uploadGJAccComment.php b/incl/comments/uploadGJAccComment.php index 3f8518ccb..e2de1cf64 100644 --- a/incl/comments/uploadGJAccComment.php +++ b/incl/comments/uploadGJAccComment.php @@ -1,28 +1,31 @@ 20 ? 'temp_0_Account posting is currently disabled!' : '-1')); $userName = ExploitPatch::remove($_POST["userName"]); $comment = ExploitPatch::remove($_POST["comment"]); +$commentLength = ($gameVersion >= 20) ? mb_strlen(ExploitPatch::url_base64_decode($comment)) : mb_strlen($comment); +if($enableCommentLengthLimiter && $commentLength > $maxAccountCommentLength) exit("temp_0_You cannot post account comments above $maxAccountCommentLength characters! (Your's ".$commentLength.")"); $accountID = GJPCheck::getAccountIDOrDie(); -$userID = $mainLib->getUserID($accountID, $userName); +$userID = $gs->getUserID($accountID, $userName); $uploadDate = time(); //usercheck -if($accountID != "" AND $comment != ""){ - $decodecomment = base64_decode($comment); - if(Commands::doProfileCommands($accountID, $decodecomment)){ - exit("-1"); - } - $query = $db->prepare("INSERT INTO acccomments (userName, comment, userID, timeStamp) - VALUES (:userName, :comment, :userID, :uploadDate)"); +if($accountID != "" AND $comment != "") { + $decodecomment = ExploitPatch::url_base64_decode($comment); + if(Commands::doProfileCommands($accountID, $decodecomment)) exit("-1"); + $checkCommentBan = $gs->getPersonBan($accountID, $userID, 3); + if($checkCommentBan) ($_POST['gameVersion'] > 20 ? exit("temp_".($checkCommentBan['expires'] - time())."_".ExploitPatch::translit(ExploitPatch::url_base64_decode($checkCommentBan['reason']))) : exit('-10')); + $query = $db->prepare("INSERT INTO acccomments (userName, comment, userID, timeStamp) VALUES (:userName, :comment, :userID, :uploadDate)"); $query->execute([':userName' => $userName, ':comment' => $comment, ':userID' => $userID, ':uploadDate' => $uploadDate]); + Automod::checkAccountPostsSpamming($userID); + $gs->logAction($accountID, 14, $userName, $comment, $db->lastInsertId()); echo 1; -}else{ - echo -1; -} +} else echo -1; ?> \ No newline at end of file diff --git a/incl/comments/uploadGJComment.php b/incl/comments/uploadGJComment.php index 56b07d700..5817bfdc1 100644 --- a/incl/comments/uploadGJComment.php +++ b/incl/comments/uploadGJComment.php @@ -1,49 +1,68 @@ 20 ? 'temp_0_Commenting is currently disabled!' : '-1')); +$userName = !empty($_POST['userName']) ? ExploitPatch::charclean($_POST['userName']) : ""; +$comment = !empty($_POST['comment']) ? $_POST['comment'] : ""; $gameVersion = !empty($_POST['gameVersion']) ? ExploitPatch::number($_POST['gameVersion']) : 0; -$comment = ExploitPatch::remove($_POST['comment']); -$comment = ($gameVersion < 20) ? base64_encode($comment) : $comment; -$levelID = ($_POST['levelID'] < 0 ? '-' : '').ExploitPatch::number($_POST["levelID"]); -$percent = !empty($_POST["percent"]) ? ExploitPatch::remove($_POST["percent"]) : 0; +$commentLength = ($gameVersion >= 20) ? mb_strlen(ExploitPatch::url_base64_decode($comment)) : mb_strlen($comment); +if($enableCommentLengthLimiter && $commentLength > $maxCommentLength) exit("temp_0_You cannot post comments above $maxCommentLength characters! (Your's ".$commentLength.")"); +$comment = ($gameVersion < 20) ? ExploitPatch::url_base64_encode(ExploitPatch::rucharclean($comment)) : ExploitPatch::url_base64_encode(ExploitPatch::rucharclean(ExploitPatch::url_base64_decode($comment))); +$levelID = ExploitPatch::numbercolon($_POST["levelID"]); +$percent = !empty($_POST["percent"]) ? ExploitPatch::number($_POST["percent"]) : 0; + +if(strpos($levelID, '-') === 0) { + $checkLevelExist = $db->prepare("SELECT * FROM lists WHERE listID = :levelID"); + $checkLevelExist->execute([':levelID' => ltrim($levelID, '-')]); +} else { + $checkLevelExist = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); + $checkLevelExist->execute([':levelID' => $levelID]); +} +if($checkLevelExist->rowCount() == 0) die("-1"); -$id = $mainLib->getIDFromPost(); +$id = $gs->getIDFromPost(); $register = is_numeric($id); -$userID = $mainLib->getUserID($id, $userName); +$userID = $gs->getUserID($id, $userName); $uploadDate = time(); -$decodecomment = base64_decode($comment); -if(Commands::doCommands($id, $decodecomment, $levelID)){ - exit($gameVersion > 20 ? "temp_0_Command executed successfully!" : "-1"); -} -if($id != "" AND $comment != ""){ +$decodecomment = ExploitPatch::url_base64_decode($comment); +$command = Commands::doCommands($id, $decodecomment, $levelID); +if($command) ($_POST['gameVersion'] > 20 ? exit("temp_0_".$command) : exit('-1')); +if($percent < 0 || $percent > 100) exit("temp_0_Invalid percentage!"); +$checkCommentBan = $gs->getPersonBan($id, $userID, 3); +if($checkCommentBan) ($_POST['gameVersion'] > 20 ? exit("temp_".($checkCommentBan['expires'] - time())."_".ExploitPatch::translit(ExploitPatch::url_base64_decode($checkCommentBan['reason']))) : exit('-10')); +if($checkLevelExist->fetch()['commentLocked']) exit("temp_0_Comments on this ".(strpos($levelID, '-') === 0 ? 'list' : 'level')." are locked!"); +if($id != "" AND $comment != "") { $query = $db->prepare("INSERT INTO comments (userName, comment, levelID, userID, timeStamp, percent) VALUES (:userName, :comment, :levelID, :userID, :uploadDate, :percent)"); $query->execute([':userName' => $userName, ':comment' => $comment, ':levelID' => $levelID, ':userID' => $userID, ':uploadDate' => $uploadDate, ':percent' => $percent]); + $gs->logAction($id, 15, $userName, $comment, $db->lastInsertId(), $levelID); + Automod::checkCommentsSpamming($userID); echo 1; - if($register){ - //TODO: improve this - if($percent != 0){ + if($register) { + if($percent != 0) { $query2 = $db->prepare("SELECT percent FROM levelscores WHERE accountID = :accountID AND levelID = :levelID"); $query2->execute([':accountID' => $id, ':levelID' => $levelID]); $result = $query2->fetchColumn(); - if ($query2->rowCount() == 0) { + if($query2->rowCount() == 0) { $query = $db->prepare("INSERT INTO levelscores (accountID, levelID, percent, uploadDate) VALUES (:accountID, :levelID, :percent, :uploadDate)"); + $gs->logAction($id, 34, $levelID, $percent); } else { - if($result < $percent){ + if($result < $percent) { + $gs->logAction($id, 35, $levelID, $percent); $query = $db->prepare("UPDATE levelscores SET percent=:percent, uploadDate=:uploadDate WHERE accountID=:accountID AND levelID=:levelID"); $query->execute([':accountID' => $id, ':levelID' => $levelID, ':percent' => $percent, ':uploadDate' => $uploadDate]); } } } } -}else{ +} else { echo -1; } ?> diff --git a/incl/levelpacks/deleteGJLevelList.php b/incl/levelpacks/deleteGJLevelList.php index 4c87ab7a0..f283ed3a8 100644 --- a/incl/levelpacks/deleteGJLevelList.php +++ b/incl/levelpacks/deleteGJLevelList.php @@ -1,15 +1,20 @@ -getListOwner($listID)) { - $list = $db->prepare('DELETE FROM lists WHERE listID = :listID'); - $list->execute([':listID' => $listID]); - exit("1"); -} else exit("-1"); +getListOwner($listID)) { + $listData = $db->prepare('SELECT * FROM lists WHERE listID = :listID AND accountID = :accountID'); + $listData->execute([':listID' => $listID, ':accountID' => $accountID]); + $listData = $listData->fetch(); + $list = $db->prepare('DELETE FROM lists WHERE listID = :listID'); + $list->execute([':listID' => $listID]); + $gs->logAction($accountID, 19, $listData['listName'], $listData['listlevels'], $listID, $listData['difficulty'], $listData['unlisted']); + $gs->sendLogsListChangeWebhook($listID, $accountID, $listData); + exit("1"); +} else exit("-1"); ?> \ No newline at end of file diff --git a/incl/levelpacks/getGJGauntlets.php b/incl/levelpacks/getGJGauntlets.php index bf0cc607b..25530413e 100644 --- a/incl/levelpacks/getGJGauntlets.php +++ b/incl/levelpacks/getGJGauntlets.php @@ -1,6 +1,6 @@ '.(time()-604800); - break; - case 4: // RECENT - $order = "uploadDate"; - break; - case 5: - $params[] = "lists.accountID = '$str'"; - break; - case 6: // TOP LISTS - $params[] = "lists.starStars > 0"; - $params[] = "lists.starFeatured > 0"; - $order = "downloads"; - break; - case 11: // RATED - $params[] = "lists.starStars > 0"; - $order = "downloads"; - break; - case 12: //FOLLOWED - $followed = ExploitPatch::numbercolon($_POST["followed"]); - if(empty($followed)) $followed = 0; // No SQL syntax error today - $params[] = "lists.accountID IN ($followed)"; - break; - case 13: //FRIENDS - $accountID = GJPCheck::getAccountIDOrDie(); - $peoplearray = $gs->getFriends($accountID); - $whereor = implode(",", $peoplearray); - $params[] = "lists.accountID IN ($whereor)"; - break; - case 7: // MAGIC - case 27: // SENT - $params[] = "suggest.suggestLevelId < 0"; - $order = "suggest.timestamp"; - $morejoins = "LEFT JOIN suggest ON lists.listID*-1 LIKE suggest.suggestLevelId"; - break; -} -//ACTUAL QUERY EXECUTION -$querybase = "FROM lists LEFT JOIN users ON lists.accountID LIKE users.extID $morejoins"; -if(!empty($params)){ - $querybase .= " WHERE (" . implode(" ) AND ( ", $params) . ")"; -} -$query = "SELECT lists.*, UNIX_TIMESTAMP(uploadDate) AS uploadDateUnix, UNIX_TIMESTAMP(updateDate) AS updateDateUnix, users.userID, users.userName, users.extID $querybase"; -if($order){ - $query .= "ORDER BY $order DESC"; -} -$query .= " LIMIT 10 OFFSET $offset"; -//echo $query; -$countquery = "SELECT count(*) $querybase"; -//echo $query; -$query = $db->prepare($query); -$query->execute(); -//echo $countquery; -$countquery = $db->prepare($countquery); -$countquery->execute(); -$totallvlcount = $countquery->fetchColumn(); -$result = $query->fetchAll(); -$levelcount = $query->rowCount(); -foreach($result as &$list) { - if(!$list['uploadDateUnix']) $list['uploadDateUnix'] = 0; - if(!$list['updateDateUnix']) $list['updateDateUnix'] = 0; - $lvlstring .= "1:{$list['listID']}:2:{$list['listName']}:3:{$list['listDesc']}:5:{$list['listVersion']}:49:{$list['accountID']}:50:{$list['userName']}:10:{$list['downloads']}:7:{$list['starDifficulty']}:14:{$list['likes']}:19:{$list['starFeatured']}:51:{$list['listlevels']}:55:{$list['starStars']}:56:{$list['countForReward']}:28:{$list['uploadDateUnix']}:29:{$list['updateDateUnix']}"."|"; - $userstring .= $gs->getUserString($list)."|"; -} -if(empty($lvlstring)) exit("-1"); -if(!empty($str) AND is_numeric($str) AND $levelcount == 1) { - $ip = $gs->getIP(); - $query6 = $db->prepare("SELECT count(*) FROM actions_downloads WHERE levelID=:listID AND ip=INET6_ATON(:ip)"); - $query6->execute([':listID' => '-'.$str, ':ip' => $ip]); - if($query6->fetchColumn() < 2){ - $query2=$db->prepare("UPDATE lists SET downloads = downloads + 1 WHERE listID = :listID"); - $query2->execute([':listID' => $str]); - $query6 = $db->prepare("INSERT INTO actions_downloads (levelID, ip) VALUES - (:listID,INET6_ATON(:ip))"); - $query6->execute([':listID' => '-'.$str, ':ip' => $ip]); - } -} -$lvlstring = substr($lvlstring, 0, -1); -$userstring = substr($userstring, 0, -1); -echo $lvlstring."#".$userstring; -echo "#".$totallvlcount.":".$offset.":10"; -echo "#"; -echo "Sa1ntSosetHuiHelloFromGreenCatsServerLOL"; -//echo GenerateHash::genMulti($lvlsmultistring); -?> \ No newline at end of file + '.(time()-604800); + break; + case 4: // RECENT + $order = "uploadDate"; + break; + case 5: + if(!empty($_POST['accountID'])) { + $accountID = GJPCheck::getAccountIDOrDie(); + if($accountID == $str) $params = []; + } + $params[] = "lists.accountID = '$str'"; + break; + case 6: // TOP LISTS + $params[] = "lists.starStars > 0"; + $params[] = "lists.starFeatured > 0"; + $order = "downloads"; + break; + case 11: // RATED + $params[] = "lists.starStars > 0"; + $order = "downloads"; + break; + case 12: //FOLLOWED + $followed = ExploitPatch::numbercolon($_POST["followed"]); + if(empty($followed)) $followed = 0; // No SQL syntax error today + $params[] = "lists.accountID IN ($followed)"; + break; + case 13: //FRIENDS + $accountID = GJPCheck::getAccountIDOrDie(); + $peoplearray = $gs->getFriends($accountID); + $whereor = implode(",", $peoplearray); + $params[] = "lists.accountID IN ($whereor)"; + break; + case 7: // MAGIC + $order = "likes"; + break; + case 27: // SENT + $params[] = "suggest.suggestLevelId < 0"; + $order = "suggest.timestamp"; + $morejoins = "LEFT JOIN suggest ON lists.listID*-1 LIKE suggest.suggestLevelId"; + break; +} +//ACTUAL QUERY EXECUTION +$querybase = "FROM lists LEFT JOIN users ON lists.accountID LIKE users.extID $morejoins"; +if(!empty($params)){ + $querybase .= " WHERE (" . implode(" ) AND ( ", $params) . ")"; +} +$query = "SELECT lists.*, UNIX_TIMESTAMP(uploadDate) AS uploadDateUnix, UNIX_TIMESTAMP(updateDate) AS updateDateUnix, users.userID, users.userName, users.extID, users.clan $querybase"; +if($order){ + $query .= "ORDER BY $order DESC"; +} +$query .= " LIMIT 10 OFFSET $offset"; +//echo $query; +$countquery = "SELECT count(*) $querybase"; +//echo $query; +$query = $db->prepare($query); +$query->execute(); +//echo $countquery; +$countquery = $db->prepare($countquery); +$countquery->execute(); +$totallvlcount = $countquery->fetchColumn(); +$result = $query->fetchAll(); +$levelcount = $query->rowCount(); +foreach($result as &$list) { + if(!$list['uploadDateUnix']) $list['uploadDateUnix'] = 0; + if(!$list['updateDateUnix']) $list['updateDateUnix'] = 0; + $list['listName'] = ExploitPatch::translit($list['listName']); + $list['listDesc'] = ExploitPatch::translit($list['listDesc']); + $list['likes'] = $list['likes']; // - $list['dislikes']; + $list['userName'] = $gs->makeClanUsername($list); + $lvlstring .= "1:{$list['listID']}:2:{$list['listName']}:3:{$list['listDesc']}:5:{$list['listVersion']}:49:{$list['accountID']}:50:{$list['userName']}:10:{$list['downloads']}:7:{$list['starDifficulty']}:14:{$list['likes']}:19:{$list['starFeatured']}:51:{$list['listlevels']}:55:{$list['starStars']}:56:{$list['countForReward']}:28:{$list['uploadDateUnix']}:29:{$list['updateDateUnix']}"."|"; + $userstring .= $gs->getUserString($list)."|"; +} +if(empty($lvlstring)) exit("-1"); +if(!empty($str) AND is_numeric($str) AND $levelcount == 1) { + $ip = $gs->getIP(); + $query6 = $db->prepare("SELECT count(*) FROM actions_downloads WHERE levelID=:listID AND ip=INET6_ATON(:ip)"); + $query6->execute([':listID' => '-'.$str, ':ip' => $ip]); + if($query6->fetchColumn() < 2){ + $query2=$db->prepare("UPDATE lists SET downloads = downloads + 1 WHERE listID = :listID"); + $query2->execute([':listID' => $str]); + $query6 = $db->prepare("INSERT INTO actions_downloads (levelID, ip) VALUES + (:listID,INET6_ATON(:ip))"); + $query6->execute([':listID' => '-'.$str, ':ip' => $ip]); + } +} +$lvlstring = substr($lvlstring, 0, -1); +$userstring = substr($userstring, 0, -1); +echo $lvlstring."#".$userstring; +echo "#".$totallvlcount.":".$offset.":10"; +echo "#"; +echo "Sa1ntSosetHuiHelloFromGreenCatsServerLOL"; +//echo GenerateHash::genMulti($lvlsmultistring); +?> diff --git a/incl/levelpacks/getGJMapPacks.php b/incl/levelpacks/getGJMapPacks.php index 6d3dd7b94..ef7c7a94f 100644 --- a/incl/levelpacks/getGJMapPacks.php +++ b/incl/levelpacks/getGJMapPacks.php @@ -1,30 +1,29 @@ prepare("SELECT colors2,rgbcolors,ID,name,levels,stars,coins,difficulty FROM `mappacks` ORDER BY `ID` ASC LIMIT 10 OFFSET $packpage"); +$lvlsmultistring = []; +if($orderMapPacksByStars) $query = $db->prepare("SELECT * FROM `mappacks` ORDER BY `stars` ASC LIMIT 10 OFFSET $packpage"); +else $query = $db->prepare("SELECT * FROM `mappacks` ORDER BY `ID` ASC LIMIT 10 OFFSET $packpage"); $query->execute(); $result = $query->fetchAll(); $packcount = $query->rowCount(); foreach($result as &$mappack) { - $lvlsmultistring .= $mappack["ID"] . ","; + $lvlsmultistring[] = ['ID' => $mappack["ID"], 'stars' => $mappack["stars"], 'coins' => $mappack["coins"]]; $colors2 = $mappack["colors2"]; - if($colors2 == "none" OR $colors2 == ""){ - $colors2 = $mappack["rgbcolors"]; - } - $mappackstring .= "1:".$mappack["ID"].":2:".$mappack["name"].":3:".$mappack["levels"].":4:".$mappack["stars"].":5:".$mappack["coins"].":6:".$mappack["difficulty"].":7:".$mappack["rgbcolors"].":8:".$colors2."|"; + if($colors2 == "none" OR $colors2 == "") $colors2 = $mappack["rgbcolors"]; + $mappackstring .= "1:".$mappack["ID"].":2:".ExploitPatch::translit($mappack["name"]).":3:".$mappack["levels"].":4:".$mappack["stars"].":5:".$mappack["coins"].":6:".$mappack["difficulty"].":7:".$mappack["rgbcolors"].":8:".$colors2."|"; } $query = $db->prepare("SELECT count(*) FROM mappacks"); $query->execute(); $totalpackcount = $query->fetchColumn(); $mappackstring = substr($mappackstring, 0, -1); -$lvlsmultistring = substr($lvlsmultistring, 0, -1); echo $mappackstring; echo "#".$totalpackcount.":".$packpage.":10"; echo "#"; diff --git a/incl/levelpacks/uploadGJLevelList.php b/incl/levelpacks/uploadGJLevelList.php index 7c26dd586..dcd45a2c9 100644 --- a/incl/levelpacks/uploadGJLevelList.php +++ b/incl/levelpacks/uploadGJLevelList.php @@ -1,36 +1,41 @@ -prepare('SELECT * FROM lists WHERE listID = :listID AND accountID = :accountID'); - $list->execute([':listID' => $listID, ':accountID' => $accountID]); - $list = $list->fetch(); - if(!empty($list)) { - $list = $db->prepare('UPDATE lists SET listDesc = :listDesc, listVersion = :listVersion, listlevels = :listlevels, starDifficulty = :difficulty, original = :original, unlisted = :unlisted, updateDate = :timestamp WHERE listID = :listID'); - $list->execute([':listID' => $listID, ':listDesc' => $listDesc, ':listVersion' => $listVersion, ':listlevels' => $listLevels, ':difficulty' => $difficulty, ':original' => $original, ':unlisted' => $unlisted, ':timestamp' => time()]); - exit($listID); - } -} -$list = $db->prepare('INSERT INTO lists (listName, listDesc, listVersion, accountID, listlevels, starDifficulty, original, unlisted, uploadDate) VALUES (:listName, :listDesc, :listVersion, :accountID, :listlevels, :difficulty, :original, :unlisted, :timestamp)'); -$list->execute([':listName' => $listName, ':listDesc' => $listDesc, ':listVersion' => $listVersion, ':accountID' => $accountID, ':listlevels' => $listLevels, ':difficulty' => $difficulty, ':original' => $original, ':unlisted' => $unlisted, ':timestamp' => time()]); -echo $db->lastInsertId(); +prepare('SELECT * FROM lists WHERE listID = :listID AND accountID = :accountID'); + $list->execute([':listID' => $listID, ':accountID' => $accountID]); + $list = $list->fetch(); + if(!empty($list)) { + $updateList = $db->prepare('UPDATE lists SET listDesc = :listDesc, listVersion = :listVersion, listlevels = :listlevels, starDifficulty = :difficulty, original = :original, unlisted = :unlisted, updateDate = :timestamp WHERE listID = :listID'); + $updateList->execute([':listID' => $listID, ':listDesc' => $listDesc, ':listVersion' => $listVersion, ':listlevels' => $listLevels, ':difficulty' => $difficulty, ':original' => $original, ':unlisted' => $unlisted, ':timestamp' => time()]); + $gs->logAction($accountID, 18, $listName, $listLevels, $listID, $difficulty, $unlisted); + $gs->sendLogsListChangeWebhook($listID, $accountID, $list); + exit($listID); + } +} +$list = $db->prepare('INSERT INTO lists (listName, listDesc, listVersion, accountID, listlevels, starDifficulty, original, unlisted, uploadDate) VALUES (:listName, :listDesc, :listVersion, :accountID, :listlevels, :difficulty, :original, :unlisted, :timestamp)'); +$list->execute([':listName' => $listName, ':listDesc' => $listDesc, ':listVersion' => $listVersion, ':accountID' => $accountID, ':listlevels' => $listLevels, ':difficulty' => $difficulty, ':original' => $original, ':unlisted' => $unlisted, ':timestamp' => time()]); +$listID = $db->lastInsertId(); +$gs->logAction($accountID, 17, $listName, $listLevels, $listID, $difficulty, $unlisted); +$gs->sendLogsListChangeWebhook($listID, $accountID); +echo $listID; ?> \ No newline at end of file diff --git a/incl/levels/deleteGJLevelUser.php b/incl/levels/deleteGJLevelUser.php index 5cb66409f..f225eac92 100644 --- a/incl/levels/deleteGJLevelUser.php +++ b/incl/levels/deleteGJLevelUser.php @@ -1,26 +1,36 @@ getUserID($accountID); -$query = $db->prepare("DELETE from levels WHERE levelID=:levelID AND userID=:userID AND starStars = 0 LIMIT 1"); +$userID = $gs->getUserID($accountID); +$query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID AND userID = :userID AND starStars = 0"); $query->execute([':levelID' => $levelID, ':userID' => $userID]); -$query6 = $db->prepare("INSERT INTO actions (type, value, timestamp, value2) VALUES - (:type,:itemID, :time, :ip)"); -$query6->execute([':type' => 8, ':itemID' => $levelID, ':time' => time(), ':ip' => $userID]); -if(file_exists("../../data/levels/$levelID") AND $query->rowCount() != 0){ - rename("../../data/levels/$levelID","../../data/levels/deleted/$levelID"); -} +$getLevelData = $query->fetch(); + +if(!$getLevelData) exit("-1"); + +$query = $db->prepare("DELETE FROM comments WHERE levelID = :levelID"); +$query->execute([':levelID' => $levelID]); +$query = $db->prepare("DELETE FROM levels WHERE levelID = :levelID AND userID = :userID LIMIT 1"); +$query->execute([':levelID' => $levelID, ':userID' => $userID]); +if(file_exists("../../data/levels/$levelID")) rename("../../data/levels/$levelID","../../data/levels/deleted/$levelID"); echo "1"; +$gs->logAction($accountID, 8, $getLevelData['levelName'], $getLevelData['levelDesc'], $getLevelData['extID'], $levelID, $getLevelData['starStars'], $getLevelData['starDifficulty']); +$gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); +if($automaticCron) { + Cron::autoban($accountID, false); + Cron::updateCreatorPoints($accountID, false); + Cron::updateSongsUsage($accountID, false); +} ?> \ No newline at end of file diff --git a/incl/levels/downloadGJLevel.php b/incl/levels/downloadGJLevel.php index 4a4ec0c62..e93c05c7f 100644 --- a/incl/levels/downloadGJLevel.php +++ b/incl/levels/downloadGJLevel.php @@ -1,136 +1,107 @@ getIP(); -$levelID = ExploitPatch::remove($_POST["levelID"]); -$binaryVersion = !empty($_POST["binaryVersion"]) ? ExploitPatch::remove($_POST["levelID"]) : 0; +$binaryVersion = !empty($_POST["binaryVersion"]) ? ExploitPatch::number($_POST["binaryVersion"]) : 0; $feaID = 0; -if(!is_numeric($levelID)){ - echo -1; -}else{ - switch($levelID){ - case -1: //Daily level - $query = $db->prepare("SELECT feaID, levelID FROM dailyfeatures WHERE timestamp < :time AND type = 0 ORDER BY timestamp DESC LIMIT 1"); - $query->execute([':time' => time()]); - $result = $query->fetch(); - $levelID = $result["levelID"]; - $feaID = $result["feaID"]; - $daily = 1; - break; - case -2: //Weekly level - $query = $db->prepare("SELECT feaID, levelID FROM dailyfeatures WHERE timestamp < :time AND type = 1 ORDER BY timestamp DESC LIMIT 1"); - $query->execute([':time' => time()]); - $result = $query->fetch(); - $levelID = $result["levelID"]; - $feaID = $result["feaID"]; - $feaID = $feaID + 100001; - $daily = 1; - break; - case -3: //Event level - $query = $db->prepare("SELECT feaID, levelID FROM dailyfeatures WHERE timestamp < :time AND type = 2 ORDER BY timestamp DESC LIMIT 1"); - $query->execute([':time' => time()]); - $result = $query->fetch(); - $levelID = $result["levelID"]; - $feaID = $result["feaID"]; - //The feaID range for event levels on real GD is currently unknown, as there haven't been any yet. As such, offsetting this is to be implemented in the future - //$feaID = $feaID + 100001; - $daily = 1; - break; - default: - $daily = 0; - } - //downloading the level - if($daily == 1) - $query=$db->prepare("SELECT levels.*, users.userName, users.extID FROM levels LEFT JOIN users ON levels.userID = users.userID WHERE levelID = :levelID"); - else - $query=$db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); - - $query->execute([':levelID' => $levelID]); - $lvls = $query->rowCount(); - if($lvls!=0){ +switch($levelID) { + case -1: // Daily level + $query = $db->prepare("SELECT feaID, levelID FROM dailyfeatures WHERE timestamp < :time AND type = 0 ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':time' => time()]); + $result = $query->fetch(); + $levelID = $result["levelID"]; + $feaID = $result["feaID"]; + $daily = 1; + break; + case -2: // Weekly level + $query = $db->prepare("SELECT feaID, levelID FROM dailyfeatures WHERE timestamp < :time AND type = 1 ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':time' => time()]); $result = $query->fetch(); - - //Verifying friends only unlisted - if($result["unlisted2"] != 0){ - $accountID = GJPCheck::getAccountIDOrDie(); - if(! ($result["extID"] == $accountID || $gs->isFriends($accountID, $result["extID"])) ) exit("-1"); + $levelID = $result["levelID"]; + $feaID = $result["feaID"] + 100000; + $daily = 1; + break; + case -3: // Event level + $query = $db->prepare("SELECT feaID, levelID FROM events WHERE timestamp < :time AND duration >= :time ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':time' => time()]); + $result = $query->fetch(); + $levelID = $result["levelID"]; + $feaID = $result["feaID"] + 200000; + $daily = 1; + break; + default: + $daily = 0; + break; +} +if($daily == 1) $query = $db->prepare("SELECT levels.*, users.userName, users.extID FROM levels LEFT JOIN users ON levels.userID = users.userID WHERE levelID = :levelID"); +else $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); +$query->execute([':levelID' => $levelID]); +$result = $query->fetch(); +if($result) { + $isPlayerAnAdmin = false; + if(!empty($_POST['accountID'])) { + $accountID = GJPCheck::getAccountIDOrDie(); + if($unlistedLevelsForAdmins) { + $checkAdmin = $db->prepare('SELECT isAdmin FROM accounts WHERE accountID = :accountID'); + $checkAdmin->execute([':accountID' => $accountID]); + $checkAdmin = $checkAdmin->fetchColumn(); + if($checkAdmin) $isPlayerAnAdmin = true; } - - //adding the download - $query6 = $db->prepare("SELECT count(*) FROM actions_downloads WHERE levelID=:levelID AND ip=INET6_ATON(:ip)"); + } + if($result["unlisted2"] == 1) if(!($result["extID"] == $accountID || $gs->isFriends($accountID, $result["extID"])) && !$isPlayerAnAdmin) exit("-1"); // Verifying friends only unlisted + // Adding the download + $query6 = $db->prepare("SELECT count(*) FROM actions_downloads WHERE levelID=:levelID AND ip=INET6_ATON(:ip)"); + $query6->execute([':levelID' => $levelID, ':ip' => $ip]); + if($inc && $query6->fetchColumn() < 2) { + $query2=$db->prepare("UPDATE levels SET downloads = downloads + 1 WHERE levelID = :levelID"); + $query2->execute([':levelID' => $levelID]); + $query6 = $db->prepare("INSERT INTO actions_downloads (levelID, ip) VALUES (:levelID,INET6_ATON(:ip))"); $query6->execute([':levelID' => $levelID, ':ip' => $ip]); - if($inc && $query6->fetchColumn() < 2){ - $query2=$db->prepare("UPDATE levels SET downloads = downloads + 1 WHERE levelID = :levelID"); - $query2->execute([':levelID' => $levelID]); - $query6 = $db->prepare("INSERT INTO actions_downloads (levelID, ip) VALUES - (:levelID,INET6_ATON(:ip))"); - $query6->execute([':levelID' => $levelID, ':ip' => $ip]); - } - //getting the days since uploaded... or outputting the date in Y-M-D format at least for now... - $uploadDate = date("d-m-Y G-i", $result["uploadDate"]); - $updateDate = date("d-m-Y G-i", $result["updateDate"]); - //password xor - $pass = $result["password"]; - $desc = $result["levelDesc"]; - if($gs->checkModIPPermission("actionFreeCopy") == 1){ - $pass = "1"; - } - $xorPass = $pass; - if($gameVersion > 19){ - if($pass != 0) $xorPass = base64_encode(XORCipher::cipher($pass,26364)); - }else{ - $desc = ExploitPatch::remove(base64_decode($desc)); - } - //submitting data - if(file_exists("../../data/levels/$levelID")){ - $levelstring = file_get_contents("../../data/levels/$levelID"); - }else{ - $levelstring = $result["levelString"]; - } - if($gameVersion > 18){ - if(substr($levelstring,0,3) == 'kS1'){ - $levelstring = base64_encode(gzcompress($levelstring)); - $levelstring = str_replace("/","_",$levelstring); - $levelstring = str_replace("+","-",$levelstring); - } - } - $response = "1:".$result["levelID"].":2:".$result["levelName"].":3:".$desc.":4:".$levelstring.":5:".$result["levelVersion"].":6:".$result["userID"].":8:10:9:".$result["starDifficulty"].":10:".$result["downloads"].":11:1:12:".$result["audioTrack"].":13:".$result["gameVersion"].":14:".$result["likes"].":17:".$result["starDemon"].":43:".$result["starDemonDiff"].":25:".$result["starAuto"].":18:".$result["starStars"].":19:".$result["starFeatured"].":42:".$result["starEpic"].":45:".$result["objects"].":15:".$result["levelLength"].":30:".$result["original"].":31:".$result['twoPlayer'].":28:".$uploadDate. ":29:".$updateDate. ":35:".$result["songID"].":36:".$result["extraString"].":37:".$result["coins"].":38:".$result["starCoins"].":39:".$result["requestedStars"].":46:".$result["wt"].":47:".$result["wt2"].":48:".$result["settingsString"].":40:".$result["isLDM"].":27:$xorPass:52:".$result["songIDs"].":53:".$result["sfxIDs"].":57:".$result['ts']; - if($daily == 1) $response .= ":41:".$feaID; - if($extras) $response .= ":26:" . $result["levelInfo"]; - //2.02 stuff - $response .= "#" . GenerateHash::genSolo($levelstring) . "#"; - //2.1 stuff - $somestring = $result["userID"].",".$result["starStars"].",".$result["starDemon"].",".$result["levelID"].",".$result["starCoins"].",".$result["starFeatured"].",".$pass.",".$feaID; - $response .= GenerateHash::genSolo2($somestring); - if($daily == 1){ - $response .= "#" . $gs->getUserString($result); - }elseif($binaryVersion == 30){ - /* - This was only part of the response for a brief time prior to GD 2.1's relase. - This binary version corresponds to the original release of Geometry Dash World. - It is currently unknown if it's required, so it is left in for now. - */ - $response .= "#" . $somestring; + } + $uploadDate = $gs->makeTime($result["uploadDate"]); + $updateDate = $gs->makeTime($result["updateDate"]); + $pass = $result["password"]; + $desc = ExploitPatch::translit(ExploitPatch::rucharclean(ExploitPatch::url_base64_decode($result["levelDesc"]))); + if($gs->checkModIPPermission("actionFreeCopy") == 1) $pass = "1"; + $xorPass = $pass; + $levelstring = file_exists("../../data/levels/$levelID") ? file_get_contents("../../data/levels/$levelID") : $result["levelString"]; + if($gameVersion > 18) { + if(substr($levelstring, 0, 3) == 'kS1') $levelstring = ExploitPatch::url_base64_encode(gzcompress($levelstring)); + if($gameVersion > 19) { + if($pass != 0) $xorPass = ExploitPatch::url_base64_encode(XORCipher::cipher($pass, 26364)); + $desc = ExploitPatch::url_base64_encode($desc); } - echo $response; - }else{ - echo -1; } -} + $response = "1:".$result["levelID"].":2:".ExploitPatch::translit($result["levelName"]).":3:".$desc.":4:".$levelstring.":5:".$result["levelVersion"].":6:".$result["userID"].":8:10:9:".$result["starDifficulty"].":10:".$result["downloads"].":11:1:12:".$result["audioTrack"].":13:".$result["gameVersion"].":14:".$result["likes"].":17:".$result["starDemon"].":43:".$result["starDemonDiff"].":25:".$result["starAuto"].":18:".$result["starStars"].":19:".$result["starFeatured"].":42:".$result["starEpic"].":45:".$result["objects"].":15:".$result["levelLength"].":30:".$result["original"].":31:".$result['twoPlayer'].":28:".$uploadDate. ":29:".$updateDate. ":35:".$result["songID"].":36:".$result["extraString"].":37:".$result["coins"].":38:".$result["starCoins"].":39:".$result["requestedStars"].":46:".$result["wt"].":47:".$result["wt2"].":48:".$result["settingsString"].":40:".$result["isLDM"].":27:$xorPass:52:".$result["songIDs"].":53:".$result["sfxIDs"].":57:".$result['ts']; + if($daily == 1) $response .= ":41:".$feaID; + if($extras) $response .= ":26:" . $result["levelInfo"]; + // 2.02 stuff + $response .= "#" . GenerateHash::genSolo($levelstring) . "#"; + // 2.1 stuff + $somestring = $result["userID"].",".$result["starStars"].",".$result["starDemon"].",".$result["levelID"].",".$result["starCoins"].",".$result["starFeatured"].",".$pass.",".$feaID; + $response .= GenerateHash::genSolo2($somestring); + if($daily == 1) { + $response .= "#" . $gs->getUserString($result); + } elseif($binaryVersion == 30) { + /* + This was only part of the response for a brief time prior to GD 2.1's relase. + This binary version corresponds to the original release of Geometry Dash World. + It is currently unknown if it's required, so it is left in for now. + */ + $response .= "#" . $somestring; + } + echo $response; +} else exit('-1'); ?> diff --git a/incl/levels/getGJDailyLevel.php b/incl/levels/getGJDailyLevel.php index 277649af7..a3689e7ee 100644 --- a/incl/levels/getGJDailyLevel.php +++ b/incl/levels/getGJDailyLevel.php @@ -1,19 +1,51 @@ prepare("SELECT feaID FROM dailyfeatures WHERE timestamp < :current AND type = :type ORDER BY timestamp DESC LIMIT 1"); -$query->execute([':current' => $current, ':type' => $type]); +switch($type) { + case 0: + case 1: + $dailyTable = 'dailyfeatures'; + $dailyTime = 'timestamp'; + $isEvent = false; + $query = $db->prepare("SELECT * FROM dailyfeatures WHERE timestamp < :current AND type = :type ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':current' => $current, ':type' => $type]); + break; + case 2: + $dailyTable = 'events'; + $dailyTime = 'duration'; + $isEvent = true; + $query = $db->prepare("SELECT * FROM events WHERE timestamp < :current AND duration >= :current ORDER BY duration ASC LIMIT 1"); + $query->execute([':current' => $current]); + break; +} + +$daily = $query->fetch(); if($query->rowCount() == 0) exit("-1"); -$dailyID = $query->fetchColumn(); -if($type == 1) $dailyID += 100001; -//Time left -$timeleft = $midnight - $current; -//output -echo $dailyID ."|". $timeleft; -?> +$dailyID = $daily['feaID'] + ($type * 100000); +$timeleft = $daily[$dailyTime] - $current; +if(!$daily['webhookSent']) { + $gs->sendDailyWebhook($daily['levelID'], $type); + $sent = $db->prepare('UPDATE '.$dailyTable.' SET webhookSent = 1 WHERE feaID = :feaID'); + $sent->execute([':feaID' => $daily['feaID']]); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); +} +$stringToAdd = ''; +if($isEvent) { + $chk = XORCipher::cipher(ExploitPatch::url_base64_decode(substr(ExploitPatch::charclean($_POST["chk"]), 5)), 59182); + $string = ExploitPatch::url_base64_encode(XORCipher::cipher('Sa1nt:'.$chk.':'.($daily['feaID'] + 19).':3:'.$daily['rewards'], 59182)); + $timeleft = 10; + $hash = $gh->genSolo4($string); + $stringToAdd = '|Sa1nt'.$string.'|'.$hash; +} +echo $dailyID ."|". $timeleft.$stringToAdd; +?> \ No newline at end of file diff --git a/incl/levels/getGJLevels.php b/incl/levels/getGJLevels.php index a48c36dbd..2591461eb 100644 --- a/incl/levels/getGJLevels.php +++ b/incl/levels/getGJLevels.php @@ -1,112 +1,77 @@ 27){ - $gameVersion++; +// Initializing variables +$lvlstring = $userstring = $songsstring = $suggestColumn = $suggestJoin = $str = $morejoins = ""; +$lvlsmultistring = $epicParams = []; +$order = "uploadDate"; +$orderenabled = $ordergauntlet = $isIDSearch = false; +$params = ["unlisted = 0"]; +if(!empty($_POST['accountID'])) { + $accountID = GJPCheck::getAccountIDOrDie(); + if($unlistedLevelsForAdmins) { + $checkAdmin = $db->prepare('SELECT isAdmin FROM accounts WHERE accountID = :accountID'); + $checkAdmin->execute([':accountID' => $accountID]); + $checkAdmin = $checkAdmin->fetchColumn(); + if($checkAdmin) $params = []; } } -if(!empty($_POST["type"])){ - $type = ExploitPatch::number($_POST["type"]); -}else{ - $type = 0; -} -if(!empty($_POST["diff"])){ - $diff = ExploitPatch::numbercolon($_POST["diff"]); -}else{ - $diff = "-"; -} +$gameVersion = ExploitPatch::number($_POST["gameVersion"]) ?: 0; +$binaryVersion = ExploitPatch::number($_POST["binaryVersion"]) ?: 0; +$type = ExploitPatch::number($_POST["type"]) ?: 0; +$diff = ExploitPatch::numbercolon($_POST["diff"]) ?: '-'; -//ADDITIONAL PARAMETERS -if($gameVersion==0){ - $params[] = "levels.gameVersion <= 18"; -}else{ - $params[] = "levels.gameVersion <= '$gameVersion'"; -} -if(!empty($_POST["original"]) AND $_POST["original"]==1){ - $params[] = "original = 0"; -} -if(!empty($_POST["coins"]) AND $_POST["coins"]==1){ - $params[] = "starCoins = 1 AND NOT levels.coins = 0"; -} -if(!empty($_POST["uncompleted"]) AND $_POST["uncompleted"]==1){ - $completedLevels = ExploitPatch::numbercolon($_POST["completedLevels"]); - $params[] = "NOT levelID IN ($completedLevels)"; +// Additional search parameters + +if(!$showAllLevels) { + if($gameVersion == 0) $params[] = "levels.gameVersion <= 18"; + else $params[] = "levels.gameVersion <= '$gameVersion'"; } -if(!empty($_POST["onlyCompleted"]) AND $_POST["onlyCompleted"]==1){ +if(isset($_POST["original"]) && $_POST["original"] == 1) $params[] = "original = 0"; +if(isset($_POST["coins"]) && $_POST["coins"] == 1) $params[] = "starCoins = 1 AND NOT levels.coins = 0"; +if((isset($_POST["uncompleted"]) || isset($_POST["onlyCompleted"])) && ($_POST["uncompleted"] == 1 || $_POST["onlyCompleted"] == 1)) { $completedLevels = ExploitPatch::numbercolon($_POST["completedLevels"]); - $params[] = "levelID IN ($completedLevels)"; + $params[] = ($_POST['uncompleted'] == 1 ? 'NOT ' : '')."levelID IN ($completedLevels)"; } -if(!empty($_POST["song"])){ - if(empty($_POST["customSong"])){ - $song = ExploitPatch::number($_POST["song"]); - $song = $song -1; +if(isset($_POST["song"]) && $_POST["song"] > 0) { + $song = ExploitPatch::number($_POST["song"]); + if(!isset($_POST["customSong"])) { + $song = $song - 1; $params[] = "audioTrack = '$song' AND songID = 0"; - }else{ - $song = ExploitPatch::number($_POST["song"]); - $params[] = "songID = '$song'"; - } -} -if(!empty($_POST["twoPlayer"]) AND $_POST["twoPlayer"]==1){ - $params[] = "twoPlayer = 1"; + } else $params[] = "songID = '$song'"; } -if(!empty($_POST["star"])){ - $params[] = "NOT starStars = 0"; -} -if(!empty($_POST["noStar"])){ - $params[] = "starStars = 0"; -} -if(!empty($_POST["gauntlet"])){ +if(isset($_POST["twoPlayer"]) && $_POST["twoPlayer"] == 1) $params[] = "twoPlayer = 1"; +if(isset($_POST["star"]) && $_POST["star"] == 1) $params[] = "NOT starStars = 0"; +if(isset($_POST["noStar"]) && $_POST["noStar"] == 1) $params[] = "starStars = 0"; +if(isset($_POST["gauntlet"]) && $_POST["gauntlet"] != 0) { $ordergauntlet = true; $order = "starStars"; - $gauntlet = ExploitPatch::remove($_POST["gauntlet"]); - $query=$db->prepare("SELECT * FROM gauntlets WHERE ID = :gauntlet"); + $gauntlet = ExploitPatch::number($_POST["gauntlet"]); + $query = $db->prepare("SELECT * FROM gauntlets WHERE ID = :gauntlet"); $query->execute([':gauntlet' => $gauntlet]); $actualgauntlet = $query->fetch(); $str = $actualgauntlet["level1"].",".$actualgauntlet["level2"].",".$actualgauntlet["level3"].",".$actualgauntlet["level4"].",".$actualgauntlet["level5"]; $params[] = "levelID IN ($str)"; $type = -1; } -if(!empty($_POST["len"])){ - $len = ExploitPatch::numbercolon($_POST["len"]); -}else{ - $len = "-"; -} -if($len != "-" AND !empty($len)){ - $params[] = "levelLength IN ($len)"; -} -if(!empty($_POST["featured"])) $epicParams[] = "starFeatured = 1"; -if(!empty($_POST["epic"])) $epicParams[] = "starEpic = 1"; -if(!empty($_POST["mythic"])) $epicParams[] = "starEpic = 2"; -if(!empty($_POST["legendary"])) $epicParams[] = "starEpic = 3"; +$len = ExploitPatch::numbercolon($_POST["len"]) ?: '-'; +if($len != "-" AND !empty($len)) $params[] = "levelLength IN ($len)"; +if(isset($_POST["featured"]) && $_POST["featured"] == 1) $epicParams[] = "starFeatured > 0"; +if(isset($_POST["epic"]) && $_POST["epic"] == 1) $epicParams[] = "starEpic = 1"; +if(isset($_POST["mythic"]) && $_POST["mythic"] == 1) $epicParams[] = "starEpic = 2"; // The reason why Mythic and Legendary ratings are swapped because RobTop accidentally swapped them in-game +if(isset($_POST["legendary"]) && $_POST["legendary"] == 1) $epicParams[] = "starEpic = 3"; $epicFilter = implode(" OR ", $epicParams); if(!empty($epicFilter)) $params[] = $epicFilter; -//DIFFICULTY FILTERS -switch($diff){ +// Difficulty filters +switch($diff) { case -1: $params[] = "starDifficulty = '0'"; break; @@ -114,13 +79,9 @@ $params[] = "starAuto = '1'"; break; case -2: - if(!empty($_POST["demonFilter"])){ - $demonFilter = ExploitPatch::number($_POST["demonFilter"]); - }else{ - $demonFilter = 0; - } + $demonFilter = ExploitPatch::number($_POST["demonFilter"]) ?: 0; $params[] = "starDemon = 1"; - switch($demonFilter){ + switch($demonFilter) { case 1: $params[] = "starDemonDiff = '3'"; break; @@ -136,167 +97,143 @@ case 5: $params[] = "starDemonDiff = '6'"; break; - default: - break; } break; case "-"; break; default: - if($diff){ + if($diff) { $diff = str_replace(",", "0,", $diff) . "0"; $params[] = "starDifficulty IN ($diff) AND starAuto = '0' AND starDemon = '0'"; } break; } -//TYPE DETECTION -//TODO: the 2 non-friend types that send GJP in 2.11 -if(!empty($_POST["str"])){ - $str = ExploitPatch::remove($_POST["str"]); -} -if(isset($_POST["page"]) AND is_numeric($_POST["page"])){ - $offset = ExploitPatch::number($_POST["page"]) . "0"; -}else{ - $offset = 0; -} +// Type detection +// TODO: the 2 non-friend types that send GJP in 2.11 +if(isset($_POST["str"])) $str = ExploitPatch::rucharclean($_POST["str"]) ?: ''; +$offset = is_numeric($_POST["page"]) ? ExploitPatch::number($_POST["page"]) . "0" : 0; switch($type){ - case 0: - case 15: //most liked, changed to 15 in GDW for whatever reason + case 0: // Search + case 15: // Most liked, changed to 15 in GDW for whatever reason $order = "likes"; - if(!empty($str)){ - if(is_numeric($str)){ + if(!empty($str)) { + if(is_numeric($str)) { $params = array("levelID = '$str'"); $isIDSearch = true; - }else{ - $params[] = "levelName LIKE '%$str%'"; - } + } else $params[] = "levelName LIKE '%$str%'"; } break; - case 1: + case 1: // Most downloaded $order = "downloads"; break; - case 2: + case 2: // Most liked $order = "likes"; break; - case 3: //TRENDING + case 3: // Trending $uploadDate = time() - (7 * 24 * 60 * 60); $params[] = "uploadDate > $uploadDate "; $order = "likes"; break; - case 5: + case 5: // Levels per user + if($accountID && $gs->getUserID($accountID, $gs->getAccountName($accountID)) == $str) $params = []; $params[] = "levels.userID = '$str'"; break; - case 6: //featured - case 17: //featured GDW //TODO: make this list of daily levels + case 6: // Featured + case 17: // Featured in GDW if($gameVersion > 21) $params[] = "NOT starFeatured = 0 OR NOT starEpic = 0"; else $params[] = "NOT starFeatured = 0"; - $order = "rateDate DESC,uploadDate"; + $order = "starFeatured DESC, rateDate DESC, uploadDate"; break; - case 16: //HALL OF FAME + case 16: // Hall of Fame $params[] = "NOT starEpic = 0"; - $order = "rateDate DESC,uploadDate"; + $order = "starFeatured DESC, rateDate DESC, uploadDate"; break; - case 7: //MAGIC - $params[] = "objects > 9999"; // L + case 7: // Magic + $params[] = "objects > 9999"; // L break; - case 10: //MAP PACKS - case 19: //unknown but same as map packs (on real GD type 10 has star rated filter and 19 doesn't) + case 10: // Map Packs + case 19: // Unknown, but same as Map Packs (on real GD type 10 has star rated filter and 19 doesn't) $order = false; $params[] = "levelID IN ($str)"; break; - case 11: //AWARDED + case 11: // Awarded $params[] = "NOT starStars = 0"; $order = "rateDate DESC,uploadDate"; break; - case 12: //FOLLOWED + case 12: // Followed $followed = ExploitPatch::numbercolon($_POST["followed"]); $params[] = "users.extID IN ($followed)"; break; - case 13: //FRIENDS - $accountID = GJPCheck::getAccountIDOrDie(); + case 13: // Friends + if(!isset($accountID)) $accountID = GJPCheck::getAccountIDOrDie(); $peoplearray = $gs->getFriends($accountID); $whereor = implode(",", $peoplearray); $params[] = "users.extID IN ($whereor)"; break; - case 21: //DAILY SAFE + case 21: // Daily safe $morejoins = "INNER JOIN dailyfeatures ON levels.levelID = dailyfeatures.levelID"; - $params[] = "dailyfeatures.type = 0"; + $params[] = "dailyfeatures.type = 0 AND timestamp < ".time(); $order = "dailyfeatures.feaID"; break; - case 22: //WEEKLY SAFE + case 22: // Weekly safe $morejoins = "INNER JOIN dailyfeatures ON levels.levelID = dailyfeatures.levelID"; - $params[] = "dailyfeatures.type = 1"; + $params[] = "dailyfeatures.type = 1 AND timestamp < ".time(); $order = "dailyfeatures.feaID"; break; - case 23: //EVENT SAFE (assumption) - $morejoins = "INNER JOIN dailyfeatures ON levels.levelID = dailyfeatures.levelID"; - $params[] = "dailyfeatures.type = 2"; - $order = "dailyfeatures.feaID"; + case 23: // Event safe + $morejoins = "INNER JOIN events ON levels.levelID = events.levelID"; + $params[] = "timestamp < ".time(); + $order = "events.feaID"; break; - case 25: // LIST LEVELS + case 25: // List levels $listLevels = $gs->getListLevels($str); $params = array("levelID IN (".$listLevels.")"); break; - case 27: // SENT LEVELS - $sug = ", suggest.suggestLevelId, suggest.timestamp"; - $sugg = "LEFT JOIN suggest ON levels.levelID = suggest.suggestLevelId"; - $params[] = "suggestLevelId > 0"; - $order = 'suggest.timestamp'; + case 27: // Sent levels + $suggestColumn = ", s.max_timestamp"; + $suggestJoin = "LEFT JOIN (SELECT suggestLevelId, MAX(timestamp) as max_timestamp FROM suggest GROUP BY suggestLevelId) s ON levels.levelID = s.suggestLevelId"; + $params[] = "s.suggestLevelId > 0"; + if(!$ratedLevelsInSent) $params[] = "starStars = 0"; + $order = 's.max_timestamp'; break; } -//ACTUAL QUERY EXECUTION -$querybase = "FROM levels LEFT JOIN songs ON levels.songID = songs.ID LEFT JOIN users ON levels.userID = users.userID $sugg $morejoins"; -if(!empty($params)){ - $querybase .= " WHERE (" . implode(" ) AND ( ", $params) . ")"; -} -$query = "SELECT levels.*, songs.ID, songs.name, songs.authorID, songs.authorName, songs.size, songs.isDisabled, songs.download, users.userName, users.extID$sug $querybase"; -if($order){ - if($ordergauntlet){ - $query .= "ORDER BY $order ASC"; - }else{ - $query .= "ORDER BY $order DESC"; - } -} +// Actual query execution +$querybase = "FROM levels LEFT JOIN songs ON levels.songID = songs.ID LEFT JOIN users ON levels.userID = users.userID $suggestJoin $morejoins"; +if(!empty($params)) $querybase .= " WHERE (" . implode(" ) AND ( ", $params) . ")"; +$query = "SELECT levels.*, songs.ID, songs.name, songs.authorID, songs.authorName, songs.size, songs.isDisabled, songs.download, users.userName, users.extID, users.clan$suggestColumn $querybase"; +if($order) $query .= "ORDER BY $order ".($ordergauntlet ? 'ASC' : 'DESC'); $query .= " LIMIT 10 OFFSET $offset"; -//echo $query; $countquery = "SELECT count(*) $querybase"; -//echo $query; $query = $db->prepare($query); $query->execute(); -//echo $countquery; $countquery = $db->prepare($countquery); $countquery->execute(); $totallvlcount = $countquery->fetchColumn(); $result = $query->fetchAll(); $levelcount = $query->rowCount(); foreach($result as &$level1) { - if($level1["levelID"]!=""){ - if($isIDSearch AND $level1['unlisted'] > 1) { - if(!isset($accountID)) $accountID = GJPCheck::getAccountIDOrDie(); - if(!$gs->isFriends($accountID, $level1['extID']) && $accountID != $level1['extID']) break; - } - $lvlsmultistring[] = ["levelID" => $level1["levelID"], "stars" => $level1["starStars"], 'coins' => $level1["starCoins"]]; - if(!empty($gauntlet)){ - $lvlstring .= "44:$gauntlet:"; - } - $lvlstring .= "1:".$level1["levelID"].":2:".$level1["levelName"].":5:".$level1["levelVersion"].":6:".$level1["userID"].":8:10:9:".$level1["starDifficulty"].":10:".$level1["downloads"].":12:".$level1["audioTrack"].":13:".$level1["gameVersion"].":14:".$level1["likes"].":17:".$level1["starDemon"].":43:".$level1["starDemonDiff"].":25:".$level1["starAuto"].":18:".$level1["starStars"].":19:".$level1["starFeatured"].":42:".$level1["starEpic"].":45:".$level1["objects"].":3:".$level1["levelDesc"].":15:".$level1["levelLength"].":30:".$level1["original"].":31:".$level1['twoPlayer'].":37:".$level1["coins"].":38:".$level1["starCoins"].":39:".$level1["requestedStars"].":46:1:47:2:40:".$level1["isLDM"].":35:".$level1["songID"]."|"; - if($level1["songID"]!=0){ - $song = $gs->getSongString($level1); - if($song){ - $songsstring .= $song . "~:~"; - } - } - $userstring .= $gs->getUserString($level1)."|"; + if(empty($level1["levelID"])) continue; + if($isIDSearch && $level1['unlisted'] > 0) { + if(!isset($accountID)) $accountID = GJPCheck::getAccountIDOrDie(); + if($level1['unlisted'] == 1 && (!$gs->isFriends($accountID, $level1['extID']) && $accountID != $level1['extID'])) break; } + if($gameVersion < 20) $level1['levelDesc'] = ExploitPatch::gd_escape(ExploitPatch::url_base64_decode($level1['levelDesc'])); + $lvlsmultistring[] = ["levelID" => $level1["levelID"], "stars" => $level1["starStars"], 'coins' => $level1["starCoins"]]; + $likes = $level1["likes"]; // - $level1["dislikes"]; // Yeah, my GDPS has dislikes separated + if(isset($gauntlet)) $lvlstring .= "44:$gauntlet:"; + $lvlstring .= "1:".$level1["levelID"].":2:".ExploitPatch::translit($level1["levelName"]).":5:".$level1["levelVersion"].":6:".$level1["userID"].":8:10:9:".$level1["starDifficulty"].":10:".$level1["downloads"].":12:".$level1["audioTrack"].":13:".$level1["gameVersion"].":14:".$likes.":17:".$level1["starDemon"].":43:".$level1["starDemonDiff"].":25:".$level1["starAuto"].":18:".$level1["starStars"].":19:".$level1["starFeatured"].":42:".$level1["starEpic"].":45:".$level1["objects"].":3:".ExploitPatch::translit($level1["levelDesc"]).":15:".$level1["levelLength"].":30:".$level1["original"].":31:".$level1['twoPlayer'].":37:".$level1["coins"].":38:".$level1["starCoins"].":39:".$level1["requestedStars"].":46:1:47:2:40:".$level1["isLDM"].":35:".$level1["songID"]."|"; + if($level1["songID"] != 0) { + $song = $gs->getSongString($level1); + if($song) $songsstring .= $song . "~:~"; + } + $userstring .= $gs->getUserString($level1)."|"; } $lvlstring = substr($lvlstring, 0, -1); $userstring = substr($userstring, 0, -1); $songsstring = substr($songsstring, 0, -3); echo $lvlstring."#".$userstring; -if($gameVersion > 18){ - echo "#".$songsstring; -} +if($gameVersion > 18) echo "#".$songsstring; echo "#".$totallvlcount.":".$offset.":10"; echo "#"; echo GenerateHash::genMulti($lvlsmultistring); -?> +?> \ No newline at end of file diff --git a/incl/levels/rateGJDemon.php b/incl/levels/rateGJDemon.php index f03d144cf..0090e50b3 100644 --- a/incl/levels/rateGJDemon.php +++ b/incl/levels/rateGJDemon.php @@ -1,7 +1,7 @@ getIDFromPost(); +$permState = $gs->checkPermission($accountID, "actionRateStars"); +if($permState) { + $difficulty = $gs->getDiffFromRating($rating); + $gs->changeDifficulty($accountID, $levelID, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); +} +echo 1; \ No newline at end of file diff --git a/incl/levels/rateGJStars.php b/incl/levels/rateGJStars.php index 9d85d56e7..50f14b431 100644 --- a/incl/levels/rateGJStars.php +++ b/incl/levels/rateGJStars.php @@ -1,6 +1,6 @@ checkPermission($accountID, "actionRateStars"); -if($permState){ +if($permState) { $difficulty = $gs->getDiffFromStars($stars); - $gs->rateLevel($accountID, $levelID, 0, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); + $gs->changeDifficulty($accountID, $levelID, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); } echo 1; \ No newline at end of file diff --git a/incl/levels/reportGJLevel.php b/incl/levels/reportGJLevel.php index 407553dfe..664740487 100644 --- a/incl/levels/reportGJLevel.php +++ b/incl/levels/reportGJLevel.php @@ -1,7 +1,7 @@ getDiffFromStars($stars); - -if($gs->checkPermission($accountID, "actionRateStars")){ - $gs->rateLevel($accountID, $levelID, $stars, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); +if($gs->checkPermission($accountID, "actionRateStars")) { $gs->featureLevel($accountID, $levelID, $feature); $gs->verifyCoinsLevel($accountID, $levelID, 1); - echo 1; -}else if($gs->checkPermission($accountID, "actionSuggestRating")){ + $gs->rateLevel($accountID, $levelID, $stars, $difficulty["diff"], $difficulty["auto"], $difficulty["demon"]); + exit('1'); +} elseif($gs->checkPermission($accountID, "actionSuggestRating")) { $gs->suggestLevel($accountID, $levelID, $difficulty["diff"], $stars, $feature, $difficulty["auto"], $difficulty["demon"]); - echo 1; -}else{ - echo -2; -} -?> + exit('1'); +} else exit('-2'); +?> \ No newline at end of file diff --git a/incl/levels/updateGJDesc.php b/incl/levels/updateGJDesc.php index 119c3eb0a..fbec47dff 100644 --- a/incl/levels/updateGJDesc.php +++ b/incl/levels/updateGJDesc.php @@ -1,26 +1,21 @@ substr_count($rawDesc, '')) { @@ -28,10 +23,11 @@ for ($i = 0; $i < $tags; $i++) { $rawDesc .= ''; } - $levelDesc = str_replace('+', '-', base64_encode($rawDesc)); - $levelDesc = str_replace('/', '_', $levelDesc); + $levelDesc = ExploitPatch::url_base64_encode($rawDesc); } } -$query = $db->prepare("UPDATE levels SET levelDesc=:levelDesc WHERE levelID=:levelID AND extID=:extID"); +$query = $db->prepare("UPDATE levels SET levelDesc = :levelDesc WHERE levelID = :levelID AND extID = :extID"); $query->execute([':levelID' => $levelID, ':extID' => $id, ':levelDesc' => $levelDesc]); +$gs->logAction($id, 21, $levelID, $levelDesc); echo 1; +?> \ No newline at end of file diff --git a/incl/levels/uploadGJLevel.php b/incl/levels/uploadGJLevel.php index 3ce2e2a7b..f936575fc 100644 --- a/incl/levels/uploadGJLevel.php +++ b/incl/levels/uploadGJLevel.php @@ -1,42 +1,35 @@ substr_count($rawDesc, '')) { + if($tags > substr_count($rawDesc, '')) { $tags = $tags - substr_count($rawDesc, ''); - for ($i = 0; $i < $tags; $i++) { + for($i = 0; $i < $tags; $i++) { $rawDesc .= ''; } - $levelDesc = str_replace('+', '-', base64_encode($rawDesc)); - $levelDesc = str_replace('/', '_', $levelDesc); } } +$levelDesc = ExploitPatch::url_base64_encode(ExploitPatch::rucharclean($rawDesc)); $levelVersion = ExploitPatch::remove($_POST["levelVersion"]); $levelLength = ExploitPatch::remove($_POST["levelLength"]); $audioTrack = ExploitPatch::remove($_POST["audioTrack"]); @@ -68,44 +61,52 @@ $sfxIDs = !empty($_POST["sfxIDs"]) ? ExploitPatch::numbercolon($_POST["sfxIDs"]) : ''; $ts = !empty($_POST["ts"]) ? ExploitPatch::number($_POST["ts"]) : 0; -if(isset($_POST["password"])){ - $password = ExploitPatch::remove($_POST["password"]); -}else{ - $password = 1; - if($gameVersion > 17){ - $password = 0; - } -} +if(isset($_POST["password"])) $password = $_POST["password"] != 0 ? ExploitPatch::remove($_POST["password"]) : 1; +else $password = $gameVersion > 21 ? 1 : 0; $id = $gs->getIDFromPost(); $hostname = $gs->getIP(); -$userID = $mainLib->getUserID($id, $userName); +$userID = $gs->getUserID($id, $userName); +$checkBan = $gs->getPersonBan($id, $userID, 2); +if($checkBan) exit("-1"); $uploadDate = time(); $query = $db->prepare("SELECT count(*) FROM levels WHERE uploadDate > :time AND (userID = :userID OR hostname = :ip)"); -$query->execute([':time' => $uploadDate - 60, ':userID' => $userID, ':ip' => $hostname]); -if($query->fetchColumn() > 0){ - exit("-1"); -} +$query->execute([':time' => $uploadDate - 15, ':userID' => $userID, ':ip' => $hostname]); +if($query->fetchColumn() > 0) exit("-1"); $query = $db->prepare("INSERT INTO levels (levelName, gameVersion, binaryVersion, userName, levelDesc, levelVersion, levelLength, audioTrack, auto, password, original, twoPlayer, songID, objects, coins, requestedStars, extraString, levelString, levelInfo, secret, uploadDate, userID, extID, updateDate, unlisted, hostname, isLDM, wt, wt2, unlisted2, settingsString, songIDs, sfxIDs, ts) VALUES (:levelName, :gameVersion, :binaryVersion, :userName, :levelDesc, :levelVersion, :levelLength, :audioTrack, :auto, :password, :original, :twoPlayer, :songID, :objects, :coins, :requestedStars, :extraString, :levelString, :levelInfo, :secret, :uploadDate, :userID, :id, :uploadDate, :unlisted, :hostname, :ldm, :wt, :wt2, :unlisted2, :settingsString, :songIDs, :sfxIDs, :ts)"); - -if($levelString != "" AND $levelName != ""){ - $querye=$db->prepare("SELECT levelID FROM levels WHERE levelName = :levelName AND userID = :userID"); +if($levelString != "" AND $levelName != "") { + $querye=$db->prepare("SELECT levelID, updateLocked FROM levels WHERE levelName = :levelName AND userID = :userID"); $querye->execute([':levelName' => $levelName, ':userID' => $userID]); - $levelID = $querye->fetchColumn(); + $level = $querye->fetch(); + $levelID = $level['levelID']; + if($level['updateLocked']) exit("-1"); $lvls = $querye->rowCount(); - if($lvls==1){ + if($lvls == 1) { + $query = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); + $query->execute([":levelID"=> $levelID]); + $getLevelData = $query->fetch(); + $stars = $getLevelData['starStars']; + if(!$ratedLevelsUpdates && !in_array($levelID, $ratedLevelsUpdatesExceptions) && $stars > 0) exit("-1"); $query = $db->prepare("UPDATE levels SET levelName=:levelName, gameVersion=:gameVersion, binaryVersion=:binaryVersion, userName=:userName, levelDesc=:levelDesc, levelVersion=:levelVersion, levelLength=:levelLength, audioTrack=:audioTrack, auto=:auto, password=:password, original=:original, twoPlayer=:twoPlayer, songID=:songID, objects=:objects, coins=:coins, requestedStars=:requestedStars, extraString=:extraString, levelString=:levelString, levelInfo=:levelInfo, secret=:secret, updateDate=:uploadDate, unlisted=:unlisted, hostname=:hostname, isLDM=:ldm, wt=:wt, wt2=:wt2, unlisted2=:unlisted2, settingsString=:settingsString, songIDs=:songIDs, sfxIDs=:sfxIDs, ts=:ts WHERE levelName=:levelName AND extID=:id"); $query->execute([':levelName' => $levelName, ':gameVersion' => $gameVersion, ':binaryVersion' => $binaryVersion, ':userName' => $userName, ':levelDesc' => $levelDesc, ':levelVersion' => $levelVersion, ':levelLength' => $levelLength, ':audioTrack' => $audioTrack, ':auto' => $auto, ':password' => $password, ':original' => $original, ':twoPlayer' => $twoPlayer, ':songID' => $songID, ':objects' => $objects, ':coins' => $coins, ':requestedStars' => $requestedStars, ':extraString' => $extraString, ':levelString' => "", ':levelInfo' => $levelInfo, ':secret' => $secret, ':levelName' => $levelName, ':id' => $id, ':uploadDate' => $uploadDate, ':unlisted' => $unlisted, ':hostname' => $hostname, ':ldm' => $ldm, ':wt' => $wt, ':wt2' => $wt2, ':unlisted2' => $unlisted2, ':settingsString' => $settingsString, ':songIDs' => $songIDs, ':sfxIDs' => $sfxIDs, ':ts' => $ts]); - file_put_contents("../../data/levels/$levelID",$levelString); + file_put_contents("../../data/levels/$levelID", $levelString); echo $levelID; - }else{ + $gs->logAction($id, 23, $levelName, $levelDesc, $levelID); + $gs->sendLogsLevelChangeWebhook($levelID, $id, $getLevelData); + Automod::checkLevelsCount(); + if($automaticCron) Cron::updateSongsUsage($id, false); + } else { $query->execute([':levelName' => $levelName, ':gameVersion' => $gameVersion, ':binaryVersion' => $binaryVersion, ':userName' => $userName, ':levelDesc' => $levelDesc, ':levelVersion' => $levelVersion, ':levelLength' => $levelLength, ':audioTrack' => $audioTrack, ':auto' => $auto, ':password' => $password, ':original' => $original, ':twoPlayer' => $twoPlayer, ':songID' => $songID, ':objects' => $objects, ':coins' => $coins, ':requestedStars' => $requestedStars, ':extraString' => $extraString, ':levelString' => "", ':levelInfo' => $levelInfo, ':secret' => $secret, ':uploadDate' => $uploadDate, ':userID' => $userID, ':id' => $id, ':unlisted' => $unlisted, ':hostname' => $hostname, ':ldm' => $ldm, ':wt' => $wt, ':wt2' => $wt2, ':unlisted2' => $unlisted2, ':settingsString' => $settingsString, ':songIDs' => $songIDs, ':sfxIDs' => $sfxIDs, ':ts' => $ts]); $levelID = $db->lastInsertId(); - file_put_contents("../../data/levels/$levelID",$levelString); + file_put_contents("../../data/levels/$levelID", $levelString); echo $levelID; + $gs->logAction($id, 22, $levelName, $levelDesc, $levelID); + $gs->sendLogsLevelChangeWebhook($levelID, $id); + Automod::checkLevelsCount(); + if($automaticCron) Cron::updateSongsUsage($id, false); } -}else{ - echo -1; +} else { + exit('-1'); } ?> diff --git a/incl/lib/Captcha.php b/incl/lib/Captcha.php index 62c3560ca..f2abf1e67 100644 --- a/incl/lib/Captcha.php +++ b/incl/lib/Captcha.php @@ -1,26 +1,76 @@ "; - echo "
"; + public static function displayCaptcha($returnOrEcho = false) { + require __DIR__ . "/../../config/security.php"; + if ($enableCaptcha) { + if($returnOrEcho) { + switch($captchaType) { + case 1: + return " +
"; + break; + case 2: + return " +
"; + break; + case 3: + return " +
"; + break; + default: + return ''; + break; + } + } else { + switch($captchaType) { + case 1: + echo " +
"; + break; + case 2: + echo " +
"; + break; + case 3: + echo " +
"; + break; + default: + break; + } + } } } public static function validateCaptcha() { - include __DIR__ . "/../../config/security.php"; - if(!$enableCaptcha) - return true; - + require __DIR__ . "/../../config/security.php"; + if(!$enableCaptcha) return true; + switch($captchaType) { + case 1: + if(isset($_GET['h-captcha-response'])) $_POST['h-captcha-response'] = $_GET['h-captcha-response']; + $url = "https://hcaptcha.com/siteverify"; + $req = isset($_POST['h-captcha-response']) ? $_POST['h-captcha-response'] : null; + break; + case 2: + if(isset($_GET['g-recaptcha-response'])) $_POST['g-recaptcha-response'] = $_GET['g-recaptcha-response']; + $url = "https://www.google.com/recaptcha/api/siteverify"; + $req = isset($_POST['g-recaptcha-response']) ? $_POST['g-recaptcha-response'] : null; + break; + case 3: + if(isset($_GET['cf-turnstile-response'])) $_POST['cf-turnstile-response'] = $_GET['cf-turnstile-response']; + $url = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; + $req = isset($_POST['cf-turnstile-response']) ? $_POST['cf-turnstile-response'] : null; + break; + default: + return false; + break; + } $data = array( - 'secret' => $hCaptchaSecret, - 'response' => $_POST['h-captcha-response'] - ); + 'secret' => $CaptchaSecret, + 'response' => $req + ); $verify = curl_init(); - curl_setopt($verify, CURLOPT_URL, "https://hcaptcha.com/siteverify"); + curl_setopt($verify, CURLOPT_URL, $url); curl_setopt($verify, CURLOPT_POST, true); curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($verify, CURLOPT_RETURNTRANSFER, true); @@ -29,5 +79,4 @@ public static function validateCaptcha() { $responseData = json_decode($response); return $responseData->success; } - } \ No newline at end of file diff --git a/incl/lib/GJPCheck.php b/incl/lib/GJPCheck.php index 0e44e1e1c..eae87abe5 100644 --- a/incl/lib/GJPCheck.php +++ b/incl/lib/GJPCheck.php @@ -1,59 +1,61 @@ getIP(); - $query = $db->prepare("SELECT count(*) FROM actions WHERE type = 16 AND value = :accountID AND value2 = :ip AND timestamp > :timestamp"); - $query->execute([':accountID' => $accountID, ':ip' => $ip, ':timestamp' => time() - 3600]); - if($query->fetchColumn() > 0){ - return 1; - } - } - $gjpdecode = str_replace("_","/",$gjp); - $gjpdecode = str_replace("-","+",$gjpdecode); - $gjpdecode = base64_decode($gjpdecode); - $gjpdecode = XORCipher::cipher($gjpdecode,37526); + $gjpdecode = XORCipher::cipher(ExploitPatch::url_base64_decode($gjp), 37526); $validationResult = GeneratePass::isValid($accountID, $gjpdecode); - if($validationResult == 1 AND $sessionGrants){ - $ip = $ml->getIP(); - $query = $db->prepare("INSERT INTO actions (type, value, value2, timestamp) VALUES (16, :accountID, :ip, :timestamp)"); - $query->execute([':accountID' => $accountID, ':ip' => $ip, ':timestamp' => time()]); - } return $validationResult; } - public static function validateGJPOrDie($gjp, $accountID){ - if(self::check($gjp, $accountID) != 1) - exit("-1"); + public static function validateGJPOrDie($gjp, $accountID, $dontDie = false) { + if(self::check($gjp, $accountID) != 1) { + if($dontDie) return false; + else exit('-1'); + } } - public static function validateGJP2OrDie($gjp2, $accountID){ - if(GeneratePass::isGJP2Valid($accountID, $gjp2) != 1) - exit("-1"); + public static function validateGJP2OrDie($gjp2, $accountID,$dontDie = false) { + if(GeneratePass::isGJP2Valid($accountID, $gjp2) != 1) { + if($dontDie) return false; + else exit('-1'); + } } /** - * Gets accountID and from the POST parameters and validates if the provided GJP matches + * Gets accountID and from the POST parameters and validates if the provided GJP/token matches * * @return The account id */ - public static function getAccountIDOrDie(){ - require_once "../lib/exploitPatch.php"; + public static function getAccountIDOrDie($dontDie = false) { + require_once __DIR__."/exploitPatch.php"; - if(empty($_POST['accountID'])) exit("-1"); + if(empty($_POST['accountID']) && empty($_POST['auth'])) { + if($dontDie) return false; + else exit('-1'); + } $accountID = ExploitPatch::remove($_POST["accountID"]); - if(!empty($_POST['gjp'])) self::validateGJPOrDie($_POST['gjp'], $accountID); - elseif(!empty($_POST['gjp2'])) self::validateGJP2OrDie($_POST['gjp2'], $accountID); - else exit("-1"); + if(!empty($_POST['gjp'])) self::validateGJPOrDie($_POST['gjp'], $accountID, $dontDie); + elseif(!empty($_POST['gjp2'])) self::validateGJP2OrDie($_POST['gjp2'], $accountID, $dontDie); + elseif(!empty($_POST['auth'])) { + $tokenAuth = GeneratePass::isValidToken($_POST['auth']); + if(!is_array($tokenAuth)) { + if($dontDie) return false; + else exit('-1'); + } + $accountID = $tokenAuth['accountID']; + } + else { + if($dontDie) return false; + else exit('-1'); + } return $accountID; } diff --git a/incl/lib/automod.php b/incl/lib/automod.php new file mode 100644 index 000000000..77b0d10b7 --- /dev/null +++ b/incl/lib/automod.php @@ -0,0 +1,638 @@ + $levelsYesterdayModified) { + $isWarned = self::getLastAutomodAction(1, true); + if(!$isWarned) { + self::logAutomodActions(1, $levelsYesterday, $levelsToday, $levelsYesterdayModified); + $gs->sendLevelsWarningWebhook($levelsYesterday, $levelsToday); + } + return false; + } + return true; + } + /* + self::logAutomodActions($type, $value1, $value2, $value3, $value4, $value5, $value6) + This private function logs any automod actions + $type — type of action (Number) + $value1 — first value of action (Mixed) + $value2 — second value of action (Mixed) + $value3 — third value of action (Mixed) + $value4 — fourth value of action (Mixed) + $value5 — fifth value of action (Mixed) + $value6 — sixth value of action (Mixed) + Return value: + ID of logged action + */ + private static function logAutomodActions($type, $value1 = '', $value2 = '', $value3 = '', $value4 = '', $value5 = '', $value6 = '') { + require __DIR__."/connection.php"; + $insertAction = $db->prepare('INSERT INTO automod (type, value1, value2, value3, value4, value5, value6, timestamp) VALUES (:type, :value1, :value2, :value3, :value4, :value5, :value6, :timestamp)'); + $insertAction->execute([':type' => $type, ':value1' => $value1, ':value2' => $value2, ':value3' => $value3, ':value4' => $value4, ':value5' => $value5, ':value6' => $value6, ':timestamp' => time()]); + return $db->lastInsertId(); + } + /* + self::getLastAutomodAction($type, $limitTime) + This private function gets last automod action from SQL + $type — type of action (Number) + $limitTime — should function return actions from limited time or all actions (Boolean) + true — limit action search by $levelsCheckPeriod from config/security.php + false — don't limit action search by time + Return value: + Array — array with action data + false — nothing found + */ + private static function getLastAutomodAction($type, $limitTime = false) { + require __DIR__."/connection.php"; + require __DIR__."/../../config/security.php"; + $getAction = $db->prepare('SELECT * FROM automod WHERE type = :type '.($limitTime ? 'AND timestamp > '.time().' - '.(int)$warningsPeriod : '').' ORDER BY timestamp DESC LIMIT 1'); + $getAction->execute([':type' => $type]); + return $getAction->fetch(); + } + /* + Automod::getAutomodActions($types) + This function returns all automod actions of $types + $types — array of types you want to see, all public types if empty (Array) + Return value: + Array — array of actions + false — nothing found + */ + public static function getAutomodActions($types = []) { + require __DIR__."/connection.php"; + if(!is_array($types) || empty($types)) $types = self::getPublicActionTypes(); + $getActions = $db->prepare('SELECT * FROM automod WHERE type IN ('.implode(',', $types).') ORDER BY timestamp DESC'); + $getActions->execute(); + return $getActions->fetchAll(); + } + /* + Automod::changeAutomodAction($actionID, $isResolved, $value1, $value2, $value3, $value4, $value5, $value6) + This function changes automod action values + $actionID — ID of action you want to change (Number) + $isResolved — is action resolved or not (Number) + 1 — action is resolved + 0 — action is not resolved + $value1 — change action value 1 (Mixed) + $value2 — change action value 2 (Mixed) + $value3 — change action value 3 (Mixed) + $value4 — change action value 4 (Mixed) + $value5 — change action value 5 (Mixed) + $value6 — change action value 6 (Mixed) + If $value is false, doesn't change value + Return value: + true — action changed successfully + false — something went wrong when changing value + */ + public static function changeAutomodAction($actionID, $isResolved, $value1 = false, $value2 = false, $value3 = false, $value4 = false, $value5 = false, $value6 = false) { + require __DIR__."/connection.php"; + $getAction = $db->prepare('SELECT * FROM automod WHERE ID = :ID'); + $getAction->execute([':ID' => $actionID]); + $getAction = $getAction->fetch(); + if(!$getAction) return false; + $changeAction = $db->prepare('UPDATE automod SET value1 = :value1, value2 = :value2, value3 = :value3, value4 = :value4, value5 = :value5, value6 = :value6, resolved = :isResolved WHERE ID = :ID'); + return $changeAction->execute([':ID' => $actionID, ':isResolved' => $isResolved, ':value1' => ($value1 === false ? $getAction['value1'] : $value1), ':value2' => ($value2 === false ? $getAction['value2'] : $value2), ':value3' => ($value3 === false ? $getAction['value3'] : $value3), ':value4' => ($value4 === false ? $getAction['value4'] : $value4), ':value5' => ($value5 === false ? $getAction['value5'] : $value5), ':value6' => ($value6 === false ? $getAction['value6'] : $value6)]); + } + /* + Automod::getAutomodActionByID($actionID) + This function returns action values by action ID + $actionID — action ID you want to find (Number) + Return value: + Array — array of action values + false — nothing found + */ + public static function getAutomodActionByID($actionID) { + require __DIR__."/connection.php"; + $getAction = $db->prepare('SELECT * FROM automod WHERE ID = :ID'); + $getAction->execute([':ID' => $actionID]); + return $getAction->fetch(); + } + /* + Automod::isLevelsDisabled() + This function checks if levels uploading is disabled by automod + Return value: + true — levels uploading is disabled + false — levels uploading is enabled + */ + public static function isLevelsDisabled($disableType = 0) { + $actionTypes = self::getLevelsDisableTypes(); + $isDisabled = self::getLastAutomodAction($actionTypes[$disableType]); + if(!$isDisabled['resolved']) { + $disableExpires = $isDisabled['value1'] ?? 0; + if($disableExpires <= time()) { + self::changeAutomodAction($isDisabled['ID'], 1); + return false; + } + return true; + } + return false; + } + /* + Automod::changeLevelsAutomodState($disableType, $isDisable, $expires) + This function change levels automod state + $disableType — type of levels disabling (Number) + $isDisable — disabling or enabling (Boolean) + true — disable state + false — enable state + $expires — when disabling will expire, required if disabling (Number) + Return value: void + */ + public static function changeLevelsAutomodState($disableType, $isDisable, $expires = 0) { + $actionTypes = self::getLevelsDisableTypes(); + $action = self::getLastAutomodAction($actionTypes[$disableType]); + if(!$action) self::logAutomodActions($actionTypes[$disableType], $expires); + elseif($isDisable) { + if($action['resolved']) self::logAutomodActions($actionTypes[$disableType], $expires); + else self::changeAutomodAction($action['ID'], 0, $expires); + } else self::changeAutomodAction($action['ID'], 1, false, time()); + } + /* + self::getPublicActionTypes() + This private function returns all types of public automod actions + Return value: + Array — array of public action types + */ + private static function getPublicActionTypes() { + return [1, 5, 10, 11, 12, 13, 14, 15]; + } + /* + Automod::getLevelsCountPerDay() + This function returns levels count yesterday and today + Return value: + Array — levels yesterday and today + */ + public static function getLevelsCountPerDay() { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + require __DIR__."/../../config/security.php"; + $gs = new mainLib(); + $levelsPeriod = time() - ($levelsCheckPeriod * 2); + $levelsCount = $db->prepare("SELECT count(*) FROM levels WHERE uploadDate >= :time AND uploadDate <= :time2 ORDER BY uploadDate DESC"); + $levelsCount->execute([':time' => $levelsPeriod, ':time2' => $levelsPeriod + $levelsCheckPeriod]); + $levelsYesterday = $levelsCount->fetchColumn(); + + $levelsPeriod = $levelsPeriod + $levelsCheckPeriod; + $levelsCount = $db->prepare("SELECT count(*) FROM levels WHERE uploadDate >= :time AND uploadDate <= :time2 ORDER BY uploadDate DESC"); + $levelsCount->execute([':time' => $levelsPeriod, ':time2' => $levelsPeriod + $levelsCheckPeriod]); + $levelsToday = $levelsCount->fetchColumn(); + return ['yesterday' => $levelsYesterday, 'today' => $levelsToday]; + } + /* + self::getLevelsDisableTypes() + This private function returns all level disables types + Return value: + Array — array of level disables types + */ + private static function getLevelsDisableTypes() { + return [2, 3, 4]; + } + /* + Automod::getLevelsDisableStates() + This function returns expire time of all level disables types + Return value: + Array — array of expire time of all level disables types + */ + public static function getLevelsDisableStates() { + $disableTypes = self::getLevelsDisableTypes(); + $levelsUploadingTime = $levelsCommentingTime = $levelsLeaderboardSubmitsTime = ''; + $levelsUploadingAction = self::getLastAutomodAction($disableTypes[0]); + if($levelsUploadingAction['value1'] <= time()) { + self::changeAutomodAction($levelsUploadingAction['ID'], 1); + $levelsUploadingAction['resolved'] = 1; + } + if(is_array($levelsUploadingAction) && !$levelsUploadingAction['resolved']) $levelsUploadingTime = date('Y-m-d\TH:i:s', $levelsUploadingAction['value1']); + $levelsCommentingAction = self::getLastAutomodAction($disableTypes[1]); + if($levelsCommentingAction['value1'] <= time()) { + self::changeAutomodAction($levelsCommentingAction['ID'], 1); + $levelsCommentingAction['resolved'] = 1; + } + if(is_array($levelsCommentingAction) && !$levelsCommentingAction['resolved']) $levelsCommentingTime = date('Y-m-d\TH:i:s', $levelsCommentingAction['value1']); + $levelsLeaderboardSubmitsAction = self::getLastAutomodAction($disableTypes[2]); + if($levelsLeaderboardSubmitsAction['value1'] <= time()) { + self::changeAutomodAction($levelsLeaderboardSubmitsAction['ID'], 1); + $levelsLeaderboardSubmitsAction['resolved'] = 1; + } + if(is_array($levelsLeaderboardSubmitsAction) && !$levelsLeaderboardSubmitsAction['resolved']) $levelsLeaderboardSubmitsTime = date('Y-m-d\TH:i:s', $levelsLeaderboardSubmitsAction['value1']); + return [$levelsUploadingTime, $levelsCommentingTime, $levelsLeaderboardSubmitsTime]; + } + /* + Automod::getAccountsCountPerDay() + This function returns accounts count yesterday and today + Return value: + Array — accounts yesterday and today + */ + public static function getAccountsCountPerDay() { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + require __DIR__."/../../config/security.php"; + $gs = new mainLib(); + $accountsPeriod = time() - ($accountsCheckPeriod * 2); + $accountsCount = $db->prepare("SELECT count(*) FROM accounts WHERE registerDate >= :time AND registerDate <= :time2 ORDER BY registerDate DESC"); + $accountsCount->execute([':time' => $accountsPeriod, ':time2' => $accountsPeriod + $accountsCheckPeriod]); + $accountsYesterday = $accountsCount->fetchColumn(); + + $accountsPeriod = $accountsPeriod + $accountsCheckPeriod; + $accountsCount = $db->prepare("SELECT count(*) FROM accounts WHERE registerDate >= :time AND registerDate <= :time2 ORDER BY registerDate DESC"); + $accountsCount->execute([':time' => $accountsPeriod, ':time2' => $accountsPeriod + $accountsCheckPeriod]); + $accountsToday = $accountsCount->fetchColumn(); + return ['yesterday' => $accountsYesterday, 'today' => $accountsToday]; + } + /* + Automod::checkAccountsCount() + This function checks accounts register count to see if there is too many accounts registered in small time + Return value: + true — everything is normal, nothing to be scared of + false — high accounts amount detected! possible raid + */ + public static function checkAccountsCount() { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + require __DIR__."/../../config/security.php"; + $gs = new mainLib(); + $accountsCount = self::getAccountsCountPerDay(); + $accountsYesterday = $levelsCount['yesterday']; + $accountsToday = $levelsCount['today']; + + $accountsYesterdayModified = $accountsYesterday * $accountsCountModifier + 3; + if($accountsToday > $accountsYesterdayModified) { + $isWarned = self::getLastAutomodAction(5, true); + if(!$isWarned) { + self::logAutomodActions(5, $accountsYesterday, $accountsToday, $accountsYesterdayModified); + $gs->sendAccountsWarningWebhook($accountsYesterday, $accountsToday); + } + return false; + } + return true; + } + /* + Automod::getAutomodTypes() + This function returns automod types (levels, accounts, etc) according to their action type + Return value: + Array — array with automod types + */ + public static function getAutomodTypes() { + return [1 => 1, 5 => 2, 10 => 3, 11 => 4, 12 => 5, 13 => 6, 14 => 7, 15 => 8]; + } + /* + self::getAccountsDisableTypes() + This private function returns all account disables types + Return value: + Array — array of account disables types + */ + private static function getAccountsDisableTypes() { + return [6, 7, 8, 9]; + } + /* + Automod::getAccountsDisableStates() + This function returns expire time of all level disables types + Return value: + Array — array of expire time of all level disables types + */ + public static function getAccountsDisableStates() { + $disableTypes = self::getAccountsDisableTypes(); + $accountRegisteringTime = $accountPostingTime = $accountStatsUpdatingTime = $accountMessagingTime = ''; + $accountRegisteringAction = self::getLastAutomodAction($disableTypes[0]); + if($accountRegisteringAction['value1'] <= time()) { + self::changeAutomodAction($accountRegisteringAction['ID'], 1); + $accountRegisteringAction['resolved'] = 1; + } + if(is_array($accountRegisteringAction) && !$accountRegisteringAction['resolved']) $accountRegisteringTime = date('Y-m-d\TH:i:s', $accountRegisteringAction['value1']); + $accountPostingAction = self::getLastAutomodAction($disableTypes[1]); + if($accountPostingAction['value1'] <= time()) { + self::changeAutomodAction($accountPostingAction['ID'], 1); + $accountPostingAction['resolved'] = 1; + } + if(is_array($accountPostingAction) && !$accountPostingAction['resolved']) $accountPostingTime = date('Y-m-d\TH:i:s', $accountPostingAction['value1']); + $accountStatsUpdatingAction = self::getLastAutomodAction($disableTypes[2]); + if($accountStatsUpdatingAction['value1'] <= time()) { + self::changeAutomodAction($accountStatsUpdatingAction['ID'], 1); + $accountStatsUpdatingAction['resolved'] = 1; + } + if(is_array($accountStatsUpdatingAction) && !$accountStatsUpdatingAction['resolved']) $accountStatsUpdatingTime = date('Y-m-d\TH:i:s', $accountStatsUpdatingAction['value1']); + $accountMessagingAction = self::getLastAutomodAction($disableTypes[3]); + if($accountMessagingAction['value1'] <= time()) { + self::changeAutomodAction($accountMessagingAction['ID'], 1); + $accountMessagingAction['resolved'] = 1; + } + if(is_array($accountMessagingAction) && !$accountMessagingAction['resolved']) $accountMessagingTime = date('Y-m-d\TH:i:s', $accountMessagingAction['value1']); + return [$accountRegisteringTime, $accountPostingTime, $accountStatsUpdatingTime, $accountMessagingTime]; + } + /* + Automod::changeAccountsAutomodState($disableType, $isDisable, $expires) + This function change accounts automod state + $disableType — type of accounts disabling (Number) + $isDisable — disabling or enabling (Boolean) + true — disable state + false — enable state + $expires — when disabling will expire, required if disabling (Number) + Return value: void + */ + public static function changeAccountsAutomodState($disableType, $isDisable, $expires = 0) { + $actionTypes = self::getAccountsDisableTypes(); + $action = self::getLastAutomodAction($actionTypes[$disableType]); + if(!$action) self::logAutomodActions($actionTypes[$disableType], $expires); + elseif($isDisable) { + if($action['resolved']) self::logAutomodActions($actionTypes[$disableType], $expires); + else self::changeAutomodAction($action['ID'], 0, $expires); + } else self::changeAutomodAction($action['ID'], 1, false, time()); + } + /* + Automod::isAccountsDisabled() + This function checks if levels uploading is disabled by automod + Return value: + true — levels uploading is disabled + false — levels uploading is enabled + */ + public static function isAccountsDisabled($disableType = 0) { + $actionTypes = self::getAccountsDisableTypes(); + $isDisabled = self::getLastAutomodAction($actionTypes[$disableType]); + if(!$isDisabled['resolved']) { + $disableExpires = $isDisabled['value1'] ?? 0; + if($disableExpires <= time()) { + self::changeAutomodAction($isDisabled['ID'], 1); + return false; + } + return true; + } + return false; + } + /* + self::check_comments_similarity($str1, $str2) + This private function checks similarity of 2 strings + $str1 — string 1 (String) + $str2 — string 2 (String) + Return value: + Number — similarity of strings + Taken from https://www.php.net/manual/ru/function.similar-text.php#118799 + */ + private static function check_comments_similarity($str1, $str2) { + $len1 = strlen($str1); + $len2 = strlen($str2); + $max = max($len1, $len2); + $similarity = $i = $j = 0; + while(($i < $len1) && isset($str2[$j])) { + if($str1[$i] == $str2[$j]) { + $similarity++; + $i++; + $j++; + } elseif($len1 < $len2) { + $len1++; + $j++; + } elseif($len1 > $len2) { + $i++; + $len1--; + } else { + $i++; + $j++; + } + } + return round($similarity / $max, 2); + } + /* + Automod::similarity($str1, $str2) + This function checks similarity of 2 strings 4 times with different algorithms and returns greatest value + $str1 — string 1 (String) + $str2 — string 2 (String) + Return value: + Number — similarity of strings + https://gcs.icu/WTFIcons/checking_speed.png + */ + public static function similarity($str1, $str2) { + $check1 = self::check_comments_similarity($str1, $str2); + $check2 = self::check_comments_similarity($str2, $str1); + similar_text($str1, $str2, $perc); + $check3 = round($perc / 100, 2); + similar_text($str2, $str1, $perc); + $check4 = round($perc / 100, 2); + $biggestOne = [$check1, $check2, $check3, $check4]; + rsort($biggestOne); + return $biggestOne[0]; + } + /* + Automod::checkCommentsSpamming($userID) + This function checks last comments for spamming + $userID — user ID of latest comment author (Number) + Return value: + true — everything is good, no spamming + false — spamming detected! + */ + public static function checkCommentsSpamming($userID) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + require_once __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/security.php"; + $gs = new mainLib(); + $returnValue = true; + $comments = $db->prepare('SELECT comment, userID FROM comments WHERE timestamp > :time ORDER BY timestamp DESC'); + $comments->execute([':time' => time() - $commentsCheckPeriod]); + $comments = $comments->fetchAll(); + $commentsCount = count($comments); + $similarity = 0; + $x = 1; + $similarCommentsCount = 0; + $similarCommentsAuthors = []; + foreach($comments AS &$comment) { + if(!isset($comments[$x])) break; + $comment1 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comment['comment'])); + $comment2 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comments[$x]['comment'])); + $sim = self::similarity($comment1, $comment2); + if($sim > 0.5) { + $similarCommentsAuthors[] = $comment['userID']; + $similarCommentsCount++; + } + $similarity += $sim; + $x++; + } + if($similarity > $commentsCount / 3 && $commentsCount > 5) { + $isWarned = self::getLastAutomodAction(10, true); + if(!$isWarned) { + $similarCommentsAuthors = array_unique($similarCommentsAuthors); + self::logAutomodActions(10, $similarCommentsCount, $similarity, $commentsCount, implode(', ', $similarCommentsAuthors)); + $gs->sendCommentsSpammingWarningWebhook($similarCommentsCount, $similarCommentsAuthors); + } + $returnValue = false; + } + + $comments = $db->prepare('SELECT comment FROM comments WHERE timestamp > :time AND userID = :userID ORDER BY timestamp DESC'); + $comments->execute([':time' => time() - $commentsCheckPeriod, ':userID' => $userID]); + $comments = $comments->fetchAll(); + $commentsCount = count($comments); + $similarity = 0; + $x = 1; + $similarCommentsCount = 0; + foreach($comments AS &$comment) { + if(!isset($comments[$x])) break; + $comment1 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comment['comment'])); + $comment2 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comments[$x]['comment'])); + $sim = self::similarity($comment1, $comment2); + if($sim > 0.5) $similarCommentsCount++; + $similarity += $sim; + $x++; + } + if($similarity > $commentsCount / 3 && $commentsCount > 3) { + $isWarned = self::getLastAutomodAction(11, true); + if(!$isWarned) { + self::logAutomodActions(11, $similarCommentsCount, $similarity, $commentsCount, $userID); + $gs->sendCommentsSpammerWarningWebhook($similarCommentsCount, $userID); + } + $returnValue = false; + } + return $returnValue; + } + /* + Automod::checkAccountPostsSpamming($userID) + This function checks last account posts for spamming + $userID — user ID of latest post author (Number) + Return value: + true — everything is good, no spamming + false — spamming detected! + */ + public static function checkAccountPostsSpamming($userID) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + require_once __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/security.php"; + $gs = new mainLib(); + $returnValue = true; + $comments = $db->prepare('SELECT comment, userID FROM acccomments WHERE timestamp > :time ORDER BY timestamp DESC'); + $comments->execute([':time' => time() - $commentsCheckPeriod]); + $comments = $comments->fetchAll(); + $commentsCount = count($comments); + $similarity = 0; + $x = 1; + $similarCommentsCount = 0; + $similarCommentsAuthors = []; + foreach($comments AS &$comment) { + if(!isset($comments[$x])) break; + $comment1 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comment['comment'])); + $comment2 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comments[$x]['comment'])); + $sim = self::similarity($comment1, $comment2); + if($sim > 0.5) { + $similarCommentsAuthors[] = $comment['userID']; + $similarCommentsCount++; + } + $similarity += $sim; + $x++; + } + if($similarity > $commentsCount / 3 && $commentsCount > 5) { + $isWarned = self::getLastAutomodAction(12, true); + if(!$isWarned) { + $similarCommentsAuthors = array_unique($similarCommentsAuthors); + self::logAutomodActions(12, $similarCommentsCount, $similarity, $commentsCount, implode(', ', $similarCommentsAuthors)); + $gs->sendAccountPostsSpammingWarningWebhook($similarCommentsCount, $similarCommentsAuthors); + } + $returnValue = false; + } + + $comments = $db->prepare('SELECT comment FROM acccomments WHERE timestamp > :time AND userID = :userID ORDER BY timestamp DESC'); + $comments->execute([':time' => time() - $commentsCheckPeriod, ':userID' => $userID]); + $comments = $comments->fetchAll(); + $commentsCount = count($comments); + $similarity = 0; + $x = 1; + $similarCommentsCount = 0; + foreach($comments AS &$comment) { + if(!isset($comments[$x])) break; + $comment1 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comment['comment'])); + $comment2 = ExploitPatch::prepare_for_checking(ExploitPatch::url_base64_decode($comments[$x]['comment'])); + $sim = self::similarity($comment1, $comment2); + if($sim > 0.5) $similarCommentsCount++; + $similarity += $sim; + $x++; + } + if($similarity > $commentsCount / 3 && $commentsCount > 3) { + $isWarned = self::getLastAutomodAction(13, true); + if(!$isWarned) { + self::logAutomodActions(13, $similarCommentsCount, $similarity, $commentsCount, $userID); + $gs->sendAccountPostsSpammerWarningWebhook($similarCommentsCount, $userID); + } + $returnValue = false; + } + return $returnValue; + } + /* + Automod::checkRepliesSpamming($accountID) + This function checks last replies for spamming + $userID — account ID of latest reply author (Number) + Return value: + true — everything is good, no spamming + false — spamming detected! + */ + public static function checkRepliesSpamming($accountID) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + require_once __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/security.php"; + $gs = new mainLib(); + $returnValue = true; + $comments = $db->prepare('SELECT body, accountID FROM replies WHERE timestamp > :time ORDER BY timestamp DESC'); + $comments->execute([':time' => time() - $commentsCheckPeriod]); + $comments = $comments->fetchAll(); + $commentsCount = count($comments); + $similarity = 0; + $x = 1; + $similarCommentsCount = 0; + $similarCommentsAuthors = []; + foreach($comments AS &$comment) { + if(!isset($comments[$x])) break; + $comment1 = ExploitPatch::prepare_for_checking(base64_decode($comment['body'])); + $comment2 = ExploitPatch::prepare_for_checking(base64_decode($comments[$x]['body'])); + $sim = self::similarity($comment1, $comment2); + if($sim > 0.5) { + $similarCommentsAuthors[] = $comment['accountID']; + $similarCommentsCount++; + } + $similarity += $sim; + $x++; + } + if($similarity > $commentsCount / 3 && $commentsCount > 5) { + $isWarned = self::getLastAutomodAction(14, true); + if(!$isWarned) { + $similarCommentsAuthors = array_unique($similarCommentsAuthors); + self::logAutomodActions(14, $similarCommentsCount, $similarity, $commentsCount, implode(', ', $similarCommentsAuthors)); + $gs->sendRepliesSpammingWarningWebhook($similarCommentsCount, $similarCommentsAuthors); + } + $returnValue = false; + } + + $comments = $db->prepare('SELECT body FROM replies WHERE timestamp > :time AND accountID = :accountID ORDER BY timestamp DESC'); + $comments->execute([':time' => time() - $commentsCheckPeriod, ':accountID' => $accountID]); + $comments = $comments->fetchAll(); + $commentsCount = count($comments); + $similarity = 0; + $x = 1; + $similarCommentsCount = 0; + foreach($comments AS &$comment) { + if(!isset($comments[$x])) break; + $comment1 = ExploitPatch::prepare_for_checking(base64_decode($comment['body'])); + $comment2 = ExploitPatch::prepare_for_checking(base64_decode($comments[$x]['body'])); + $sim = self::similarity($comment1, $comment2); + if($sim > 0.5) $similarCommentsCount++; + $similarity += $sim; + $x++; + } + if($similarity > $commentsCount / 3 && $commentsCount > 3) { + $isWarned = self::getLastAutomodAction(15, true); + if(!$isWarned) { + self::logAutomodActions(15, $similarCommentsCount, $similarity, $commentsCount, $accountID); + $gs->sendRepliesSpammerWarningWebhook($similarCommentsCount, $accountID); + } + $returnValue = false; + } + return $returnValue; + } +} +?> \ No newline at end of file diff --git a/incl/lib/commands.php b/incl/lib/commands.php index ea3e68f44..fd74a6fee 100644 --- a/incl/lib/commands.php +++ b/incl/lib/commands.php @@ -1,256 +1,452 @@ checkPermission($accountID, "command".$commandInPerms."All") OR ($targetExtID == $accountID AND $gs->checkPermission($accountID, "command".$commandInPerms."Own"))))){ - return true; - } + if($gs->checkPermission($accountID, "command".$commandInPerms."All") OR ($targetExtID == $accountID AND $gs->checkPermission($accountID, "command".$commandInPerms."Own"))) return true; return false; } public static function doCommands($accountID, $comment, $levelID) { - if(!is_numeric($accountID)) return false; + if(!is_numeric($accountID) || !is_numeric($levelID) || substr($comment, 0, 1) != '!') return false; if($levelID < 0) return self::doListCommands($accountID, $comment, $levelID); - include dirname(__FILE__)."/../lib/connection.php"; - require_once "../lib/exploitPatch.php"; - require_once "../lib/mainLib.php"; + require __DIR__."/connection.php"; + require __DIR__."/../../config/misc.php"; + require_once __DIR__."/exploitPatch.php"; + require_once __DIR__."/mainLib.php"; + require_once __DIR__."/cron.php"; $gs = new mainLib(); $commentarray = explode(' ', $comment); $uploadDate = time(); - //LEVELINFO - $query2 = $db->prepare("SELECT extID FROM levels WHERE levelID = :id"); - $query2->execute([':id' => $levelID]); + $query2 = $db->prepare("SELECT extID FROM levels WHERE levelID = :levelID"); + $query2->execute([':levelID' => $levelID]); $targetExtID = $query2->fetchColumn(); - //ADMIN COMMANDS - if(substr($comment,0,5) == '!rate' AND $gs->checkPermission($accountID, "commandRate")){ - $starStars = $commentarray[2]; - if($starStars == ""){ - $starStars = 0; - } - $starCoins = $commentarray[3]; - $starFeatured = $commentarray[4]; - $diffArray = $gs->getDiffFromName($commentarray[1]); - $starDemon = $diffArray[1]; - $starAuto = $diffArray[2]; - $starDifficulty = $diffArray[0]; - $query = $db->prepare("UPDATE levels SET starStars=:starStars, starDifficulty=:starDifficulty, starDemon=:starDemon, starAuto=:starAuto, rateDate=:timestamp WHERE levelID=:levelID"); - $query->execute([':starStars' => $starStars, ':starDifficulty' => $starDifficulty, ':starDemon' => $starDemon, ':starAuto' => $starAuto, ':timestamp' => $uploadDate, ':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('1', :value, :value2, :levelID, :timestamp, :id)"); - $query->execute([':value' => $commentarray[1], ':timestamp' => $uploadDate, ':id' => $accountID, ':value2' => $starStars, ':levelID' => $levelID]); - if($starFeatured != ""){ - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => $starFeatured, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - $query = $db->prepare("UPDATE levels SET starFeatured=:starFeatured WHERE levelID=:levelID"); - $query->execute([':starFeatured' => $starFeatured, ':levelID' => $levelID]); - } - if($starCoins != ""){ + $getLevelData = $db->prepare("SELECT * FROM levels WHERE levelID = :levelID"); + $getLevelData->execute([':levelID' => $levelID]); + $getLevelData = $getLevelData->fetch(); + switch($commentarray[0]) { + case '!r': + case '!rate': + if(!$gs->checkPermission($accountID, "commandRate")) return false; + $starStars = ExploitPatch::number($commentarray[2]); + if(!is_numeric($starStars)) $starStars = 0; + if($starStars == 0) return 'Please use !unrate.'; + $starCoins = ExploitPatch::number($commentarray[3]); + $starFeatured = ExploitPatch::number($commentarray[4]); + if(!is_numeric($starFeatured)) $starFeatured = 0; + $diffArray = $gs->getDiffFromName(ExploitPatch::charclean($commentarray[1])); + $starDemon = $diffArray[1]; + $starAuto = $diffArray[2]; + $starDifficulty = $diffArray[0]; + $diffic = $gs->getDiffFromStars($starStars); + if($diffic["demon"] == 1) $diffic = 'Demon'; + elseif($diffic["auto"] == 1) $diffic = 'Auto'; + else $diffic = $diffic["name"]; + $query = $db->prepare("UPDATE levels SET starStars = :starStars, starDifficulty = :starDifficulty, starDemon = :starDemon, starAuto = :starAuto, rateDate = :timestamp WHERE levelID = :levelID"); + $query->execute([':starStars' => $starStars, ':starDifficulty' => $starDifficulty, ':starDemon' => $starDemon, ':starAuto' => $starAuto, ':timestamp' => $uploadDate, ':levelID' => $levelID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('1', :value, :value2, :levelID, :timestamp, :id)"); + $query->execute([':value' => $commentarray[1], ':timestamp' => $uploadDate, ':id' => $accountID, ':value2' => $starStars, ':levelID' => $levelID]); + if(!empty($starFeatured)) { + if($starFeatured > 1) { + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('4', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $starFeatured - 1, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $query = $db->prepare("UPDATE levels SET starEpic = :starEpic WHERE levelID = :levelID"); + $query->execute([':starEpic' => $starFeatured - 1, ':levelID' => $levelID]); + } elseif($starFeatured == 1) { + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => 1, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $query = $db->prepare("SELECT starFeatured FROM levels WHERE levelID=:levelID ORDER BY starFeatured DESC LIMIT 1"); + $query->execute([':levelID' => $levelID]); + $featuredID = $query->fetchColumn(); + if(!$featuredID) { + $query = $db->prepare("SELECT starFeatured FROM levels ORDER BY starFeatured DESC LIMIT 1"); + $query->execute(); + $featuredID = $query->fetchColumn() + 1; + } + $query = $db->prepare("UPDATE levels SET starFeatured=:starFeatured WHERE levelID=:levelID"); + $query->execute([':starFeatured' => $featuredID + 1, ':levelID' => $levelID]); + } + } else $starFeatured = 0; + if(!empty($starCoins)) { + if($starCoins > 1 OR $starCoins < 0) $starCoins = 1; + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('3', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $starCoins, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $query = $db->prepare("UPDATE levels SET starCoins=:starCoins WHERE levelID=:levelID"); + $query->execute([':starCoins' => $starCoins, ':levelID' => $levelID]); + } + $gs->sendRateWebhook($accountID, $levelID); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); + return 'You successfully rated '.$gs->getLevelName($levelID).' as '.$diffic.', '.$starStars.' star'.($starStars == 1 ? '' : 's').'!'; + break; + case '!unr': + case '!unrate': + if(!$gs->checkPermission($accountID, "commandRate")) return false; + $query = $db->prepare("UPDATE levels SET starStars = 0, starDemon = 0, rateDate = :timestamp, starFeatured = 0, starEpic = 0, starCoins = 0 WHERE levelID = :levelID"); + $query->execute([':timestamp' => $uploadDate, ':levelID' => $levelID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('1', :value, :value2, :levelID, :timestamp, :id)"); + $query->execute([':value' => 0, ':timestamp' => $uploadDate, ':id' => $accountID, ':value2' => 0, ':levelID' => $levelID]); + $levelDiff = $gs->getLevelDiff($levelID); + $gs->sendRateWebhook($accountID, $levelID); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); + return 'You successfully unrated '.$gs->getLevelName($levelID).'!'; + break; + case '!f': + case '!feature': + case '!epic': + case '!legendary': + case '!mythic': + case '!unfeature': + case '!unepic': + case '!unlegendary': + case '!unmythic': + if(!isset($commentarray[1])) { + $starArray = ['!f' => 1, '!feature' => 1, '!epic' => 2, '!legendary' => 3, '!mythic' => 4, '!unfeature' => 0, '!unepic' => 0, '!unlegendary' => 0, '!unmythic' => 0]; + if($starArray[$commentarray[0]] > 1) { + if(!$gs->checkPermission($accountID, "commandEpic")) return false; + $column = 'starEpic'; + $starFeatured = $starArray[$commentarray[0]] - 1; + $returnTextArray = ['!epic' => 'epiced %1$s!', '!legendary' => 'set %1$s as a legendary level!', '!mythic' => 'set %1$s as a mythic level!']; + $returnText = 'You successfully '.sprintf($returnTextArray[$commentarray[0]], $gs->getLevelName($levelID)); + } else { + if(!$gs->checkPermission($accountID, "commandFeature")) return false; + $column = 'starFeatured'; + if($starArray[$commentarray[0]] != 0) { + $query = $db->prepare("SELECT starFeatured FROM levels WHERE levelID=:levelID ORDER BY starFeatured DESC LIMIT 1"); + $query->execute([':levelID' => $levelID]); + $starFeatured = $query->fetchColumn(); + if(!$starFeatured) { + $query = $db->prepare("SELECT starFeatured FROM levels ORDER BY starFeatured DESC LIMIT 1"); + $query->execute(); + $starFeatured = $query->fetchColumn() + 1; + } + $returnText = 'You successfully featured '.$gs->getLevelName($levelID).'!'; + } else $returnText = 'You successfully unfeatured '.$gs->getLevelName($levelID).'!'; + } + } else { + if($commentarray[1] > 1) { + if(!$gs->checkPermission($accountID, "commandEpic")) return false; + $column = 'starEpic'; + $starFeatured = ExploitPatch::number($commentarray[1]) - 1; + $returnTextArray = ['!epic' => 'epiced %1$s!', '!legendary' => 'set %1$s as a legendary level!', '!mythic' => 'set %1$s as a mythic level!']; + $returnText = 'You successfully '.sprintf($returnTextArray[$commentarray[0]], $gs->getLevelName($levelID)); + } else { + if(!$gs->checkPermission($accountID, "commandFeature")) return false; + $column = 'starFeatured'; + if($starArray[$commentarray[0]] != 0) { + $query = $db->prepare("SELECT starFeatured FROM levels WHERE levelID=:levelID ORDER BY starFeatured DESC LIMIT 1"); + $query->execute([':levelID' => $levelID]); + $starFeatured = $query->fetchColumn(); + if(!$starFeatured) { + $query = $db->prepare("SELECT starFeatured FROM levels ORDER BY starFeatured DESC LIMIT 1"); + $query->execute(); + $starFeatured = $query->fetchColumn() + 1; + } + $returnText = 'You successfully featured '.$gs->getLevelName($levelID).'!'; + } else $returnText = 'You successfully unfeatured '.$gs->getLevelName($levelID).'!'; + } + } + if($starArray[$commentarray[0]] == 0 && $commentarray[1] == 0) $column = 'starFeatured = 0, starEpic'; + $query = $db->prepare("UPDATE levels SET $column = :starFeatured WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':starFeatured' => $starFeatured]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('".($column == 'starEpic' ? 4 : 2)."', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => ($column == 'starEpic' ? $starArray[$commentarray[0]] - 1 : $starArray[$commentarray[0]]), ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); + return $returnText; + break; + case '!vc': + case '!verifycoins': + case '!unverifycoins': + if(!$gs->checkPermission($accountID, "commandVerifycoins")) return false; + if(!isset($commentarray[1])) { + $coinsArray = ['!vc' => 1, '!verifycoins' => 1, '!unverifycoins' => 0]; + $verifyCoins = $coinsArray[ExploitPatch::number($commentarray[0])] ?? 1; + } else $verifyCoins = ExploitPatch::number($commentarray[0]) > 1 ? 1 : ExploitPatch::number($commentarray[0]); + $query = $db->prepare("UPDATE levels SET starCoins = :starCoins WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':starCoins' => $verifyCoins]); $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('3', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => $starCoins, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - $query = $db->prepare("UPDATE levels SET starCoins=:starCoins WHERE levelID=:levelID"); - $query->execute([':starCoins' => $starCoins, ':levelID' => $levelID]); - } - return true; - } - if(substr($comment,0,8) == '!feature' AND $gs->checkPermission($accountID, "commandFeature")){ - $query = $db->prepare("UPDATE levels SET starFeatured='1' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(substr($comment,0,5) == '!epic' AND $gs->checkPermission($accountID, "commandEpic")){ - $query = $db->prepare("UPDATE levels SET starEpic='1' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('4', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(substr($comment,0,7) == '!unepic' AND $gs->checkPermission($accountID, "commandUnepic")){ - $query = $db->prepare("UPDATE levels SET starEpic='0' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('4', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "0", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(substr($comment,0,12) == '!verifycoins' AND $gs->checkPermission($accountID, "commandVerifycoins")){ - $query = $db->prepare("UPDATE levels SET starCoins='1' WHERE levelID = :levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(substr($comment,0,6) == '!daily' AND $gs->checkPermission($accountID, "commandDaily")){ - $query = $db->prepare("SELECT count(*) FROM dailyfeatures WHERE levelID = :level AND type = 0"); + $query->execute([':value' => $verifyCoins, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return 'You successfully '.($verifyCoins ? '' : 'un').'verified coins in '.$gs->getLevelName($levelID).'!'; + break; + case '!da': + case '!daily': + if(!$gs->checkPermission($accountID, "commandDaily")) return false; + $query = $db->prepare("SELECT count(*) FROM dailyfeatures WHERE levelID = :level AND type = 0"); $query->execute([':level' => $levelID]); - if($query->fetchColumn() != 0){ - return false; - } - $query = $db->prepare("SELECT timestamp FROM dailyfeatures WHERE timestamp >= :tomorrow AND type = 0 ORDER BY timestamp DESC LIMIT 1"); - $query->execute([':tomorrow' => strtotime("tomorrow 00:00:00")]); - if($query->rowCount() == 0){ - $timestamp = strtotime("tomorrow 00:00:00"); - }else{ - $timestamp = $query->fetchColumn() + 86400; - } - $query = $db->prepare("INSERT INTO dailyfeatures (levelID, timestamp, type) VALUES (:levelID, :uploadDate, 0)"); + if($query->fetchColumn() != 0) return $gs->getLevelName($levelID).' is already daily!'; + $query = $db->prepare("SELECT timestamp FROM dailyfeatures WHERE timestamp >= :tomorrow AND type = 0 ORDER BY timestamp DESC LIMIT 1"); + $query->execute([':tomorrow' => strtotime("tomorrow 00:00:00")]); + if($query->rowCount() == 0) $timestamp = strtotime("tomorrow 00:00:00"); + else $timestamp = $query->fetchColumn() + 86400; + $query = $db->prepare("INSERT INTO dailyfeatures (levelID, timestamp, type) VALUES (:levelID, :uploadDate, 0)"); $query->execute([':levelID' => $levelID, ':uploadDate' => $timestamp]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account, value2, value4) VALUES ('5', :value, :levelID, :timestamp, :id, :dailytime, 0)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID, ':dailytime' => $timestamp]); - return true; - } - if(substr($comment,0,7) == '!weekly' AND $gs->checkPermission($accountID, "commandWeekly")){ - $query = $db->prepare("SELECT count(*) FROM dailyfeatures WHERE levelID = :level AND type = 1"); - $query->execute([':level' => $levelID]); - if($query->fetchColumn() != 0){ - return false; - } - $query = $db->prepare("SELECT timestamp FROM dailyfeatures WHERE timestamp >= :tomorrow AND type = 1 ORDER BY timestamp DESC LIMIT 1"); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account, value2, value4) VALUES ('5', :value, :levelID, :timestamp, :id, :dailytime, 0)"); + $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID, ':dailytime' => $timestamp]); + return 'You successfully made '.$gs->getLevelName($levelID).' daily!'; + break; + case '!w': + case '!weekly': + if(!$gs->checkPermission($accountID, "commandWeekly")) return false; + $query = $db->prepare("SELECT count(*) FROM dailyfeatures WHERE levelID = :level AND type = 1"); + $query->execute([':level' => $levelID]); + if($query->fetchColumn() != 0) return $gs->getLevelName($levelID).' is already weekly!'; + $query = $db->prepare("SELECT timestamp FROM dailyfeatures WHERE timestamp >= :tomorrow AND type = 1 ORDER BY timestamp DESC LIMIT 1"); $query->execute([':tomorrow' => strtotime("next monday")]); - if($query->rowCount() == 0){ - $timestamp = strtotime("next monday"); - }else{ - $timestamp = $query->fetchColumn() + 604800; - } - $query = $db->prepare("INSERT INTO dailyfeatures (levelID, timestamp, type) VALUES (:levelID, :uploadDate, 1)"); - $query->execute([':levelID' => $levelID, ':uploadDate' => $timestamp]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account, value2, value4) VALUES ('5', :value, :levelID, :timestamp, :id, :dailytime, 1)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID, ':dailytime' => $timestamp]); - return true; - } - if(substr($comment,0,6) == '!delet' AND $gs->checkPermission($accountID, "commandDelete")){ - if(!is_numeric($levelID)){ - return false; - } - $query = $db->prepare("DELETE from levels WHERE levelID=:levelID LIMIT 1"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('6', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - if(file_exists(dirname(__FILE__)."../../data/levels/$levelID")){ - rename(dirname(__FILE__)."../../data/levels/$levelID",dirname(__FILE__)."../../data/levels/deleted/$levelID"); - } - return true; - } - if(substr($comment,0,7) == '!setacc' AND $gs->checkPermission($accountID, "commandSetacc")){ - $query = $db->prepare("SELECT accountID FROM accounts WHERE userName = :userName OR accountID = :userName LIMIT 1"); - $query->execute([':userName' => $commentarray[1]]); - if($query->rowCount() == 0){ - return false; - } - $targetAcc = $query->fetchColumn(); - //var_dump($result); - $query = $db->prepare("SELECT userID FROM users WHERE extID = :extID LIMIT 1"); - $query->execute([':extID' => $targetAcc]); - $userID = $query->fetchColumn(); - $query = $db->prepare("UPDATE levels SET extID=:extID, userID=:userID, userName=:userName WHERE levelID=:levelID"); - $query->execute([':extID' => $targetAcc, ':userID' => $userID, ':userName' => $commentarray[1], ':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('7', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => $commentarray[1], ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - - - //NON-ADMIN COMMANDS - if(self::ownCommand($comment, "rename", $accountID, $targetExtID)){ - $name = ExploitPatch::remove(str_replace("!rename ", "", $comment)); - $query = $db->prepare("UPDATE levels SET levelName=:levelName WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID, ':levelName' => $name]); - $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('8', :value, :timestamp, :id, :levelID)"); - $query->execute([':value' => $name, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(self::ownCommand($comment, "pass", $accountID, $targetExtID)){ - $pass = ExploitPatch::remove(str_replace("!pass ", "", $comment)); - if(is_numeric($pass)){ - $pass = sprintf("%06d", $pass); - if($pass == "000000"){ - $pass = ""; + if($query->rowCount() == 0) $timestamp = strtotime("next monday"); + else $timestamp = $query->fetchColumn() + 604800; + $query = $db->prepare("INSERT INTO dailyfeatures (levelID, timestamp, type) VALUES (:levelID, :uploadDate, 1)"); + $query->execute([':levelID' => $levelID, ':uploadDate' => $timestamp]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account, value2, value4) VALUES ('5', :value, :levelID, :timestamp, :id, :dailytime, 1)"); + $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID, ':dailytime' => $timestamp]); + return 'You successfully made '.$gs->getLevelName($levelID).' weekly!'; + break; + case '!d': + case '!delet': + case '!delete': + if(!$gs->checkPermission($accountID, "commandDelete")) return false; + $levelName = $gs->getLevelName($levelID); + if(!$levelName) return false; + $query = $db->prepare("DELETE FROM comments WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID]); + $query = $db->prepare("DELETE from levels WHERE levelID = :levelID LIMIT 1"); + $query->execute([':levelID' => $levelID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('6', :value, :value2, :levelID, :timestamp, :id)"); + $query->execute([':value' => "1", ":value2" => $levelName, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + if(file_exists(__DIR__."../../data/levels/$levelID")) rename(__DIR__."../../data/levels/$levelID", __DIR__."../../data/levels/deleted/$levelID"); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return 'You successfully deleted '.$levelName.'!'; + break; + case '!send': + case '!unsend': + if(!$gs->checkPermission($accountID, "actionSuggestRating")) return false; + $levelName = $gs->getLevelName($levelID); + if(!$levelName) return false; + $sendArray = ['!send' => 1, '!unsend' => 0]; + $sendValue = $sendArray[$commentarray[0]]; + if($sendValue) { + $query = $db->prepare("SELECT COUNT(*) FROM suggest WHERE suggestLevelId = :levelID AND suggestBy = :accountID"); + $query->execute([':levelID' => $levelID, ':accountID' => $accountID]); + if($query->fetchColumn() != 0) return 'You already suggested '.$levelName.'!'; + $diffVariable = ExploitPatch::charclean($commentarray[1]); + $starStars = ExploitPatch::number($commentarray[2]); + $starFeatured = ExploitPatch::number($commentarray[3]); + if(!is_numeric($starFeatured)) $starFeatured = 0; + $diffArray = $gs->getDiffFromName($diffVariable); + $starDemon = $diffArray[1]; + $starAuto = $diffArray[2]; + $starDifficulty = $diffArray[0]; + $diffic = $gs->getDiffFromStars($starStars); + if($diffic["demon"] == 1) $diffic = 'Demon'; + elseif($diffic["auto"] == 1) $diffic = 'Auto'; + else $diffic = $diffic["name"]; + $gs->suggestLevel($accountID, $levelID, $starDifficulty, $starStars, $starFeatured, $starAuto, $starDemon); + return 'You successfully suggested '.$levelName.' as '.$diffic.', '.$starStars.' star'.($starStars == 1 ? '' : 's').'!'; + } else { + $query = $db->prepare("SELECT COUNT(*) FROM suggest WHERE suggestLevelId = :levelID"); + $query->execute([':levelID' => $levelID]); + if($query->fetchColumn() == 0) return $levelName.' is not suggested!'; + $gs->removeSuggestedLevel($accountID, $levelID); + return 'You successfully removed level '.$levelName.' from being suggested!'; } - $pass = "1".$pass; - $query = $db->prepare("UPDATE levels SET password=:password WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID, ':password' => $pass]); - $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('9', :value, :timestamp, :id, :levelID)"); - $query->execute([':value' => $pass, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - } - if(self::ownCommand($comment, "song", $accountID, $targetExtID)){ - $song = ExploitPatch::remove(str_replace("!song ", "", $comment)); - if(is_numeric($song)){ - $query = $db->prepare("UPDATE levels SET songID=:song WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID, ':song' => $song]); - $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('16', :value, :timestamp, :id, :levelID)"); - $query->execute([':value' => $song, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - } - if(self::ownCommand($comment, "description", $accountID, $targetExtID)){ - $desc = base64_encode(ExploitPatch::remove(str_replace("!description ", "", $comment))); - $query = $db->prepare("UPDATE levels SET levelDesc=:desc WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID, ':desc' => $desc]); - $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('13', :value, :timestamp, :id, :levelID)"); - $query->execute([':value' => $desc, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(self::ownCommand($comment, "public", $accountID, $targetExtID)){ - $query = $db->prepare("UPDATE levels SET unlisted='0' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('12', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "0", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(self::ownCommand($comment, "unlist", $accountID, $targetExtID)){ - $query = $db->prepare("UPDATE levels SET unlisted='1' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('12', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(self::ownCommand($comment, "sharecp", $accountID, $targetExtID)){ - $query = $db->prepare("SELECT userID FROM users WHERE userName = :userName ORDER BY isRegistered DESC LIMIT 1"); - $query->execute([':userName' => $commentarray[1]]); - $targetAcc = $query->fetchColumn(); - //var_dump($result); - $query = $db->prepare("INSERT INTO cpshares (levelID, userID) VALUES (:levelID, :userID)"); - $query->execute([':userID' => $targetAcc, ':levelID' => $levelID]); - $query = $db->prepare("UPDATE levels SET isCPShared='1' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('11', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => $commentarray[1], ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(self::ownCommand($comment, "ldm", $accountID, $targetExtID)){ - $query = $db->prepare("UPDATE levels SET isLDM='1' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('14', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "1", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; - } - if(self::ownCommand($comment, "unldm", $accountID, $targetExtID)){ - $query = $db->prepare("UPDATE levels SET isLDM='0' WHERE levelID=:levelID"); - $query->execute([':levelID' => $levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('14', :value, :levelID, :timestamp, :id)"); - $query->execute([':value' => "0", ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); - return true; + break; + case '!sa': + case '!setacc': + if(!$gs->checkPermission($accountID, "commandSetacc")) return false; + $query = $db->prepare("SELECT accountID, userName FROM accounts WHERE userName = :userName OR accountID = :userName LIMIT 1"); + $query->execute([':userName' => ExploitPatch::charclean($commentarray[1])]); + $query = $query->fetch(); + if(!$query) return false; + $targetAcc = $query['accountID']; + $targetUserName = $query['userName']; + $query = $db->prepare("SELECT userID FROM users WHERE extID = :extID LIMIT 1"); + $query->execute([':extID' => $targetAcc]); + $userID = $query->fetchColumn(); + $query = $db->prepare("UPDATE levels SET extID = :extID, userID = :userID, userName = :userName WHERE levelID = :levelID"); + $query->execute([':extID' => $targetAcc, ':userID' => $userID, ':userName' => $targetUserName, ':levelID' => $levelID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('7', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $targetUserName, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); + return 'You successfully set '.$gs->getAccountName($targetAcc).' as creator of '.$gs->getLevelName($levelID).'!'; + break; + case '!lockUpdating': + case '!unlockUpdating': + case '!lu': + case '!unlu': + if(!$gs->checkPermission($accountID, "commandLockUpdating")) return false; + if(!isset($commentarray[1])) { + $lockArray = ['!lockUpdating' => 1, '!unlockUpdating' => 0, '!lu' => 1, '!unlu' => 0]; + $lockValue = $lockArray[$commentarray[0]]; + } else $lockValue = $commentarray[1]; + $query = $db->prepare("UPDATE levels SET updateLocked = :updateLocked WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':updateLocked' => $lockValue]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('29', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $lockValue, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return 'You successfully '.($lockValue ? 'locked updating of' : 'unlocked updating of').' level '.$gs->getLevelName($levelID).'!'; + case '!re': + case '!rename': + if(self::ownCommand("rename", $accountID, $targetExtID)) { + $name = ExploitPatch::rucharclean(str_replace($commentarray[0]." ", "", $comment)); + $levelName = $gs->getLevelName($levelID); + if(!$levelName) return false; + $query = $db->prepare("UPDATE levels SET levelName = :levelName WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':levelName' => $name]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, timestamp, account, value3) VALUES ('8', :value, :value2, :timestamp, :id, :levelID)"); + $query->execute([':value' => $name, ":value2" => $levelName, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return 'You successfully renamed '.$levelName.' to '.$gs->getLevelName($levelID).'!'; + } + break; + case '!p': + case '!pass': + case '!password': + if(self::ownCommand("pass", $accountID, $targetExtID)) { + $pass = ExploitPatch::number($commentarray[1]); + if(is_numeric($pass)) { + $pass = sprintf("%06d", $pass); + if($pass == "000000") $pass = ""; + $pass = "1".$pass; + $query = $db->prepare("UPDATE levels SET password = :password WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':password' => $pass]); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('9', :value, :timestamp, :id, :levelID)"); + $query->execute([':value' => $pass, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $returnText = $pass == 1 ? 'You successfully removed password of level '.$gs->getLevelName($levelID).'!' : 'You successfully set password of level '.$gs->getLevelName($levelID).' to '.ExploitPatch::number($commentarray[1]).'!'; + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return $returnText; + } + } + break; + case '!s': + case '!song': + if(self::ownCommand("song", $accountID, $targetExtID)) { + $song = ExploitPatch::number($commentarray[1]); + if(is_numeric($song)) { + $songInfo = $gs->getSongInfo($song); + if(!$songInfo) return false; + $query = $db->prepare("UPDATE levels SET songID = :song WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':song' => $song]); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('16', :value, :timestamp, :id, :levelID)"); + $query->execute([':value' => $song, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + if($automaticCron) Cron::updateSongsUsage($accountID, false); + return 'You successfully changed song of level '.$gs->getLevelName($levelID).' to '.$songInfo['authorName'].' - '.$songInfo['name'].' ('.$songInfo['ID'].')!'; + } + } + break; + case '!desc': + case '!description': + if(self::ownCommand("description", $accountID, $targetExtID)) { + $desc = ExploitPatch::url_base64_encode(ExploitPatch::rucharclean(str_replace($commentarray[0]." ", "", $comment))); + $query = $db->prepare("UPDATE levels SET levelDesc = :desc WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':desc' => $desc]); + $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value3) VALUES ('13', :value, :timestamp, :id, :levelID)"); + $query->execute([':value' => $desc, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return 'You successfully changed description of level '.$gs->getLevelName($levelID).'!'; + } + break; + case '!pub': + case '!public': + case '!unlist': + if(!isset($commentarray[1])) { + $permsArray = ['!pub' => 'public', '!public' => 'public', '!unlist' => 'unlist']; + $permission = $permsArray[$commentarray[0]]; + } else $permission = $commentarray[1] == 1 ? 'public' : 'unlist'; + if(self::ownCommand($permission, $accountID, $targetExtID)) { + $unlisted = $permission == 'public' ? 0 : 1; + $query = $db->prepare("UPDATE levels SET unlisted = :unlisted WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':unlisted' => $unlisted]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('12', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $unlisted, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $returnText = $unlisted ? 'You successfully unlisted level '.$gs->getLevelName($levelID).'!' : 'You successfully made level '.$gs->getLevelName($levelID).' public!'; + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return $returnText; + } + break; + case '!cp': + case '!sharecp': + if(self::ownCommand("sharecp", $accountID, $targetExtID)) { + $query = $db->prepare("SELECT userID FROM users WHERE userName = :userName ORDER BY isRegistered DESC LIMIT 1"); + $query->execute([':userName' => ExploitPatch::charclean($commentarray[1])]); + $targetAcc = $query->fetchColumn(); + $query = $db->prepare("INSERT INTO cpshares (levelID, userID) VALUES (:levelID, :userID)"); + $query->execute([':userID' => $targetAcc, ':levelID' => $levelID]); + $query = $db->prepare("UPDATE levels SET isCPShared = '1' WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('11', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => ExploitPatch::charclean($commentarray[1]), ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); + return 'You successfully shared Creator Points of level '.$gs->getLevelName($levelID).' with '.ExploitPatch::charclean($commentarray[1]).'!'; + } + break; + case '!ldm': + case '!unldm': + if(!isset($commentarray[1])) { + $permsArray = ['!ldm' => 'ldm', '!unldm' => 'unldm']; + $permission = $permsArray[$commentarray[0]]; + } else $permission = $commentarray[1] == 1 ? 'ldm' : 'unldm'; + if(self::ownCommand($permission, $accountID, $targetExtID)) { + $ldm = $permission == 'ldm' ? 1 : 0; + $query = $db->prepare("UPDATE levels SET isLDM = :ldm WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':ldm' => $ldm]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('14', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $ldm, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + return 'You successfully '.($ldm ? 'added LDM to' : 'removed LDM from').' level '.$gs->getLevelName($levelID).'!'; + } + case '!lockComments': + case '!unlockComments': + case '!lc': + case '!unlc': + if(self::ownCommand("lockComments", $accountID, $targetExtID)) { + if(!isset($commentarray[1])) { + $lockArray = ['!lockComments' => 1, '!unlockComments' => 0, '!lc' => 1, '!unlc' => 0]; + $lockValue = $lockArray[$commentarray[0]]; + } else $lockValue = $commentarray[1]; + $query = $db->prepare("UPDATE levels SET commentLocked = :commentLocked WHERE levelID = :levelID"); + $query->execute([':levelID' => $levelID, ':commentLocked' => $lockValue]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('38', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => $lockValue, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + $gs->sendLogsLevelChangeWebhook($levelID, $accountID, $getLevelData); + return 'You successfully '.($lockValue ? 'locked comments on' : 'unlocked comments on').' level '.$gs->getLevelName($levelID).'!'; + } + case '!ev': + case '!event': + if(!$gs->checkPermission($accountID, "commandEvent")) return false; + $duration = time() + ((int)ExploitPatch::number($commentarray[1]) * 60); + unset($commentarray[0]); + unset($commentarray[1]); + $rewards = implode(",", $commentarray); + if($duration <= time() || !$rewards) return 'Usage: !event *event duration in minutes* *reward type* *reward amount* *reward type* *reward amount* and so on...'; + $check = $db->prepare('SELECT count(*) FROM events WHERE timestamp < :time AND duration >= :time'); + $check->execute([':time' => $duration]); + $check = $check->fetchColumn(); + if($check) return 'There is already an event level! Try increasing duration ;)'; + $query = $db->prepare("INSERT INTO events (levelID, timestamp, duration, rewards) VALUES (:levelID, :timestamp, :duration, :rewards)"); + $query->execute([':levelID' => $levelID, ':timestamp' => time(), ':duration' => $duration, ':rewards' => $rewards]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('44', :value, :value2, :levelID, :timestamp, :id)"); + $query->execute([':value' => $duration, ':value2' => $rewards, ':timestamp' => $uploadDate, ':id' => $accountID, ':levelID' => $levelID]); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); + return 'You successfully made '.$gs->getLevelName($levelID).' event level!'; } return false; } public static function doListCommands($accountID, $command, $listID) { if(substr($command,0,1) != '!') return false; $listID = $listID * -1; - include dirname(__FILE__)."/../lib/connection.php"; - require_once "../lib/exploitPatch.php"; - require_once "../lib/mainLib.php"; + require __DIR__."/connection.php"; + require_once __DIR__."/exploitPatch.php"; + require_once __DIR__."/mainLib.php"; $gs = new mainLib(); $carray = explode(' ', $command); + $getList = $db->prepare('SELECT * FROM lists WHERE listID = :listID'); + $getList->execute([':listID' => $listID]); + $getList = $getList->fetch(); switch($carray[0]) { case '!r': case '!rate': - $getList = $db->prepare('SELECT * FROM lists WHERE listID = :listID'); - $getList->execute([':listID' => $listID]); - $getList = $getList->fetch(); $reward = ExploitPatch::number($carray[1]); $diff = ExploitPatch::charclean($carray[2]); $featured = is_numeric($carray[3]) ? ExploitPatch::number($carray[3]) : ExploitPatch::number($carray[4]); @@ -275,11 +471,14 @@ public static function doListCommands($accountID, $command, $listID) { $query->execute([':listID' => $listID, ':reward' => $reward, ':diff' => $diff, ':feat' => $featured, ':count' => $count]); $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('30', :value, :value2, :listID, :timestamp, :id)"); $query->execute([':value' => $reward, ':value2' => $diff, ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully '.($reward == 0 ? 'un' : '').'rated list '.$gs->getListName($listID).'!'; } elseif($gs->checkPermission($accountID, "actionSuggestRating")) { $query = $db->prepare("INSERT INTO suggest (suggestBy, suggestLevelId, suggestDifficulty, suggestStars, suggestFeatured, timestamp) VALUES (:accID, :listID, :diff, :reward, :feat, :time)"); $query->execute([':listID' => $listID*-1, ':reward' => $reward, ':diff' => $diff, ':accID' => $accountID, ':feat' => $featured, ':time' => time()]); $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('31', :value, :value2, :listID, :timestamp, :id)"); $query->execute([':value' => $reward, ':value2' => $diff, ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + return 'You successfully suggested list '.$gs->getListName($listID).'!'; } else return false; break; case '!f': @@ -290,24 +489,33 @@ public static function doListCommands($accountID, $command, $listID) { $query->execute([':listID' => $listID, ':feat' => $carray[1]]); $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('32', :value, :listID, :timestamp, :id)"); $query->execute([':value' => $carray[1], ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully '.($carray[1] == 0 ? 'un' : '').'featured list '.$gs->getListName($listID).'!'; break; case '!un': case '!unlist': $accCheck = $gs->getListOwner($listID); if(!$gs->checkPermission($accountID, "commandUnlistAll") AND $accountID != $accCheck) return false; - if(!isset($carray[1])) $carray[1] = 1; + if(!isset($carray[1])) $carray[1] = 2; $query = $db->prepare("UPDATE lists SET unlisted = :unlisted WHERE listID=:listID"); $query->execute([':listID' => $listID, ':unlisted' => $carray[1]]); $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('33', :value, :listID, :timestamp, :id)"); $query->execute([':value' => $carray[1], ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $listName = $gs->getListName($listID); + $unlistedText = ['You successfully made public list '.$listName.'!', 'You successfully made list '.$listName.' only for friends!', 'You successfully unlisted list '.$listName.'!']; + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return $unlistedText[$carray[1]] ?? $unlistedText[2]; break; case '!d': case '!delete': if(!$gs->checkPermission($accountID, "commandDelete")) return false; + $oldName = $gs->getListName($listID); $query = $db->prepare("DELETE FROM lists WHERE listID = :listID"); $query->execute([':listID' => $listID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('34', 0, :listID, :timestamp, :id)"); - $query->execute([':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('34', 0, :value2, :listID, :timestamp, :id)"); + $query->execute([':timestamp' => time(), ':value2' => $oldName, ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully deleted list '.$oldName.'!'; break; case '!acc': case '!setacc': @@ -319,6 +527,8 @@ public static function doListCommands($accountID, $command, $listID) { $query->execute([':listID' => $listID, ':accID' => $acc]); $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('35', :value, :listID, :timestamp, :id)"); $query->execute([':value' => $acc, ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully changed creator of list '.$gs->getListName($listID).'!'; break; case '!re': case '!rename': @@ -331,52 +541,157 @@ public static function doListCommands($accountID, $command, $listID) { $query->execute([':listID' => $listID, ':name' => $name]); $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('36', :value, :value2, :listID, :timestamp, :id)"); $query->execute([':value' => $name, ':value2' => $oldName, ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully changed description renamed '.$oldName.' to '.$gs->getListName($listID).'!'; break; case '!desc': case '!description': $accCheck = $gs->getListOwner($listID); if(!$gs->checkPermission($accountID, "commandDescriptionAll") AND $accountID != $accCheck) return false; $carray[0] = ''; - $name = base64_encode(trim(ExploitPatch::charclean(implode(' ', $carray)))); + $name = ExploitPatch::url_base64_encode(trim(ExploitPatch::charclean(implode(' ', $carray)))); $query = $db->prepare("UPDATE lists SET listDesc = :name WHERE listID = :listID"); $query->execute([':listID' => $listID, ':name' => $name]); $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('37', :value, :listID, :timestamp, :id)"); $query->execute([':value' => $name, ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully changed description on list '.$gs->getListName($listID).'!'; break; + case '!lockComments': + case '!unlockComments': + case '!lc': + case '!unlc': + if(self::ownCommand("lockComments", $accountID, $targetExtID)) { + if(!isset($carray[1])) { + $lockArray = ['!lockComments' => 1, '!unlockComments' => 0, '!lc' => 1, '!unlc' => 0]; + $lockValue = $lockArray[$carray[0]]; + } else $lockValue = $carray[1]; + $query = $db->prepare("UPDATE lists SET commentLocked = :commentLocked WHERE listID = :listID"); + $query->execute([':listID' => $listID, ':commentLocked' => $lockValue]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('39', :value, :listID, :timestamp, :id)"); + $query->execute([':value' => $lockValue, ':timestamp' => time(), ':id' => $accountID, ':listID' => $listID]); + $gs->sendLogsListChangeWebhook($listID, $accountID, $getList); + return 'You successfully '.($lockValue ? 'locked comments on' : 'unlocked comments on').' list '.$gs->getListName($listID).'!'; + } } - return true; + return false; } public static function doProfileCommands($accountID, $command){ - include dirname(__FILE__)."/../lib/connection.php"; - require_once "../lib/exploitPatch.php"; - require_once "../lib/mainLib.php"; - $gs = new mainLib(); - if(substr($command, 0, 8) == '!discord'){ - if(substr($command, 9, 6) == "accept"){ - $query = $db->prepare("UPDATE accounts SET discordID = discordLinkReq, discordLinkReq = '0' WHERE accountID = :accountID AND discordLinkReq <> 0"); - $query->execute([':accountID' => $accountID]); - $query = $db->prepare("SELECT discordID, userName FROM accounts WHERE accountID = :accountID"); - $query->execute([':accountID' => $accountID]); - $account = $query->fetch(); - $gs->sendDiscordPM($account["discordID"], "Your link request to " . $account["userName"] . " has been accepted!"); - return true; + require __DIR__."/connection.php"; + require_once __DIR__."/exploitPatch.php"; + require_once __DIR__."/mainLib.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + $gs = new mainLib(); + $carray = explode(' ', $command); + $acc = $gs->getAccountName($accountID); + $timestamp = date("c", strtotime("now")); + if(substr($command, 0, 8) == '!discord') { + $webhookLangArray = $gs->webhookStartLanguage($webhookLanguage); + if(substr($command, 9, 6) == "accept") { + $query = $db->prepare("SELECT accountID, discordID FROM accounts WHERE discordLinkReq = :id"); + $query->execute([':id' => ExploitPatch::number($carray[2])]); + $check = $query->fetch(); + if($check["accountID"] != $accountID) return false; + else { + $query = $db->prepare("UPDATE accounts SET discordLinkReq = '0' WHERE accountID = :accountID"); + $query->execute([':accountID' => $accountID]); + $setColor = $successColor; + $setTitle = $gs->webhookLanguage('accountAcceptTitle', $webhookLangArray); + $setDescription = sprintf($gs->webhookLanguage('accountAcceptDesc', $webhookLangArray), '**'.$acc.'**'); + $setThumbnail = $acceptThumbnailURL; + $setFooter = sprintf($gs->webhookLanguage('footer', $webhookLangArray), $gdps); + $embed = $gs->generateEmbedArray( + [$gdps, $authorURL, $authorIconURL], + $setColor, + [$setTitle, $linkTitleURL], + $setDescription, + $setThumbnail, + [], + [$setFooter, $footerIconURL] + ); + $json = json_encode([ + "content" => "", + "tts" => false, + "embeds" => [$embed] + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $gs->sendDiscordPM($check["discordID"], $json, true); + $gs->logAction($accountID, 24, $check["discordID"]); + return true; + } } - if(substr($command, 9, 4) == "deny"){ - $query = $db->prepare("SELECT discordLinkReq, userName FROM accounts WHERE accountID = :accountID"); - $query->execute([':accountID' => $accountID]); - $account = $query->fetch(); - $gs->sendDiscordPM($account["discordLinkReq"], "Your link request to " . $account["userName"] . " has been denied!"); - $query = $db->prepare("UPDATE accounts SET discordLinkReq = '0' WHERE accountID = :accountID"); - $query->execute([':accountID' => $accountID]); - return true; + if(substr($command, 9, 6) == "unlink") { + $query = $db->prepare("SELECT discordID, discordLinkReq FROM accounts WHERE accountID = :id"); + $query->execute([':id' => $accountID]); + $check = $query->fetch(); + if($check["discordID"] == 0 || $check['discordLinkReq'] != 0) return false; + else { + $query = $db->prepare("UPDATE accounts SET discordID = '0' WHERE accountID = :accountID"); + $query->execute([':accountID' => $accountID]); + $setColor = $failColor; + $setTitle = $gs->webhookLanguage('accountUnlinkTitle', $webhookLangArray); + $setDescription = sprintf($gs->webhookLanguage('accountUnlinkDesc', $webhookLangArray), '**'.$acc.'**'); + $setThumbnail = $unlinkThumbnailURL; + $setFooter = sprintf($gs->webhookLanguage('footer', $webhookLangArray), $gdps); + $embed = $gs->generateEmbedArray( + [$gdps, $authorURL, $authorIconURL], + $setColor, + [$setTitle, $linkTitleURL], + $setDescription, + $setThumbnail, + [], + [$setFooter, $footerIconURL] + ); + $json = json_encode([ + "content" => "", + "tts" => false, + "embeds" => [$embed] + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $gs->sendDiscordPM($check["discordID"], $json, true); + $gs->logAction($accountID, 25, $check["discordID"]); + return true; + } } - if(substr($command, 9, 6) == "unlink"){ - $query = $db->prepare("SELECT discordID, userName FROM accounts WHERE accountID = :accountID"); - $query->execute([':accountID' => $accountID]); - $account = $query->fetch(); - $gs->sendDiscordPM($account["discordID"], "Your Discord account has been unlinked from " . $account["userName"] . "!"); - $query = $db->prepare("UPDATE accounts SET discordID = '0' WHERE accountID = :accountID"); - $query->execute([':accountID' => $accountID]); + if(substr($command, 9, 4) == "link") { + $query = $db->prepare("SELECT discordID, discordLinkReq FROM accounts WHERE accountID = :id"); + $query->execute([':id' => $accountID]); + $check = $query->fetch(); + if($check["discordID"] != 0 && $check['discordLinkReq'] == 0) return false; + $query = $db->prepare("SELECT count(*) FROM accounts WHERE discordID = :id AND discordLinkReq = 0"); + $query->execute([':id' => $carray[2]]); + $check = $query->fetchColumn(); + if($check > 0) return false; + $code = rand(1000, 9999); + $emojis = [":zero:", ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:"]; + $acode = str_split($code); + $acode = [$emojis[$acode[0]], $emojis[$acode[1]], $emojis[$acode[2]], $emojis[$acode[3]]]; + $setColor = $pendingColor; + $setTitle = $gs->webhookLanguage('accountLinkTitle', $webhookLangArray); + $setDescription = sprintf($gs->webhookLanguage('accountLinkDesc', $webhookLangArray), '**'.$acc.'**'); + $setThumbnail = $linkThumbnailURL; + $codeFirst = [$gs->webhookLanguage('accountCodeFirst', $webhookLangArray), $acode[0], true]; + $codeSecond = [$gs->webhookLanguage('accountCodeSecond', $webhookLangArray), $acode[1], true]; + $codeThird = [$gs->webhookLanguage('accountCodeThird', $webhookLangArray), $acode[2], true]; + $codeFourth = [$gs->webhookLanguage('accountCodeFourth', $webhookLangArray), $acode[3], true]; + $setFooter = sprintf($gs->webhookLanguage('footer', $webhookLangArray), $gdps); + $embed = $gs->generateEmbedArray( + [$gdps, $authorURL, $authorIconURL], + $setColor, + [$setTitle, $linkTitleURL], + $setDescription, + $setThumbnail, + [$codeFirst, $codeSecond, $codeThird, $codeFourth], + [$setFooter, $footerIconURL] + ); + $json = json_encode([ + "content" => "", + "tts" => false, + "embeds" => [$embed] + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $query = $db->prepare("UPDATE accounts SET discordLinkReq = :id, discordID = :did WHERE accountID = :accountID"); + $query->execute([':accountID' => $accountID, ':id' => $code, ':did' => ExploitPatch::number($carray[2])]); + $gs->sendDiscordPM($carray[2], $json, true); + $gs->logAction($accountID, 26, $check["discordID"]); return true; } } diff --git a/incl/lib/connection.php b/incl/lib/connection.php index 6748d1d53..f0554a44c 100644 --- a/incl/lib/connection.php +++ b/incl/lib/connection.php @@ -1,18 +1,49 @@ true -)); - // set the PDO error mode to exception - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - } -catch(PDOException $e) - { - echo "Connection failed: " . $e->getMessage(); - } +if(!isset($db)) global $db; +if(empty($db)) { + error_reporting(0); + require dirname(__FILE__)."/../../config/connection.php"; + require dirname(__FILE__)."/../../config/misc.php"; + require_once dirname(__FILE__)."/../../config/security.php"; + require_once dirname(__FILE__)."/../../config/dashboard.php"; + require_once dirname(__FILE__)."/ipCheck.php"; + $ic = new ipCheck(); + @header('Content-Type: text/html; charset=utf-8'); + if(!isset($port)) $port = 3306; + try { + $db = new PDO("mysql:host=$servername;port=$port;dbname=$dbname", $username, $password, array(PDO::ATTR_PERSISTENT => true)); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $ic->checkIP(); + $ip = $ic->getYourIP(); + if($minGameVersion != 0 && isset($_POST['gameVersion']) && $_POST['gameVersion'] != 0 && $_POST['gameVersion'] < $minGameVersion && !isset($_SESSION)) exit("-1"); + if($maxGameVersion != 0 && isset($_POST['gameVersion']) && $_POST['gameVersion'] != 0 && $_POST['gameVersion'] > $maxGameVersion && !isset($_SESSION)) exit("-1"); + if($minBinaryVersion != 0 && isset($_POST['binaryVersion']) && $_POST['binaryVersion'] != 0 && $_POST['binaryVersion'] < $minBinaryVersion && !isset($_SESSION)) exit("-1"); + if($maxBinaryVersion != 0 && isset($_POST['binaryVersion']) && $_POST['binaryVersion'] != 0 && $_POST['binaryVersion'] > $maxBinaryVersion && !isset($_SESSION)) exit("-1"); + if(!isset($_SESSION['accountID'])) { + $getExtID = $db->prepare('SELECT extID FROM users WHERE isRegistered = 1 AND IP = :ip LIMIT 1'); + $getExtID->execute([':ip' => $ip]); + $getExtID = $getExtID->fetchColumn(); + } + if(!isset($installed)) global $installed; + if($installed && (!empty($getExtID) || isset($_SESSION['accountID']))) { + $accountIDcheck = $getExtID ?? $_SESSION['accountID']; + $timezone = $db->prepare('SELECT timezone FROM accounts WHERE accountID = :id'); + $timezone->execute([':id' => $accountIDcheck]); + $timezone = $timezone->fetchColumn(); + if(!empty($timezone)) date_default_timezone_set($timezone); + else { + $json = file_get_contents('http://ip-api.com/json/'.$ip); + $ipData = json_decode($json, true); + if($ipData['timezone']) { + $update = $db->prepare('UPDATE accounts SET timezone = :tz WHERE accountID = :id'); + $update->execute([':tz' => $ipData['timezone'], ':id' => $accountIDcheck]); + date_default_timezone_set($ipData['timezone']); + } + } + } + } + catch(PDOException $e) { + echo "Connection failed: " . $e->getMessage(); + } +} ?> \ No newline at end of file diff --git a/incl/lib/cron.php b/incl/lib/cron.php new file mode 100644 index 000000000..e60833adc --- /dev/null +++ b/incl/lib/cron.php @@ -0,0 +1,320 @@ +prepare("SELECT count(*) FROM actions WHERE type = 39 AND timestamp >= :timestamp"); + $check->execute([':timestamp' => time() - 30]); + $check = $check->fetchColumn(); + if($check) return false; + } + $query = $db->prepare("SELECT + 212 + IFNULL(stars.stars, 0) + IFNULL(dailyStars.stars, 0) + IFNULL(eventStars.stars, 0) + IFNULL(gauntletStars.stars, 0) + IFNULL(mappackStars.stars, 0) AS stars, + 10 + IFNULL(coins.coins, 0) + IFNULL(dailyCoins.coins, 0) + IFNULL(eventCoins.coins, 0) + IFNULL(gauntletCoins.coins, 0) AS coins, + 3 + IFNULL(demons.demons, 0) + IFNULL(dailyDemons.demons, 0) + IFNULL(eventDemons.demons, 0) + IFNULL(gauntletDemons.demons, 0) AS demons, + 25 + IFNULL(moons.moons, 0) + IFNULL(dailyMoons.moons, 0) + IFNULL(eventMoons.moons, 0) + IFNULL(gauntletMoons.moons, 0) AS moons + FROM ( + -- Levels table + (SELECT SUM(starStars) AS stars FROM levels WHERE levelLength != 5) stars + JOIN (SELECT SUM(coins) AS coins FROM levels WHERE starCoins > 0) coins + JOIN (SELECT SUM(starDemon) AS demons FROM levels WHERE starStars > 0) demons + JOIN (SELECT SUM(starStars) AS moons FROM levels WHERE levelLength = 5) moons + + -- Daily/Weekly levels + JOIN (SELECT SUM(starStars) as stars FROM dailyfeatures INNER JOIN levels ON levels.levelID = dailyfeatures.levelID WHERE levelLength != 5) dailyStars + JOIN (SELECT SUM(coins) as coins FROM dailyfeatures INNER JOIN levels ON levels.levelID = dailyfeatures.levelID WHERE starCoins > 0) dailyCoins + JOIN (SELECT SUM(starDemon) as demons FROM dailyfeatures INNER JOIN levels ON levels.levelID = dailyfeatures.levelID WHERE starStars > 0) dailyDemons + JOIN (SELECT SUM(starStars) as moons FROM dailyfeatures INNER JOIN levels ON levels.levelID = dailyfeatures.levelID WHERE levelLength = 5) dailyMoons + + -- Event levels + JOIN (SELECT SUM(starStars) as stars FROM events INNER JOIN levels ON levels.levelID = events.levelID WHERE levelLength != 5) eventStars + JOIN (SELECT SUM(coins) as coins FROM events INNER JOIN levels ON levels.levelID = events.levelID WHERE starCoins > 0) eventCoins + JOIN (SELECT SUM(starDemon) as demons FROM events INNER JOIN levels ON levels.levelID = events.levelID WHERE starStars > 0) eventDemons + JOIN (SELECT SUM(starStars) as moons FROM events INNER JOIN levels ON levels.levelID = events.levelID WHERE levelLength = 5) eventMoons + + -- Map Packs + JOIN (SELECT SUM(stars) as stars FROM mappacks) mappackStars + + -- Gauntlets + JOIN (SELECT SUM(starStars) as stars FROM levels INNER JOIN gauntlets ON levels.levelID IN (gauntlets.level1, gauntlets.level2, gauntlets.level3, gauntlets.level4, gauntlets.level5) WHERE levelLength != 5) gauntletStars + JOIN (SELECT SUM(coins) as coins FROM levels INNER JOIN gauntlets ON levels.levelID IN (gauntlets.level1, gauntlets.level2, gauntlets.level3, gauntlets.level4, gauntlets.level5) WHERE starCoins > 0) gauntletCoins + JOIN (SELECT SUM(starDemon) as demons FROM levels INNER JOIN gauntlets ON levels.levelID IN (gauntlets.level1, gauntlets.level2, gauntlets.level3, gauntlets.level4, gauntlets.level5) WHERE starStars > 0) gauntletDemons + JOIN (SELECT SUM(starStars) as moons FROM levels INNER JOIN gauntlets ON levels.levelID IN (gauntlets.level1, gauntlets.level2, gauntlets.level3, gauntlets.level4, gauntlets.level5) WHERE levelLength = 5) gauntletMoons + )"); + $query->execute(); + $levelstuff = $query->fetch(); + $stars = $levelstuff['stars']; + $coins = $levelstuff['coins']; + $demons = $levelstuff['demons']; + $moons = $levelstuff['moons']; + $query = $db->prepare("SELECT userID FROM users WHERE stars > :stars OR demons > :demons OR userCoins > :coins OR moons > :moons OR stars < 0 OR demons < 0 OR coins < 0 OR userCoins < 0 OR diamonds < 0 OR moons < 0"); + $query->execute([':stars' => $stars, ':demons' => $demons, ':coins' => $coins, ':moons' => $moons]); + $query = $query->fetchAll(); + foreach($query AS &$ban) { + $getUser = $db->prepare('SELECT stars, demons, userCoins, moons FROM users WHERE userID = :userID'); + $getUser->execute([':userID' => $ban['userID']]); + $getUser = $getUser->fetch(); + $maxText = 'MAX: ⭐'.$stars.' • 🌙'.$moons.' • 👿'.$demons.' • 🪙'.$coins.' | USER: ⭐'.$getUser['stars'].' • 🌙'.$getUser['moons'].' • 👿'.$getUser['demons'].' • 🪙'.$getUser['userCoins']; + $gs->banPerson(0, $ban['userID'], $maxText, 0, 1, 2147483647); + } + $gs->logAction($accountID, 39, $stars, $coins, $demons, $moons, count($query)); + return true; + } + public static function updateCreatorPoints($accountID, $checkForTime) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + $gs = new mainLib(); + if($checkForTime) { + $check = $db->prepare("SELECT count(*) FROM actions WHERE type = 40 AND timestamp >= :timestamp"); + $check->execute([':timestamp' => time() - 30]); + $check = $check->fetchColumn(); + if($check) return false; + } + $people = []; + /* + Creator Points for rated levels + */ + $query = $db->prepare("UPDATE users + LEFT JOIN + ( + SELECT usersTable.userID, (IFNULL(starredTable.starred, 0) + IFNULL(featuredTable.featured, 0) + (IFNULL(epicTable.epic,0))) as CP FROM ( + SELECT userID FROM users + ) AS usersTable + LEFT JOIN + ( + SELECT count(*) as starred, userID FROM levels WHERE starStars != 0 AND isCPShared = 0 GROUP BY(userID) + ) AS starredTable ON usersTable.userID = starredTable.userID + LEFT JOIN + ( + SELECT count(*) as featured, userID FROM levels WHERE starFeatured != 0 AND isCPShared = 0 GROUP BY(userID) + ) AS featuredTable ON usersTable.userID = featuredTable.userID + LEFT JOIN + ( + SELECT starEpic as epic, userID FROM levels WHERE starEpic != 0 AND isCPShared = 0 GROUP BY(userID) + ) AS epicTable ON usersTable.userID = epicTable.userID + ) calculated + ON users.userID = calculated.userID + SET users.creatorPoints = IFNULL(calculated.CP, 0)"); + $query->execute(); + /* + Creator Points sharing + */ + $query = $db->prepare("SELECT levelID, userID, starStars, starFeatured, starEpic FROM levels WHERE isCPShared != 0"); + $query->execute(); + $result = $query->fetchAll(); + foreach($result AS &$level) { + $deservedcp = 0; + if($level["starStars"] != 0) $deservedcp++; + if($level["starFeatured"] != 0) $deservedcp++; + if($level["starEpic"] != 0) $deservedcp += $level["starEpic"]; + $query = $db->prepare("SELECT userID FROM cpshares WHERE levelID = :levelID"); + $query->execute([':levelID' => $level["levelID"]]); + $sharecount = $query->rowCount() + 1; + $addcp = $deservedcp / $sharecount; + $shares = $query->fetchAll(); + foreach($shares as &$share) $people[$share["userID"]] += $addcp; + $people[$level["userID"]] += $addcp; + } + /* + Creator Points for levels in Map Packs + */ + $query = $db->prepare("SELECT levels FROM mappacks"); + $query->execute(); + $result = $query->fetchAll(); + foreach($result AS &$pack) { + $query = $db->prepare("SELECT userID FROM levels WHERE levelID IN (".$pack['levels'].")"); + $query->execute(); + $levels = $query->fetch(); + foreach($levels AS &$level) $people[$level["userID"]] += 1; + } + /* + Creator Points for levels in Gauntlets + */ + $query = $db->prepare("SELECT level1, level2, level3, level4, level5 FROM gauntlets"); + $query->execute(); + $result = $query->fetchAll(); + foreach($result AS &$gauntlet) { + for($x = 1; $x < 6; $x++) { + $query = $db->prepare("SELECT userID FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $gauntlet["level".$x]]); + $result = $query->fetch(); + if($result) $people[$result["userID"]] += 1; + } + } + /* + Creator Points for Daily/Weekly levels + */ + $query = $db->prepare("SELECT levelID FROM dailyfeatures WHERE timestamp < :time"); + $query->execute([':time' => time()]); + $result = $query->fetchAll(); + foreach($result AS &$daily) { + $query = $db->prepare("SELECT userID, levelID FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $daily["levelID"]]); + $result = $query->fetch(); + if($result) $people[$result["userID"]] += 1; + } + /* + Creator Points for Event levels + */ + $query = $db->prepare("SELECT levelID FROM events WHERE timestamp < :time"); + $query->execute([':time' => time()]); + $result = $query->fetchAll(); + foreach($result AS &$event) { + $query = $db->prepare("SELECT userID, levelID FROM levels WHERE levelID = :levelID"); + $query->execute([':levelID' => $event["levelID"]]); + $result = $query->fetch(); + if($result) $people[$result["userID"]] += 1; + } + /* + Done + */ + foreach($people AS $user => $cp) { + $query4 = $db->prepare("UPDATE users SET creatorPoints = (creatorpoints + :creatorpoints) WHERE userID = :userID"); + $query4->execute([':userID' => $user, ':creatorpoints' => $cp]); + } + $gs->logAction($accountID, 40); + return true; + } + public static function fixUsernames($accountID, $checkForTime) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + $gs = new mainLib(); + if($checkForTime) { + $check = $db->prepare("SELECT count(*) FROM actions WHERE type = 41 AND timestamp >= :timestamp"); + $check->execute([':timestamp' => time() - 30]); + $check = $check->fetchColumn(); + if($check) return false; + } + $query = $db->prepare("UPDATE users + INNER JOIN accounts ON accounts.accountID = users.extID + SET users.userName = accounts.userName + WHERE users.extID REGEXP '^-?[0-9]+$' + AND LENGTH(accounts.userName) <= 69"); + $query->execute(); + $gs->logAction($accountID, 41); + return true; + } + public static function updateFriendsCount($accountID, $checkForTime) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + $gs = new mainLib(); + if($checkForTime) { + $check = $db->prepare("SELECT count(*) FROM actions WHERE type = 42 AND timestamp >= :timestamp"); + $check->execute([':timestamp' => time() - 30]); + $check = $check->fetchColumn(); + if($check) return false; + } + $query = $db->prepare("UPDATE accounts + LEFT JOIN + ( + SELECT a.person, (IFNULL(a.friends, 0) + IFNULL(b.friends, 0)) AS friends FROM ( + SELECT count(*) as friends, person1 AS person FROM friendships GROUP BY(person1) + ) AS a + JOIN + ( + SELECT count(*) as friends, person2 AS person FROM friendships GROUP BY(person2) + ) AS b ON a.person = b.person + ) calculated + ON accounts.accountID = calculated.person + SET accounts.friendsCount = IFNULL(calculated.friends, 0)"); + $query->execute(); + $gs->logAction($accountID, 42); + return true; + } + public static function miscFixes($accountID, $checkForTime) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + $gs = new mainLib(); + if($checkForTime) { + $check = $db->prepare("SELECT count(*) FROM actions WHERE type = 43 AND timestamp >= :timestamp"); + $check->execute([':timestamp' => time() - 30]); + $check = $check->fetchColumn(); + if($check) return false; + } + /* + Unbanning everyone who has expired ban + */ + $bans = $db->prepare('UPDATE bans SET isActive = 0 WHERE expires < :time'); + $bans->execute([':time' => time()]); + /* + Unbanning IPs + */ + $getIPBans = $db->prepare("SELECT person FROM bans WHERE personType = 2 AND banType = 4 AND isActive = 0"); + $getIPBans->execute(); + $getIPBans = $getIPBans->fetchAll(); + $IPBans = []; + foreach($getIPBans AS &$ban) { + $IPBans[] = $gs->IPForBan($ban['person'], true); + } + $bannedIPsString = implode("|", $IPBans); + $unbanIPs = $db->prepare('DELETE FROM bannedips WHERE IP REGEXP "'.$bannedIPsString.'"'); + $unbanIPs->execute(); + $gs->logAction($accountID, 43); + return true; + } + public static function updateSongsUsage($accountID, $checkForTime) { + require __DIR__."/connection.php"; + require_once __DIR__."/mainLib.php"; + $gs = new mainLib(); + if($checkForTime) { + $check = $db->prepare("SELECT count(*) FROM actions WHERE type = 44 AND timestamp >= :timestamp"); + $check->execute([':timestamp' => time() - 30]); + $check = $check->fetchColumn(); + if($check) return false; + } + $query = $db->prepare("SELECT songID, songIDs, sfxIDs FROM levels"); + $query->execute(); + $levels = $query->fetchAll(); + $songsUsage = $sfxsUsage = []; + /* + Count songs and SFXs usage + */ + $songsLibrary = json_decode(file_get_contents(__DIR__.'/../../music/ids.json'), true) ?: []; + $sfxsLibrary = json_decode(file_get_contents(__DIR__.'/../../sfx/ids.json'), true) ?: []; + foreach($levels AS &$level) { + $mainSong = $gs->getSongInfo($level['songID'], "*", $songsLibrary); + if($mainSong && $mainSong['isLocalSong']) $songsUsage[$mainSong['ID']]++; + $extraSongs = explode(',', $level['songIDs']); + foreach($extraSongs AS &$song) { + if(empty($song)) continue; + $extraSong = $gs->getSongInfo($song, "*", $songsLibrary); + if($extraSong && $extraSong['isLocalSong']) $songsUsage[$extraSong['ID']]++; + } + $extraSFXs = explode(',', $level['sfxIDs']); + foreach($extraSFXs AS &$sfx) { + if(empty($sfx)) continue; + $extraSFX = $gs->getLibrarySongInfo($sfx, 'sfx', $sfxsLibrary); + if($extraSFX && $extraSFX['isLocalSFX']) $sfxsUsage[$extraSFX['originalID']]++; + } + } + /* + Add this info to SQL + */ + $db->query("UPDATE songs SET levelsCount = 0"); + $db->query("UPDATE sfxs SET levelsCount = 0"); + foreach($songsUsage AS $song => $usage) { + $addInfo = $db->prepare("UPDATE songs SET levelsCount = :usage WHERE ID = :songID"); + $addInfo->execute([':usage' => $usage, ':songID' => $song]); + } + foreach($sfxsUsage AS $sfx => $usage) { + $addInfo = $db->prepare("UPDATE sfxs SET levelsCount = :usage WHERE ID = :sfxID"); + $addInfo->execute([':usage' => $usage, ':sfxID' => $sfx]); + } + $gs->logAction($accountID, 44, count($songsUsage), count($sfxsUsage)); + return true; + } + public static function doEverything($accountID, $checkForTime) { + if( + !self::autoban($accountID, $checkForTime) || + !self::updateCreatorPoints($accountID, $checkForTime) || + !self::fixUsernames($accountID, $checkForTime) || + !self::updateFriendsCount($accountID, $checkForTime) || + !self::miscFixes($accountID, $checkForTime) || + !self::updateSongsUsage($accountID, $checkForTime) + ) return false; + return true; + } +} +?> diff --git a/incl/lib/exploitPatch.php b/incl/lib/exploitPatch.php index 3b7f45441..09a1460a8 100644 --- a/incl/lib/exploitPatch.php +++ b/incl/lib/exploitPatch.php @@ -1,17 +1,49 @@ '-', '/' => '_']); + } + public static function prepare_for_checking($string) { + $string = self::charclean(strtolower(self::translit($string))); + $lettersToReplace = ['0', '1', '3', '@', 'l', '4', '7', '6', '5']; + $replaceLetters = ['o', 'i', 'e', 'a', 'i', 'a', 't', 'g', 's']; + return str_replace(' ', '', mb_ereg_replace('([А-Яа-яA-Za-z0-9 ])\1+', '\1', str_replace($lettersToReplace, $replaceLetters, $string))); + } + public static function translit($string) { + require_once __DIR__.'/../../config/translit/Translit.php'; + return \ashtokalo\translit\Translit::object()->convert(str_replace(['Ё', 'ё'], ['YO', 'yo'], $string), 'ru,uk,mk,be,bg,kk,ka,hy,el,cyrillic,latin'); + } + public static function gd_escape($string) { + return strtr(self::rucharclean($string), ':#', ';N'); + } } } -?> +?> \ No newline at end of file diff --git a/incl/lib/generateHash.php b/incl/lib/generateHash.php index 2f419ef69..9b51fca92 100644 --- a/incl/lib/generateHash.php +++ b/incl/lib/generateHash.php @@ -1,13 +1,7 @@ prepare("SELECT ID,stars,coins FROM mappacks WHERE ID = :id"); - $query->execute([':id' => $id]); - $result2 = $query->fetchAll(); - $result = $result2[0]; - $idstring = strval($result["ID"]); - $hash = $hash . $idstring[0].$idstring[strlen($idstring)-1].$result["stars"].$result["coins"]; + foreach($lvlsmultistring as $result) { + $result["ID"] = strval($result["ID"]); + $hash = $hash . $result["ID"][0].$result["ID"][strlen($result["ID"])-1].$result["stars"].$result["coins"]; } return sha1($hash . "xI25fpAapCQg"); } @@ -61,4 +49,4 @@ public static function genSeed2noXor($levelstring) { return $hash; } } -?> +?> \ No newline at end of file diff --git a/incl/lib/generatePass.php b/incl/lib/generatePass.php index eb4ffa7b0..b3f93df05 100644 --- a/incl/lib/generatePass.php +++ b/incl/lib/generatePass.php @@ -1,8 +1,6 @@ prepare("UPDATE accounts SET gjp2 = :gjp2 WHERE accountID = :id"); $query->execute(["gjp2" => self::GJP2hash($pass), ":id" => $accid]); } public static function attemptsFromIP() { - include dirname(__FILE__)."/connection.php"; + require __DIR__."/connection.php"; $gs = new mainLib(); $ip = $gs->getIP(); $newtime = time() - (60*60); - $query6 = $db->prepare("SELECT count(*) FROM actions WHERE type = '6' AND timestamp > :time AND value2 = :ip"); + $query6 = $db->prepare("SELECT count(*) FROM actions WHERE type = '6' AND timestamp > :time AND IP = :ip"); $query6->execute([':time' => $newtime, ':ip' => $ip]); return $query6->fetchColumn(); } @@ -32,100 +29,130 @@ public static function tooManyAttemptsFromIP() { return self::attemptsFromIP() > 7; } - public static function logInvalidAttemptFromIP($accid) { - include dirname(__FILE__)."/connection.php"; + public static function logInvalidAttemptFromIP($accountID) { + require __DIR__."/connection.php"; $gs = new mainLib(); - $ip = $gs->getIP(); - $query6 = $db->prepare("INSERT INTO actions (type, value, timestamp, value2) VALUES - ('6',:accid,:time,:ip)"); - $query6->execute([':accid' => $accid, ':time' => time(), ':ip' => $ip]); + $gs->logAction($accountID, 6, $accountID); } public static function assignModIPs($accountID, $ip) { //this system is most likely going to be removed altogether soon - include dirname(__FILE__)."/connection.php"; + require __DIR__."/connection.php"; $gs = new mainLib(); $modipCategory = $gs->getMaxValuePermission($accountID, "modipCategory"); - if($modipCategory > 0){ //modIPs + if($modipCategory > 0) { //modIPs $query4 = $db->prepare("SELECT count(*) FROM modips WHERE accountID = :id"); $query4->execute([':id' => $accountID]); - if ($query4->fetchColumn() > 0) { - $query6 = $db->prepare("UPDATE modips SET IP=:hostname, modipCategory=:modipCategory WHERE accountID=:id"); - }else{ - $query6 = $db->prepare("INSERT INTO modips (IP, accountID, isMod, modipCategory) VALUES (:hostname,:id,'1',:modipCategory)"); - } + if($query4->fetchColumn() > 0) $query6 = $db->prepare("UPDATE modips SET IP = :hostname, modipCategory = :modipCategory WHERE accountID = :id"); + else $query6 = $db->prepare("INSERT INTO modips (IP, accountID, isMod, modipCategory) VALUES (:hostname, :id, '1', :modipCategory)"); $query6->execute([':hostname' => $ip, ':id' => $accountID, ':modipCategory' => $modipCategory]); } } public static function isGJP2Valid($accid, $gjp2) { - include dirname(__FILE__)."/connection.php"; + require __DIR__."/connection.php"; $gs = new mainLib(); - - if(self::tooManyAttemptsFromIP()) return -1; - - $userInfo = $db->prepare("SELECT gjp2, isActive FROM accounts WHERE accountID = :accid"); + if(self::tooManyAttemptsFromIP()) return '-1'; + $userInfo = $db->prepare("SELECT userName, gjp2, isActive FROM accounts WHERE accountID = :accid"); $userInfo->execute([':accid' => $accid]); - if($userInfo->rowCount() == 0) return 0; - $userInfo = $userInfo->fetch(); - if(!($userInfo['gjp2'])) return -2; - + if(!$userInfo) { + self::logInvalidAttemptFromIP(0); + return 0; + } + if(!$userInfo['gjp2']) return '-2'; if(password_verify($gjp2, $userInfo['gjp2'])) { + $checkBan = $gs->getPersonBan($accid, $gs->getUserID($accid, $userInfo['userName']), 4); + if($checkBan) return '-1'; self::assignModIPs($accid, $gs->getIP()); - return $userInfo['isActive'] ? 1 : -2; + if($userInfo['isActive']) { + self::updateLastPlayed($accid); + return 1; + } + return '-2'; } else { self::logInvalidAttemptFromIP($accid); return 0; } - } public static function isGJP2ValidUsrname($userName, $gjp2) { - include dirname(__FILE__)."/connection.php"; + require __DIR__."/connection.php"; $query = $db->prepare("SELECT accountID FROM accounts WHERE userName LIKE :userName"); $query->execute([':userName' => $userName]); - if($query->rowCount() == 0){ - return 0; - } + if($query->rowCount() == 0) return 0; $result = $query->fetch(); $accID = $result["accountID"]; return self::isGJP2Valid($accID, $gjp2); - } public static function isValid($accid, $pass) { - include dirname(__FILE__)."/connection.php"; + require __DIR__."/connection.php"; $gs = new mainLib(); - - if(self::tooManyAttemptsFromIP()) return -1; - - $query = $db->prepare("SELECT accountID, salt, password, isActive, gjp2 FROM accounts WHERE accountID = :accid"); + if(self::tooManyAttemptsFromIP()) return '-1'; + $query = $db->prepare("SELECT userName, accountID, salt, password, isActive, gjp2 FROM accounts WHERE accountID = :accid"); $query->execute([':accid' => $accid]); - if($query->rowCount() == 0) return 0; - + if($query->rowCount() == 0) { + self::logInvalidAttemptFromIP(0); + return 0; + } $result = $query->fetch(); - if(password_verify($pass, $result["password"])){ + if(password_verify($pass, $result["password"])) { if(!$result["gjp2"]) self::assignGJP2($accid, $pass); + $checkBan = $gs->getPersonBan($accid, $gs->getUserID($accid, $result['userName']), 4); + if($checkBan) { + self::logInvalidAttemptFromIP($accid); + return -1; + } self::assignModIPs($accid, $gs->getIP()); - return $result['isActive'] ? 1 : -2; + if($result['isActive']) { + self::updateLastPlayed($accid); + return 1; + } + return '-2'; } else { // Code to validate password hashes created prior to March 2017 has been removed. self::logInvalidAttemptFromIP($accid); return 0; } - } + public static function isValidUsrname($userName, $pass){ - include dirname(__FILE__)."/connection.php"; + require __DIR__."/connection.php"; $query = $db->prepare("SELECT accountID FROM accounts WHERE userName LIKE :userName"); $query->execute([':userName' => $userName]); - if($query->rowCount() == 0){ - return 0; - } - $result = $query->fetch(); - $accID = $result["accountID"]; + if($query->rowCount() == 0) return 0; + $accID = $query->fetchColumn(); return self::isValid($accID, $pass); } + + public static function isValidToken($auth) { + require __DIR__."/connection.php"; + $gs = new mainLib(); + if(self::tooManyAttemptsFromIP() || empty(trim($auth))) return '-3'; + $query = $db->prepare("SELECT userName, accountID, isActive FROM accounts WHERE auth = :id"); + $query->execute([':id' => $auth]); + $fetch = $query->fetch(); + if(!$fetch) { + self::logInvalidAttemptFromIP(0); + return '-4'; + } else { + $userID = $gs->getUserID($fetch['accountID'], $fetch['userName']); + $checkBan = $gs->getPersonBan($fetch['accountID'], $userID, 4); + if($checkBan) { + self::logInvalidAttemptFromIP($fetch['accountID']); + return '-1'; + } + self::updateLastPlayed($fetch['accountID']); + if(!$fetch['isActive']) return '-2'; + return ['accountID' => $fetch['accountID'], 'userID' => $userID, 'userName' => $fetch['userName'], 'color' => $gs->getAccountCommentColor($fetch["accountID"])]; + } + } + + public static function updateLastPlayed($accountID) { + require __DIR__."/connection.php"; + $update = $db->prepare("UPDATE users SET lastPlayed = :time WHERE extID = :accountID"); + $update->execute([':accountID' => $accountID, ':time' => time()]); + } } ?> \ No newline at end of file diff --git a/incl/lib/ipCheck.php b/incl/lib/ipCheck.php new file mode 100644 index 000000000..7309f70a9 --- /dev/null +++ b/incl/lib/ipCheck.php @@ -0,0 +1,142 @@ +ipv4inrange($ip, $cf_ip)) { + return true; + } + } + foreach ($cf_ipv6s as $cf_ip) { + if ($this->ipv6_in_range($ip, $cf_ip)) { + return true; + } + } + return false; + } + public function getYourIP() { + if (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && $this->isCloudFlareIP($_SERVER['REMOTE_ADDR'])) //CLOUDFLARE REVERSE PROXY SUPPORT + return $_SERVER['HTTP_CF_CONNECTING_IP']; + if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $this->ipv4inrange($_SERVER['REMOTE_ADDR'], '127.0.0.0/8')) //LOCALHOST REVERSE PROXY SUPPORT (7m.pl) + return $_SERVER['HTTP_X_FORWARDED_FOR']; + if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['REMOTE_ADDR'] == "10.0.1.10") // 141412 PROXY SUPPORT FUCK YOU HESTIA + return explode(",", $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; // fuck my life + return $_SERVER['REMOTE_ADDR']; + } + public function checkProxy() { + require __DIR__."/../../config/security.php"; + if(!isset($blockFreeProxies)) global $blockFreeProxies; + if(!isset($proxies)) global $proxies; + if(!$blockFreeProxies) return; + $fileExists = file_exists(__DIR__ .'/../../config/proxies.txt'); + $lastUpdate = $fileExists ? filemtime(__DIR__ .'/../../config/proxies.txt') : 0; + $checkTime = time() - 3600; + $allProxies = ''; + if($checkTime > $lastUpdate) { + foreach($proxies AS $link) { + $ch = curl_init($link); + if($proxytype > 0) { + curl_setopt($ch, CURLOPT_PROXY, $host); + if($proxytype == 2) curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + if(!empty($auth)) curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth); + } + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + curl_setopt($ch, CURLOPT_USERAGENT, ""); + $IPs = curl_exec($ch); + $proxy = preg_split('/\r\n|\r|\n/', $IPs); + foreach($proxy AS $ip) $allProxies .= explode(':', $ip)[0].PHP_EOL; + } + file_put_contents(__DIR__ .'/../../config/proxies.txt', $allProxies); + } + if(empty($allProxies)) $allProxies = file(__DIR__ .'/../../config/proxies.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + else $allProxies = explode(PHP_EOL, $allProxies); + if(in_array($this->getYourIP(), $allProxies)) { + http_response_code(404); + exit; + } + } + public function checkVPN() { + require __DIR__."/../../config/security.php"; + require __DIR__."/../../config/proxy.php"; + if(!isset($blockCommonVPNs)) global $blockCommonVPNs; + if(!isset($vpns)) global $vpns; + if(!$blockCommonVPNs) return; + $fileExists = file_exists(__DIR__ .'/../../config/vpns.txt'); + $lastUpdate = $fileExists ? filemtime(__DIR__ .'/../../config/vpns.txt') : 0; + $checkTime = time() - 3600; + $allVPNs = ''; + if($checkTime > $lastUpdate) { + foreach($vpns AS $link) { + $ch = curl_init($link); + if($proxytype > 0) { + curl_setopt($ch, CURLOPT_PROXY, $host); + if($proxytype == 2) curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + if(!empty($auth)) curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth); + } + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + curl_setopt($ch, CURLOPT_USERAGENT, ""); + $IPs = curl_exec($ch); + $allVPNs .= $IPs.PHP_EOL; + } + file_put_contents(__DIR__ .'/../../config/vpns.txt', $allVPNs); + } + if(empty($allVPNs)) $allVPNs = file(__DIR__ .'/../../config/vpns.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + else $allVPNs = explode(PHP_EOL, $allVPNs); + foreach($allVPNs AS &$vpnCheck) { + if($this->ipv4inrange($this->getYourIP(), $vpnCheck)) { + http_response_code(404); + exit; + } + } + } + public function checkIP() { + global $db; + $this->checkProxy(); + $this->checkVPN(); + $banip = $db->prepare("SELECT count(*) FROM bannedips WHERE IP = :ip"); + $banip->execute([':ip' => $this->getYourIP()]); + $banip = $banip->fetchColumn(); + if($banip > 0) { + http_response_code(404); + exit; + } + } +} +?> diff --git a/incl/lib/ip_in_range.php b/incl/lib/ip_in_range.php index 1a128d1aa..440ea29d7 100644 --- a/incl/lib/ip_in_range.php +++ b/incl/lib/ip_in_range.php @@ -34,16 +34,6 @@ */ class ipInRange { - - - // decbin32 - // In order to simplify working with IP addresses (in binary) and their - // netmasks, it is easier to ensure that the binary strings are padded - // with zeros out to 32 characters - IP addresses are 32 bit numbers - public static function decbin32 ($dec) { - return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT); - } - // ipv4_in_range // This function takes 2 arguments, an IP address and a "range" in several // different formats. @@ -156,7 +146,7 @@ public static function get_ipv6_full($ip) // Rebuild the final long form IPV6 address $final_ip = implode(":", $main_ip_pieces); - return ip2long6($final_ip); + return self::ip2long6($final_ip); } @@ -207,8 +197,8 @@ public static function ipv6_in_range($ip, $range_ip) } // Rebuild the final long form IPV6 address - $first = ip2long6(implode(":", $first)); - $last = ip2long6(implode(":", $last)); + $first = self::ip2long6(implode(":", $first)); + $last = self::ip2long6(implode(":", $last)); $in_range = ($ip >= $first && $ip <= $last); return $in_range; diff --git a/incl/lib/mainLib.php b/incl/lib/mainLib.php index 817c2ac3c..1ca2c44a3 100644 --- a/incl/lib/mainLib.php +++ b/incl/lib/mainLib.php @@ -1,8 +1,9 @@ = count($songs)) - return "Unknown by DJVI"; + "Monster Dance Off by F-777", + "Press Start by MDK", + "Nock Em by Bossfight", + "Power Trip by Boom Kitty" + ]; + if($id === -1) return "Practice: Stay Inside Me by OcularNebula"; + if($id < 0 || $id >= count($songs)) return "Unknown by DJVI"; return $songs[$id]; } - public function getDifficulty($diff,$auto,$demon) { - if($auto != 0){ - return "Auto"; - }else if($demon != 0){ - return "Demon"; - }else{ - switch($diff){ + public function getDifficulty($diff, $auto, $demon, $demonDiff = 1) { + if($auto != 0) return "Auto"; + if($demon != 0) { + switch($demonDiff) { + case 0: + return 'Hard Demon'; + break; + case 3: + return 'Easy Demon'; + break; + case 4: + return 'Medium Demon'; + break; + case 5: + return 'Insane Demon'; + break; + case 6: + return 'Extreme Demon'; + break; + default: + return 'Demon'; + break; + } + } else { + switch($diff) { case 0: return "N/A"; break; @@ -110,15 +135,30 @@ public function getDiffFromStars($stars) { $demon = 1; break; default: - $diffname = "N/A: " . $stars; + $diffname = "N/A"; $diff = 0; $demon = 0; break; } return array('diff' => $diff, 'auto' => $auto, 'demon' => $demon, 'name' => $diffname); } + public function getLevelDiff($levelID) { + require __DIR__ . "/connection.php"; + $diff = $db->prepare("SELECT starDifficulty FROM levels WHERE levelID = :id"); + $diff->execute([':id' => $levelID]); + $diff = $diff->fetch(); + $diff = $this->getDifficulty($diff["starDifficulty"], 0, 0); + return $diff; + } + public function getLevelStars($levelID) { + require __DIR__ . "/connection.php"; + $diff = $db->prepare("SELECT starStars FROM levels WHERE levelID = :id"); + $diff->execute([':id' => $levelID]); + $diff = $diff->fetch(); + return $diff["starStars"]; + } public function getLength($length) { - switch($length){ + switch($length) { case 0: return "Tiny"; break; @@ -143,15 +183,20 @@ public function getLength($length) { } } public function getGameVersion($version) { - if($version > 17){ - return $version / 10; - }elseif($version == 11){ - return "1.8"; - }elseif($version == 10){ - return "1.7"; - }else{ - $version--; - return "1.$version"; + switch(true) { + case $version > 17: + return $version / 10; + break; + case $version == 11: + return "1.8"; + break; + case $version == 10: + return "1.0"; + break; + default: + $version--; + return "1.$version"; + break; } } public function getDemonDiff($dmn) { @@ -207,87 +252,54 @@ public function getDiffFromName($name) { } return array($starDifficulty, $starDemon, $starAuto); } - public function getGauntletName($id){ - $gauntlets = ["Unknown", "Fire", "Ice", "Poison", "Shadow", "Lava", "Bonus", "Chaos", "Demon", "Time", "Crystal", "Magic", "Spike", "Monster", "Doom", "Death", 'Forest', 'Rune', 'Force', 'Spooky', 'Dragon', 'Water', 'Haunted', 'Acid', 'Witch', 'Power', 'Potion', 'Snake', 'Toxic', 'Halloween', 'Treasure', 'Ghost', 'Spider', 'Gem', 'Inferno', 'Portal', 'Strange', 'Fantasy', 'Christmas', 'Surprise', 'Mystery', 'Cursed', 'Cyborg', 'Castle', 'Grave', 'Temple', 'World', 'Galaxy', 'Universe', 'Discord', 'Split']; + public function getGauntletName($id, $wholeArray = false){ + $gauntlets = ["Unknown", "Fire", "Ice", "Poison", "Shadow", "Lava", "Bonus", "Chaos", "Demon", "Time", "Crystal", "Magic", "Spike", "Monster", "Doom", "Death", 'Forest', 'Rune', 'Force', 'Spooky', 'Dragon', 'Water', 'Haunted', 'Acid', 'Witch', 'Power', 'Potion', 'Snake', 'Toxic', 'Halloween', 'Treasure', 'Ghost', 'Spider', 'Gem', 'Inferno', 'Portal', 'Strange', 'Fantasy', 'Christmas', 'Surprise', 'Mystery', 'Cursed', 'Cyborg', 'Castle', 'Grave', 'Temple', 'World', 'Galaxy', 'Universe', 'Discord', 'Split', 'NCS I', 'NCS II', 'Space', 'Cosmos']; + if($wholeArray) return $gauntlets; if($id < 0 || $id >= count($gauntlets)) return $gauntlets[0]; return $gauntlets[$id]; } - - function makeTime($delta) - { - if ($delta < 31536000) - { - if ($delta < 2628000) - { - if ($delta < 604800) - { - if ($delta < 86400) - { - if ($delta < 3600) - { - if ($delta < 60) - { - return $delta." second".($delta == 1 ? "" : "s"); - } - else - { - $rounded = floor($delta / 60); - return $rounded." minute".($rounded == 1 ? "" : "s"); - } - } - else - { - $rounded = floor($delta / 3600); - return $rounded." hour".($rounded == 1 ? "" : "s"); - } - } - else - { - $rounded = floor($delta / 86400); - return $rounded." day".($rounded == 1 ? "" : "s"); - } - } - else - { - $rounded = floor($delta / 604800); - return $rounded." week".($rounded == 1 ? "" : "s"); + public function getGauntletCount() { + return count($this->getGauntletName(0, true))-1; + } + public function makeTime($time) { + require __DIR__ . "/../../config/dashboard.php"; + if(!isset($timeType)) $timeType = 0; + switch($timeType) { + case 1: + if(date("d.m.Y", $time) == date("d.m.Y", time())) return date("G;i", $time); + elseif(date("Y", $time) == date("Y", time())) return date("d.m", $time); + else return date("d.m.Y", $time); + break; + case 2: + // taken from https://stackoverflow.com/a/36297417 + $time = time() - $time; + $time = ($time < 1) ? 1 : $time; + $tokens = array (31536000 => 'year', 2592000 => 'month', 604800 => 'week', 86400 => 'day', 3600 => 'hour', 60 => 'minute', 1 => 'second'); + foreach($tokens as $unit => $text) { + if($time < $unit) continue; + $numberOfUnits = floor($time / $unit); + return $numberOfUnits . ' ' . $text . (($numberOfUnits > 1) ? 's' : ''); } - } - else - { - $rounded = floor($delta / 2628000); - return $rounded." month".($rounded == 1 ? "" : "s"); - } - } - else - { - $rounded = floor($delta / 31536000); - return $rounded." year".($rounded == 1 ? "" : "s"); + break; + default: + return date("d/m/Y G.i", $time); + break; } } - public function getIDFromPost(){ - include __DIR__ . "/../../config/security.php"; - include_once __DIR__ . "/exploitPatch.php"; - include_once __DIR__ . "/GJPCheck.php"; - - if(!empty($_POST["udid"]) AND $_POST['gameVersion'] < 20 AND $unregisteredSubmissions) - { + public function getIDFromPost() { + require __DIR__ . "/../../config/security.php"; + require_once __DIR__ . "/exploitPatch.php"; + require_once __DIR__ . "/GJPCheck.php"; + if(!empty($_POST["udid"]) && $unregisteredSubmissions) { $id = ExploitPatch::remove($_POST["udid"]); if(is_numeric($id)) exit("-1"); - } - elseif(!empty($_POST["accountID"]) AND $_POST["accountID"]!="0") - { - $id = GJPCheck::getAccountIDOrDie(); - } - else - { - exit("-1"); - } + } elseif(!empty($_POST["accountID"]) AND $_POST["accountID"] !="0") $id = GJPCheck::getAccountIDOrDie(); + else exit("-1"); return $id; } public function getUserID($extID, $userName = "Undefined") { - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; if(is_numeric($extID)){ $register = 1; }else{ @@ -298,29 +310,29 @@ public function getUserID($extID, $userName = "Undefined") { if ($query->rowCount() > 0) { $userID = $query->fetchColumn(); } else { - $query = $db->prepare("INSERT INTO users (isRegistered, extID, userName, lastPlayed) - VALUES (:register, :id, :userName, :uploadDate)"); - - $query->execute([':id' => $extID, ':register' => $register, ':userName' => $userName, ':uploadDate' => time()]); + $query = $db->prepare("INSERT INTO users (isRegistered, extID, userName, lastPlayed, IP) + VALUES (:register, :id, :userName, :uploadDate, :IP)"); + $ip = $this->getIP(); + $query->execute([':id' => $extID, ':register' => $register, ':userName' => $userName, ':uploadDate' => time(), ':IP' => $ip]); $userID = $db->lastInsertId(); } return $userID; } public function getAccountName($accountID) { - if(!is_numeric($accountID)) return false; - - include __DIR__ . "/connection.php"; - $query = $db->prepare("SELECT userName FROM accounts WHERE accountID = :id"); - $query->execute([':id' => $accountID]); - if ($query->rowCount() > 0) { + require __DIR__ . "/connection.php"; + if(is_numeric($accountID)) { + $query = $db->prepare("SELECT userName FROM accounts WHERE accountID = :id"); + $query->execute([':id' => $accountID]); $userName = $query->fetchColumn(); } else { - $userName = false; + $query = $db->prepare("SELECT userName FROM users WHERE extID = :id"); + $query->execute([':id' => $accountID]); + $userName = $query->fetchColumn(); } return $userName; } public function getUserName($userID) { - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare("SELECT userName FROM users WHERE userID = :id"); $query->execute([':id' => $userID]); if ($query->rowCount() > 0) { @@ -331,7 +343,7 @@ public function getUserName($userID) { return $userName; } public function getAccountIDFromName($userName) { - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare("SELECT accountID FROM accounts WHERE userName LIKE :usr"); $query->execute([':usr' => $userName]); if ($query->rowCount() > 0) { @@ -342,7 +354,7 @@ public function getAccountIDFromName($userName) { return $accountID; } public function getExtID($userID) { - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare("SELECT extID FROM users WHERE userID = :id"); $query->execute([':id' => $userID]); if ($query->rowCount() > 0) { @@ -352,33 +364,116 @@ public function getExtID($userID) { } } public function getUserString($userdata) { - include __DIR__ . "/connection.php"; - /*$query = $db->prepare("SELECT userName, extID FROM users WHERE userID = :id"); - $query->execute([':id' => $userID]); - $userdata = $query->fetch();*/ + $userdata['userName'] = $this->makeClanUsername($userdata); $extID = is_numeric($userdata['extID']) ? $userdata['extID'] : 0; - return "${userdata['userID']}:${userdata['userName']}:${extID}"; + return "{$userdata['userID']}:{$userdata["userName"]}:{$extID}"; } public function getSongString($song){ - include __DIR__ . "/connection.php"; - /*$query3=$db->prepare("SELECT ID,name,authorID,authorName,size,isDisabled,download FROM songs WHERE ID = :songid LIMIT 1"); - $query3->execute([':songid' => $songID]);*/ - if($song['ID'] == 0 || empty($song['ID'])){ - return false; - } - //$song = $query3->fetch(); - if($song["isDisabled"] == 1){ - return false; + require __DIR__ . "/connection.php"; + require_once __DIR__ . "/exploitPatch.php"; + $librarySong = false; + $extraSongString = ''; + if(!isset($song['ID'])) { + $librarySong = true; + $song = $this->getLibrarySongInfo($song['songID']); } + if(!$song || $song['ID'] == 0 || empty($song['ID']) || $song["isDisabled"] == 1) return false; $dl = $song["download"]; if(strpos($dl, ':') !== false){ $dl = urlencode($dl); } - return "1~|~".$song["ID"]."~|~2~|~".str_replace("#", "", $song["name"])."~|~3~|~".$song["authorID"]."~|~4~|~".$song["authorName"]."~|~5~|~".$song["size"]."~|~6~|~~|~10~|~".$dl."~|~7~|~~|~8~|~1"; + if($librarySong) { + $artistsNames = []; + $artistsArray = explode('.', $song['artists']); + if(count($artistsArray) > 0) { + foreach($artistsArray AS &$artistID) { + $artistData = $this->getLibrarySongAuthorInfo($artistID); + if(!$artistData) continue; + $artistsNames[] = $artistID; + $artistsNames[] = $artistData['name']; + } + } + $artistsNames = implode(',', $artistsNames); + $extraSongString = '~|~9~|~'.$song['priorityOrder'].'~|~11~|~'.$song['ncs'].'~|~12~|~'.$song['artists'].'~|~13~|~'.($song['new'] ? 1 : 0).'~|~14~|~'.$song['new'].'~|~15~|~'.$artistsNames; + } + return "1~|~".$song["ID"]."~|~2~|~".ExploitPatch::translit(str_replace("#", "", $song["name"]))."~|~3~|~".$song["authorID"]."~|~4~|~".ExploitPatch::translit($song["authorName"])."~|~5~|~".$song["size"]."~|~6~|~~|~10~|~".$dl."~|~7~|~~|~8~|~1".$extraSongString; + } + public function getSongInfo($id, $column = "*", $library = false) { + if(!is_numeric($id)) return; + require __DIR__ . "/connection.php"; + $sinfo = $db->prepare("SELECT $column FROM songs WHERE ID = :id"); + $sinfo->execute([':id' => $id]); + $sinfo = $sinfo->fetch(); + if(empty($sinfo)) { + $sinfo = $this->getLibrarySongInfo($id, 'music', $library); + if(!$sinfo) return false; + else { + if($column != "*") return $sinfo[$column]; + else return array("isLocalSong" => false, "ID" => $sinfo["ID"], "name" => $sinfo["name"], "authorName" => $sinfo["authorName"], "size" => $sinfo["size"], "duration" => $sinfo["duration"], "download" => $sinfo["download"], "reuploadTime" => $sinfo["reuploadTime"], "reuploadID" => $sinfo["reuploadID"]); + } + } else { + if($column != "*") return $sinfo[$column]; + else return array("isLocalSong" => true, "ID" => $sinfo["ID"], "name" => $sinfo["name"], "authorName" => $sinfo["authorName"], "size" => $sinfo["size"], "duration" => $sinfo["duration"], "download" => $sinfo["download"], "reuploadTime" => $sinfo["reuploadTime"], "reuploadID" => $sinfo["reuploadID"]); + } + } + public function getSFXInfo($id, $column = "*") { + if(!is_numeric($id)) return; + require __DIR__ . "/connection.php"; + $sinfo = $db->prepare("SELECT $column FROM sfxs WHERE ID = :id"); + $sinfo->execute([':id' => $id]); + $sinfo = $sinfo->fetch(); + if(empty($sinfo)) return false; + else { + if($column != "*") return $sinfo[$column]; + else return array("ID" => $sinfo["ID"], "name" => $sinfo["name"], "authorName" => $sinfo["authorName"], "size" => $sinfo["size"], "download" => $sinfo["download"], "reuploadTime" => $sinfo["reuploadTime"], "reuploadID" => $sinfo["reuploadID"]); + } + } + public function getClanInfo($clan, $column = "*") { + global $dashCheck; + if(!is_numeric($clan) || $dashCheck === 'no') return false; + require __DIR__ . "/connection.php"; + $claninfo = $db->prepare("SELECT $column FROM clans WHERE ID = :id"); + $claninfo->execute([':id' => $clan]); + $claninfo = $claninfo->fetch(); + if(empty($claninfo)) return false; + else { + if($column != "*") { + if($column != "clan" AND $column != "desc" AND $column != "tag") return $claninfo[$column]; + else return base64_decode($claninfo[$column]); + } + else return array("ID" => $claninfo["ID"], "clan" => base64_decode($claninfo["clan"]), "tag" => base64_decode($claninfo["tag"]), "desc" => base64_decode($claninfo["desc"]), "clanOwner" => $claninfo["clanOwner"], "color" => $claninfo["color"], "isClosed" => $claninfo["isClosed"], "creationDate" => $claninfo["creationDate"]); + } + } + public function getClanID($clan) { + global $dashCheck; + if($dashCheck === 'no') return false; + require __DIR__ . "/connection.php"; + $claninfo = $db->prepare("SELECT ID FROM clans WHERE clan = :id"); + $claninfo->execute([':id' => base64_encode($clan)]); + $claninfo = $claninfo->fetch(); + return $claninfo["ID"]; + } + public function isPlayerInClan($id) { + global $dashCheck; + if(!is_numeric($id) || $dashCheck === 'no') return false; + require __DIR__ . "/connection.php"; + $claninfo = $db->prepare("SELECT clan FROM users WHERE extID = :id"); + $claninfo->execute([':id' => $id]); + $claninfo = $claninfo->fetch(); + if(!empty($claninfo)) return $claninfo["clan"]; + else return false; } - public function sendDiscordPM($receiver, $message){ - include __DIR__ . "/../../config/discord.php"; - if(!$discordEnabled){ + public function isPendingRequests($clan) { + global $dashCheck; + if(!is_numeric($clan) || $dashCheck === 'no') return false; + require __DIR__ . "/connection.php"; + $claninfo = $db->prepare("SELECT count(*) FROM clanrequests WHERE clanID = :id"); + $claninfo->execute([':id' => $clan]); + return $claninfo->fetchColumn(); + } + public function sendDiscordPM($receiver, $message, $json = false){ + require __DIR__ . "/../../config/discord.php"; + if(!$discordEnabled) { return false; } //findind the channel id @@ -409,7 +504,8 @@ public function sendDiscordPM($receiver, $message){ $headr = array(); $headr['User-Agent'] = 'GMDprivateServer (https://github.com/Cvolton/GMDprivateServer, 1.0)'; curl_setopt($crl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_setopt($crl, CURLOPT_POSTFIELDS, $data_string); + if(!$json) curl_setopt($crl, CURLOPT_POSTFIELDS, $data_string); + else curl_setopt($crl, CURLOPT_POSTFIELDS, $message); $headr[] = 'Content-type: application/json'; $headr[] = 'Authorization: Bot '.$bottoken; curl_setopt($crl, CURLOPT_HTTPHEADER,$headr); @@ -420,7 +516,7 @@ public function sendDiscordPM($receiver, $message){ return $response; } public function getDiscordAcc($discordID){ - include __DIR__ . "/../../config/discord.php"; + require __DIR__ . "/../../config/discord.php"; ///getting discord acc info $url = "https://discord.com/api/v8/users/".$discordID; $crl = curl_init($url); @@ -435,7 +531,56 @@ public function getDiscordAcc($discordID){ curl_close($crl); $userinfo = json_decode($response, true); //var_dump($userinfo); - return $userinfo["username"] . "#" . $userinfo["discriminator"]; + if($userinfo["discriminator"] != "0") $userinfo["discriminator"] = '#'.$userinfo["discriminator"]; + else $userinfo["discriminator"] = ''; + return $userinfo["username"].$userinfo["discriminator"]; + } + public function getDesc($lid, $dashboard = false) { + require __DIR__ . "/connection.php"; + require __DIR__ . "/exploitPatch.php"; + $desc = $db->prepare("SELECT levelDesc FROM levels WHERE levelID = :id"); + $desc->execute([':id' => $lid]); + $desc = $desc->fetch(); + if(empty($desc["levelDesc"])) return !$dashboard ? '*This level doesn\'t have description*' : 'This level doesn\'t have description'; + else return ExploitPatch::url_base64_decode($desc["levelDesc"]); + } + public function getLevelName($lid) { + require __DIR__ . "/connection.php"; + $desc = $db->prepare("SELECT levelName FROM levels WHERE levelID = :id"); + $desc->execute([':id' => $lid]); + $desc = $desc->fetch(); + if(!empty($desc["levelName"])) return $desc["levelName"]; else return false; + } + public function getLevelStats($lid) { + require __DIR__ . "/connection.php"; + $info = $db->prepare("SELECT downloads, likes, requestedStars FROM levels WHERE levelID = :id"); + $info->execute([':id' => $lid]); + $info = $info->fetch(); + $likes = $info["likes"]; // - $info["dislikes"]; + if(!empty($info)) return array('dl' => $info["downloads"], 'likes' => $likes, 'req' => $info["requestedStars"]); + } + public function getLevelAuthor($lid) { + require __DIR__ . "/connection.php"; + $desc = $db->prepare("SELECT extID FROM levels WHERE levelID = :id"); + $desc->execute([':id' => $lid]); + $desc = $desc->fetch(); + return $desc["extID"]; + } + public function isRated($lid) { + require __DIR__ . "/connection.php"; + $desc = $db->prepare("SELECT starStars FROM levels WHERE levelID = :id"); + $desc->execute([':id' => $lid]); + $desc = $desc->fetch(); + if($desc["starStars"] == 0) return false; + else return true; + } + public function hasDiscord($acc) { + require __DIR__ . "/connection.php"; + $ds = $db->prepare("SELECT discordID, discordLinkReq FROM accounts WHERE accountID = :id"); + $ds->execute([':id' => $acc]); + $ds = $ds->fetch(); + if($ds["discordID"] != 0 AND $ds["discordLinkReq"] == 0) return $ds["discordID"]; + else return false; } public function randomString($length = 6) { $randomString = openssl_random_pseudo_bytes($length); @@ -452,7 +597,7 @@ public function randomString($length = 6) { return $randomString; } public function getAccountsWithPermission($permission){ - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare("SELECT roleID FROM roles WHERE $permission = 1 ORDER BY priority DESC"); $query->execute(); $result = $query->fetchAll(); @@ -470,7 +615,7 @@ public function getAccountsWithPermission($permission){ public function checkPermission($accountID, $permission){ if(!is_numeric($accountID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; //isAdmin check $query = $db->prepare("SELECT isAdmin FROM accounts WHERE accountID = :accountID"); $query->execute([':accountID' => $accountID]); @@ -482,6 +627,7 @@ public function checkPermission($accountID, $permission){ $query = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); $query->execute([':accountID' => $accountID]); $roleIDarray = $query->fetchAll(); + if(empty($roleIDarray)) return false; $roleIDlist = ""; foreach($roleIDarray as &$roleIDobject){ $roleIDlist .= $roleIDobject["roleID"] . ","; @@ -512,8 +658,8 @@ public function checkPermission($accountID, $permission){ return false; } public function isCloudFlareIP($ip) { - $cf_ips = array( - '173.245.48.0/20', + $cf_ipv4s = array( + '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', @@ -529,10 +675,20 @@ public function isCloudFlareIP($ip) { '172.64.0.0/13', '131.0.72.0/22' ); - foreach ($cf_ips as $cf_ip) { - if (ipInRange::ipv4_in_range($ip, $cf_ip)) { - return true; - } + $cf_ipv6s = array( + '2400:cb00::/32', + '2606:4700::/32', + '2803:f800::/32', + '2405:b500::/32', + '2405:8100::/32', + '2a06:98c0::/29', + '2c0f:f248::/32' + ); + foreach($cf_ipv4s as $cf_ip) { + if(ipInRange::ipv4_in_range($ip, $cf_ip)) return true; + } + foreach($cf_ipv6s as $cf_ip) { + if(ipInRange::ipv6_in_range($ip, $cf_ip)) return true; } return false; } @@ -541,10 +697,12 @@ public function getIP(){ return $_SERVER['HTTP_CF_CONNECTING_IP']; if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && ipInRange::ipv4_in_range($_SERVER['REMOTE_ADDR'], '127.0.0.0/8')) //LOCALHOST REVERSE PROXY SUPPORT (7m.pl) return $_SERVER['HTTP_X_FORWARDED_FOR']; + if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['REMOTE_ADDR'] == "10.0.1.10") // 141412 PROXY SUPPORT FUCK YOU HESTIA + return explode(",", $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; // fuck my life return $_SERVER['REMOTE_ADDR']; } public function checkModIPPermission($permission){ - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $ip = $this->getIP(); $query=$db->prepare("SELECT modipCategory FROM modips WHERE IP = :ip"); $query->execute([':ip' => $ip]); @@ -565,7 +723,7 @@ public function checkModIPPermission($permission){ public function getFriends($accountID){ if(!is_numeric($accountID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $friendsarray = array(); $query = "SELECT person1,person2 FROM friendships WHERE person1 = :accountID OR person2 = :accountID"; //selecting friendships $query = $db->prepare($query); @@ -589,7 +747,7 @@ public function getFriends($accountID){ public function isFriends($accountID, $targetAccountID) { if(!is_numeric($accountID) || !is_numeric($targetAccountID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare("SELECT count(*) FROM friendships WHERE person1 = :accountID AND person2 = :targetAccountID OR person1 = :targetAccountID AND person2 = :accountID"); $query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); return $query->fetchColumn() > 0; @@ -597,7 +755,7 @@ public function isFriends($accountID, $targetAccountID) { public function getMaxValuePermission($accountID, $permission){ if(!is_numeric($accountID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $maxvalue = 0; $query = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); $query->execute([':accountID' => $accountID]); @@ -621,8 +779,7 @@ public function getMaxValuePermission($accountID, $permission){ } public function getAccountCommentColor($accountID){ if(!is_numeric($accountID)) return false; - - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare("SELECT roleID FROM roleassign WHERE accountID = :accountID"); $query->execute([':accountID' => $accountID]); $roleIDarray = $query->fetchAll(); @@ -647,88 +804,89 @@ public function getAccountCommentColor($accountID){ return $query->fetchColumn(); return "255,255,255"; } - public function rateLevel($accountID, $levelID, $stars, $difficulty, $auto, $demon){ + public function rateLevel($accountID, $levelID, $stars, $difficulty, $auto, $demon) { if(!is_numeric($accountID)) return false; - - include __DIR__ . "/connection.php"; - //lets assume the perms check is done properly before + require __DIR__ . "/connection.php"; + require __DIR__ . "/../../config/misc.php"; + require_once __DIR__ . "/cron.php"; + $diffName = $this->getDiffFromStars($stars)["name"]; $query = "UPDATE levels SET starDemon=:demon, starAuto=:auto, starDifficulty=:diff, starStars=:stars, rateDate=:now WHERE levelID=:levelID"; $query = $db->prepare($query); $query->execute([':demon' => $demon, ':auto' => $auto, ':diff' => $difficulty, ':stars' => $stars, ':levelID'=>$levelID, ':now' => time()]); - $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('1', :value, :value2, :levelID, :timestamp, :id)"); - $query->execute([':value' => $this->getDiffFromStars($stars)["name"], ':timestamp' => time(), ':id' => $accountID, ':value2' => $stars, ':levelID' => $levelID]); - - + $query->execute([':value' => $diffName, ':timestamp' => time(), ':id' => $accountID, ':value2' => $stars, ':levelID' => $levelID]); + $this->sendRateWebhook($accountID, $levelID); + if($automaticCron) Cron::updateCreatorPoints($accountID, false); } public function featureLevel($accountID, $levelID, $state) { if(!is_numeric($accountID)) return false; + require __DIR__ . "/connection.php"; + $query = $db->prepare("SELECT starFeatured FROM levels WHERE levelID=:levelID ORDER BY starFeatured DESC LIMIT 1"); + $query->execute([':levelID' => $levelID]); + $featured = $query->fetchColumn(); + if(!$featured) { + $query = $db->prepare("SELECT starFeatured FROM levels ORDER BY starFeatured DESC LIMIT 1"); + $query->execute(); + $featured = $query->fetchColumn() + 1; + } switch($state) { - case 0: - $feature = 0; - $epic = 0; - break; - case 1: - $feature = 1; - $epic = 0; - break; - case 2: // Stole from TheJulfor - $feature = 1; - $epic = 1; - break; - case 3: - $feature = 1; - $epic = 2; - break; - case 4: - $feature = 1; - $epic = 3; - break; - } - include __DIR__ . "/connection.php"; - $query = "UPDATE levels SET starFeatured=:feature, starEpic=:epic, rateDate=:now WHERE levelID=:levelID"; - $query = $db->prepare($query); + case 0: + $feature = 0; + $epic = 0; + break; + case 1: + case 2: + case 3: + case 4: + $feature = $featured; + $epic = $state - 1; + break; + } + $query = $db->prepare("UPDATE levels SET starFeatured = :feature, starEpic = :epic, rateDate = :now WHERE levelID = :levelID"); $query->execute([':feature' => $feature, ':epic' => $epic, ':levelID' => $levelID, ':now' => time()]); $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('2', :value, :levelID, :timestamp, :id)"); $query->execute([':value' => $state, ':timestamp' => time(), ':id' => $accountID, ':levelID' => $levelID]); } - public function verifyCoinsLevel($accountID, $levelID, $coins){ + public function verifyCoinsLevel($accountID, $levelID, $coins) { if(!is_numeric($accountID)) return false; - - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = "UPDATE levels SET starCoins=:coins WHERE levelID=:levelID"; $query = $db->prepare($query); $query->execute([':coins' => $coins, ':levelID'=>$levelID]); - $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('3', :value, :levelID, :timestamp, :id)"); $query->execute([':value' => $coins, ':timestamp' => time(), ':id' => $accountID, ':levelID' => $levelID]); } - public function songReupload($url){ + public function songReupload($url, $author, $name, $accountID) { require __DIR__ . "/../../incl/lib/connection.php"; require_once __DIR__ . "/../../incl/lib/exploitPatch.php"; $song = str_replace("www.dropbox.com","dl.dropboxusercontent.com",$url); - if (filter_var($song, FILTER_VALIDATE_URL) == TRUE && substr($song, 0, 4) == "http") { + if(filter_var($song, FILTER_VALIDATE_URL) == TRUE && substr($song, 0, 4) == "http") { $song = str_replace(["?dl=0","?dl=1"],"",$song); $song = trim($song); - $query = $db->prepare("SELECT count(*) FROM songs WHERE download = :download"); + $query = $db->prepare("SELECT ID FROM songs WHERE download = :download"); $query->execute([':download' => $song]); - $count = $query->fetchColumn(); - if($count != 0){ - return "-3"; + $count = $query->fetch(); + if(!empty($count)){ + return "-3".$count["ID"]; } - $name = ExploitPatch::remove(urldecode(str_replace([".mp3",".webm",".mp4",".wav"], "", basename($song)))); - $author = "Reupload"; + $freeID = false; + while(!$freeID) { + $db_fid = rand(99, 7999999); + $checkID = $db->prepare('SELECT count(*) FROM songs WHERE ID = :id'); // If randomized ID picks existing song ID + $checkID->execute([':id' => $db_fid]); + if($checkID->fetchColumn() == 0) $freeID = true; + } + if(empty($name)) $name = ExploitPatch::remove(urldecode(str_replace([".mp3",".webm",".mp4",".wav"], "", basename($song)))); + if(empty($author)) $author = "Reupload"; $info = $this->getFileInfo($song); - $size = $info['size']; - if(substr($info['type'], 0, 6) != "audio/") - return "-4"; - $size = round($size / 1024 / 1024, 2); + $size = round($info['size'] / 1024 / 1024, 2); + if(substr($info['type'], 0, 6) != "audio/" || $size == 0 || $size == '-0') return "-4"; $hash = ""; - $query = $db->prepare("INSERT INTO songs (name, authorID, authorName, size, download, hash) - VALUES (:name, '9', :author, :size, :download, :hash)"); - $query->execute([':name' => $name, ':download' => $song, ':author' => $author, ':size' => $size, ':hash' => $hash]); - return $db->lastInsertId(); - }else{ + $query = $db->prepare("INSERT INTO songs (ID, name, authorID, authorName, size, download, hash, reuploadTime, reuploadID) + VALUES (:songID, :name, '9', :author, :size, :download, :hash, :time, :accountID)"); + $query->execute([':songID' => $db_fid, ':name' => $name, ':download' => $song, ':author' => $author, ':size' => $size, ':hash' => $hash, ':time' => time(), ':accountID' => $accountID]); + return $db_fid; + } else { return "-2"; } } @@ -747,38 +905,2161 @@ public function getFileInfo($url){ curl_close($ch); return ['size' => $size, 'type' => $mime]; } - public function suggestLevel($accountID, $levelID, $difficulty, $stars, $feat, $auto, $demon){ + public function suggestLevel($accountID, $levelID, $difficulty, $stars, $feat, $auto, $demon) { if(!is_numeric($accountID)) return false; - - include __DIR__ . "/connection.php"; - $query = "INSERT INTO suggest (suggestBy, suggestLevelID, suggestDifficulty, suggestStars, suggestFeatured, suggestAuto, suggestDemon, timestamp) VALUES (:account, :level, :diff, :stars, :feat, :auto, :demon, :timestamp)"; - $query = $db->prepare($query); + require __DIR__ . "/connection.php"; + $query = $db->prepare("INSERT INTO suggest (suggestBy, suggestLevelID, suggestDifficulty, suggestStars, suggestFeatured, suggestAuto, suggestDemon, timestamp) VALUES (:account, :level, :diff, :stars, :feat, :auto, :demon, :timestamp)"); $query->execute([':account' => $accountID, ':level' => $levelID, ':diff' => $difficulty, ':stars' => $stars, ':feat' => $feat, ':auto' => $auto, ':demon' => $demon, ':timestamp' => time()]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, account, timestamp) VALUES ('41', :value, :value3, :id, :timestamp)"); + $query->execute([':value' => $stars, ':value3' => $levelID, ':id' => $accountID, ':timestamp' => time()]); + $this->sendSuggestWebhook($accountID, $levelID, $difficulty, $stars, $feat, $auto, $demon); + } + public function removeSuggestedLevel($accountID, $levelID) { + if(!is_numeric($accountID)) return false; + require __DIR__ . "/connection.php"; + $query = $db->prepare("DELETE FROM suggest WHERE suggestLevelId = :levelID"); + $query->execute([':levelID' => $levelID]); + $query = $db->prepare("INSERT INTO modactions (type, value, value3, timestamp, account) VALUES ('40', :value, :levelID, :timestamp, :id)"); + $query->execute([':value' => "1", ':timestamp' => time(), ':id' => $accountID, ':levelID' => $levelID]); + } + public function isUnlisted($levelID) { + require __DIR__."/connection.php"; + $query = $db->prepare("SELECT count(*) FROM levels WHERE unlisted > 0 AND levelID = :id"); + $query->execute([':id' => $levelID]); + $query = $query->fetch(); + if(!empty($query)) return true; + else return false; } public function getListOwner($listID) { if(!is_numeric($listID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare('SELECT accountID FROM lists WHERE listID = :id'); $query->execute([':id' => $listID]); return $query->fetchColumn(); } public function getListLevels($listID) { if(!is_numeric($listID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare('SELECT listlevels FROM lists WHERE listID = :id'); $query->execute([':id' => $listID]); return $query->fetchColumn(); } public function getListDiffName($diff) { - if($diff == -1) return 'N/A'; $diffs = ['Auto', 'Easy', 'Normal', 'Hard', 'Harder', 'Extreme', 'Easy Demon', 'Medium Demon', 'Hard Demon', 'Insane Demon', 'Extreme Demon']; + if($diff == -1 || $diff >= count($diffs)) return 'N/A'; return $diffs[$diff]; } public function getListName($listID) { if(!is_numeric($listID)) return false; - include __DIR__ . "/connection.php"; + require __DIR__ . "/connection.php"; $query = $db->prepare('SELECT listName FROM lists WHERE listID = :id'); $query->execute([':id' => $listID]); return $query->fetchColumn(); } + public function makeClanUsername($user) { + require __DIR__ . "/../../config/dashboard.php"; + if($clansEnabled && $user['clan'] > 0 && !isset($_REQUEST['noClan'])) { + $clan = $this->getClanInfo($user['clan'], 'tag'); + if(!empty($clan)) return '['.$clan.'] '.$user['userName']; + } + return $user['userName']; + } + public function updateLibraries($token, $expires, $mainServerTime, $type = 0) { + require __DIR__ . "/../../config/dashboard.php"; + $servers = []; + $types = ['sfx', 'music']; + if(!isset($customLibrary)) $customLibrary = [[1, 'Geometry Dash', 'https://geometrydashfiles.b-cdn.net'], [3, $gdps, null]]; + foreach($customLibrary AS $library) { + if(($types[$type] == 'sfx' AND $library[3] === 1) OR ($types[$type] == 'music' AND $library[3] === 0)) continue; + if($library[2] !== null) { + $servers['s'.$library[0]] = $library[2]; + } + } + $updatedLib = false; + foreach($servers AS $key => &$server) { + if($types[$type] == 'music') { + $versionUrl = $server.'/'.$types[$type].'/'.$types[$type].'library_version_02.txt'; + $dataUrl = $server.'/'.$types[$type].'/'.$types[$type].'library_02.dat'; + } else { + $versionUrl = $server.'/'.$types[$type].'/'.$types[$type].'library_version.txt'; + $dataUrl = $server.'/'.$types[$type].'/'.$types[$type].'library.dat'; + } + if(file_exists(__DIR__.'/../../'.$types[$type].'/'.$key.'.txt')) $oldVersion = explode(', ', file_get_contents(__DIR__.'/../../'.$types[$type].'/'.$key.'.txt')); + else $oldVersion = [0, 0]; + if($oldVersion[1] + 600 > time()) continue; // Download library only once per 10 minutes + $curl = curl_init($versionUrl.'?token='.$token.'&expires='.$expires); + curl_setopt_array($curl, [ + CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_FOLLOWLOCATION => 1 + ]); + $newVersion = (int)curl_exec($curl); + curl_close($curl); + if($newVersion > $oldVersion[0]) { + file_put_contents(__DIR__.'/../../'.$types[$type].'/'.$key.'.txt', $newVersion.', '.time()); + $download = curl_init($dataUrl.'?token='.$token.'&expires='.$expires.'&dashboard=1'); + curl_setopt_array($download, [ + CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_FOLLOWLOCATION => 1 + ]); + $dat = curl_exec($download); + $resultStatus = curl_getinfo($download, CURLINFO_HTTP_CODE); + curl_close($download); + if($resultStatus == 200) { + file_put_contents(__DIR__.'/../../'.$types[$type].'/'.$key.'.dat', $dat); + $updatedLib = true; + } + } + } + // Now this server's version check + if(file_exists(__DIR__.'/../../'.$types[$type].'/gdps.txt')) $oldVersion = file_get_contents(__DIR__.'/../../'.$types[$type].'/gdps.txt'); + else { + $oldVersion = 0; + file_put_contents(__DIR__.'/../../'.$types[$type].'/gdps.txt', $mainServerTime); + } + if($oldVersion < $mainServerTime || $updatedLib) $this->generateDATFile($mainServerTime, $type); + } + public function generateDATFile($mainServerTime, $type = 0) { + require __DIR__ . "/connection.php"; + require __DIR__ . "/exploitPatch.php"; + require __DIR__ . "/../../config/dashboard.php"; + $library = $servers = $serverIDs = $serverTypes = []; + if(!isset($customLibrary)) $customLibrary = [[1, 'Geometry Dash', 'https://geometrydashfiles.b-cdn.net', 2], [3, $gdps, null, 2]]; + $types = ['sfx', 'music']; + foreach($customLibrary AS $customLib) { + if($customLib[2] !== null) { + $servers['s'.$customLib[0]] = $customLib[0]; + } + $serverIDs[$customLib[2]] = $customLib[0]; + if($types[$type] == 'sfx') { + $library['folders'][($customLib[0] + 1)] = [ + 'name' => ExploitPatch::escapedat($customLib[1]), + 'type' => 1, + 'parent' => 1 + ]; + } else { + $library['tags'][$customLib[0]] = [ + 'ID' => $customLib[0], + 'name' => ExploitPatch::escapedat($customLib[1]), + ]; + } + } + if(file_exists(__DIR__.'/../../'.$types[$type].'/ids.json')) $idsConverter = json_decode(file_get_contents(__DIR__.'/../../'.$types[$type].'/ids.json'), true); + else $idsConverter = ['count' => ($type == 0 ? count($customLibrary) + 2 : 8000000), 'IDs' => [], 'originalIDs' => []]; + if(file_exists(__DIR__.'/../../config/skipSFXIDs.json')) $skipSFXIDs = json_decode(file_get_contents(__DIR__.'/../../config/skipSFXIDs.json'), true); + else $skipSFXIDs = []; + foreach($servers AS $key => $server) { + if(!file_exists(__DIR__.'/../../'.$types[$type].'/'.$key.'.dat')) continue; + $res = null; + $bits = null; + $res = file_get_contents(__DIR__.'/../../'.$types[$type].'/'.$key.'.dat'); + $res = mb_convert_encoding($res, 'UTF-8', 'UTF-8'); + try { + $res = ExploitPatch::url_base64_decode($res); + $res = zlib_decode($res); + } catch(Exception $e) { + unlink(__DIR__.'/../../'.$types[$type].'/'.$key.'.dat'); + continue; + } + $res = explode('|', $res); + if(!$type) { + for($i = 0; $i < count($res); $i++) { // SFX library decoding was made by MigMatos, check their ObeyGDBot! https://obeybd.web.app/ + $res[$i] = explode(';', $res[$i]); + if($i === 0) { + $library['version'] = $mainServerTime; + $version = explode(',', $res[0][0]); + $version[1] = $mainServerTime; + $version = implode(',', $version); + } + for($j = 1; $j <= count($res[$i]); $j++) { + $bits = explode(',', $res[$i][$j]); + switch($i) { + case 0: // File/Folder + if(empty(trim($bits[1])) || empty($bits[0]) || !is_numeric($bits[0])) break; + if(empty($idsConverter['originalIDs'][$server][$bits[0]])) { + $idsConverter['count']++; + while(in_array($idsConverter['count'], $skipSFXIDs)) $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $bits[0], 'name' => $bits[1], 'type' => $bits[2]]; + $idsConverter['originalIDs'][$server][$bits[0]] = $idsConverter['count']; + $bits[0] = $idsConverter['count']; + } else { + $bits[0] = $idsConverter['originalIDs'][$server][$bits[0]]; + if(!isset($idsConverter['IDs'][$bits[0]]['name'])) $idsConverter['IDs'][$bits[0]] = ['server' => $server, 'ID' => $bits[0], 'name' => $bits[1], 'type' => $bits[2]]; + } + if($bits[3] != 1) { + if(empty($idsConverter['originalIDs'][$server][$bits[3]])) { + $idsConverter['count']++; + while(in_array($idsConverter['count'], $skipSFXIDs)) $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $bits[3], 'name' => $bits[1], 'type' => 1]; + $idsConverter['originalIDs'][$server][$bits[3]] = $idsConverter['count']; + $bits[3] = $idsConverter['count']; + } else $bits[3] = $idsConverter['originalIDs'][$server][$bits[3]]; + } else $bits[3] = $server + 1; + if($bits[2]) { + $library['folders'][$bits[0]] = [ + 'name' => ExploitPatch::escapedat($bits[1]), + 'type' => (int)$bits[2], + 'parent' => (int)$bits[3] + ]; + } else { + $library['files'][$bits[0]] = [ + 'name' => ExploitPatch::escapedat($bits[1]), + 'type' => (int)$bits[2], + 'parent' => (int)$bits[3], + 'bytes' => (int)$bits[4], + 'milliseconds' => (int)$bits[5], + ]; + } + break; + case 1: // Credit + if(empty(trim($bits[0])) || empty(trim($bits[1]))) continue 2; + $library['credits'][ExploitPatch::escapedat($bits[0])] = [ + 'name' => ExploitPatch::escapedat($bits[0]), + 'website' => ExploitPatch::escapedat($bits[1]), + ]; + break; + } + } + } + } else { + $version = $mainServerTime; + array_shift($res); + $x = 0; + foreach($res AS &$data) { + $data = rtrim($data, ';'); + $music = explode(';', $data); + foreach($music AS &$songString) { + $song = explode(',', $songString); + $originalID = $song[0]; + if(empty($song[0]) || !is_numeric($song[0])) continue; + if(empty($idsConverter['originalIDs'][$server][$song[0]])) { + $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $song[0], 'type' => $x]; + $idsConverter['originalIDs'][$server][$song[0]] = $idsConverter['count']; + $song[0] = $idsConverter['count']; + } else $song[0] = $idsConverter['originalIDs'][$server][$song[0]]; + switch($x) { + case 0: + $idsConverter['IDs'][$song[0]] = $library['authors'][$song[0]] = [ + 'server' => $server, + 'type' => $x, + 'originalID' => $originalID, + 'authorID' => $song[0], + 'name' => ExploitPatch::escapedat($song[1]), + 'link' => ExploitPatch::escapedat($song[2]), + 'yt' => ExploitPatch::escapedat($song[3]) + ]; + break; + case 1: + if(empty($idsConverter['originalIDs'][$server][$song[2]])) { + $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $song[2], 'type' => $x]; + $idsConverter['originalIDs'][$server][$song[2]] = $idsConverter['count']; + $song[2] = $idsConverter['count']; + } else $song[2] = $idsConverter['originalIDs'][$server][$song[2]]; + $tags = explode('.', $song[5]); + $newTags = []; + foreach($tags AS &$tag) { + if(empty($tag)) continue; + if(empty($idsConverter['originalIDs'][$server][$tag])) { + $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $tag, 'type' => 2]; + $idsConverter['originalIDs'][$server][$tag] = $idsConverter['count']; + $tag = $idsConverter['count']; + } else $tag = $idsConverter['originalIDs'][$server][$tag]; + $newTags[] = $tag; + } + $newTags[] = $server; + $tags = '.'.implode('.', $newTags).'.'; + $newArtists = []; + $artists = explode('.', $song[7]); + foreach($artists AS &$artist) { + if(empty($artist)) continue; + if(empty($idsConverter['originalIDs'][$server][$artist])) { + $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $artist, 'type' => 0]; + $idsConverter['originalIDs'][$server][$artist] = $idsConverter['count']; + $artist = $idsConverter['count']; + } else $artist = $idsConverter['originalIDs'][$server][$artist]; + $newArtists[] = $artist; + } + $artists = implode('.', $newArtists); + $idsConverter['IDs'][$song[0]] = $library['songs'][$song[0]] = [ + 'server' => $server, + 'type' => $x, + 'originalID' => $originalID, + 'ID' => $song[0], + 'name' => ExploitPatch::escapedat($song[1]), + 'authorID' => $song[2], + 'size' => $song[3], + 'seconds' => $song[4], + 'tags' => $tags, + 'ncs' => $song[6] ?: 0, + 'artists' => $artists, + 'externalLink' => $song[8] ?: '', + 'new' => $song[9] ?: 0, + 'priorityOrder' => $song[10] ?: 0 + ]; + break; + case 2: + $idsConverter['IDs'][$song[0]] = $library['tags'][$song[0]] = [ + 'server' => $server, + 'type' => $x, + 'originalID' => $originalID, + 'ID' => $song[0], + 'name' => ExploitPatch::escapedat($song[1]) + ]; + break; + } + } + $x++; + } + } + } + if(!$type) { + $sfxs = $db->prepare("SELECT sfxs.*, accounts.userName FROM sfxs JOIN accounts ON accounts.accountID = sfxs.reuploadID"); + $sfxs->execute(); + $sfxs = $sfxs->fetchAll(); + $folderID = $gdpsLibrary = []; + $server = $serverIDs[null]; + foreach($sfxs AS &$customSFX) { + if(!isset($folderID[$customSFX['reuploadID']])) { + if(empty($idsConverter['originalIDs'][$server][$customSFX['reuploadID']])) { + $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $customSFX['ID'], 'name' => $customSFX['userName'].'\'s SFXs', 'type' => 1]; + $idsConverter['originalIDs'][$server][$customSFX['reuploadID']] = $idsConverter['count']; + $newID = $idsConverter['count']; + } else $newID = $idsConverter['originalIDs'][$server][$customSFX['reuploadID']]; + $library['folders'][$newID] = [ + 'name' => ExploitPatch::escapedat($customSFX['userName']).'\'s SFXs', + 'type' => 1, + 'parent' => (int)($server + 1) + ]; + $gdpsLibrary['folders'][$newID] = [ + 'name' => ExploitPatch::escapedat($customSFX['userName']).'\'s SFXs', + 'type' => 1, + 'parent' => 1 + ]; + $folderID[$customSFX['reuploadID']] = true; + } + if(empty($idsConverter['originalIDs'][$server][$customSFX['ID'] + 8000000])) { + $idsConverter['count']++; + $idsConverter['IDs'][$idsConverter['count']] = ['server' => $server, 'ID' => $customSFX['ID'], 'name' => $customSFX['name'], 'type' => 0]; + $idsConverter['originalIDs'][$server][$customSFX['ID'] + 8000000] = $idsConverter['count']; + $customSFX['ID'] = $idsConverter['count']; + } else $customSFX['ID'] = $idsConverter['originalIDs'][$server][$customSFX['ID'] + 8000000]; + $library['files'][$customSFX['ID']] = $gdpsLibrary['files'][$customSFX['ID']] = [ + 'name' => ExploitPatch::escapedat($customSFX['name']), + 'type' => 0, + 'parent' => (int)$idsConverter['originalIDs'][$server][$customSFX['reuploadID']], + 'bytes' => (int)$customSFX['size'], + 'milliseconds' => (int)($customSFX['milliseconds'] / 10) + ]; + } + $filesEncrypted = $creditsEncrypted = []; + foreach($library['folders'] AS $id => &$folder) $filesEncrypted[] = implode(',', [$id, $folder['name'], 1, $folder['parent'], 0, 0]); + foreach($library['files'] AS $id => &$file) $filesEncrypted[] = implode(',', [$id, $file['name'], 0, $file['parent'], $file['bytes'], $file['milliseconds']]); + foreach($library['credits'] AS &$credit) $creditsEncrypted[] = implode(',', [$credit['name'], $credit['website']]); + $encrypted = $version.";".implode(';', $filesEncrypted)."|" .implode(';', $creditsEncrypted).';'; + $filesEncrypted = $creditsEncrypted = []; + foreach($gdpsLibrary['folders'] AS $id => &$folder) $filesEncrypted[] = implode(',', [$id, $folder['name'], 1, $folder['parent'], 0, 0]); + foreach($gdpsLibrary['files'] AS $id => &$file) $filesEncrypted[] = implode(',', [$id, $file['name'], 0, $file['parent'], $file['bytes'], $file['milliseconds']]); + $creditsEncrypted[] = implode(',', [$gdps, $_SERVER['SERVER_NAME']]); + $gdpsEncrypted = $version.";".implode(';', $filesEncrypted)."|" .implode(';', $creditsEncrypted).';'; + } else { + $songs = $db->prepare("SELECT songs.*, accounts.userName FROM songs JOIN accounts ON accounts.accountID = songs.reuploadID WHERE isDisabled = 0"); + $songs->execute(); + $songs = $songs->fetchAll(); + $folderID = $accIDs = $gdpsLibrary = []; + $c = 100; + foreach($songs AS &$customSongs) { + $c++; + $authorName = trim(ExploitPatch::rucharclean(ExploitPatch::escapedat(ExploitPatch::translit($customSongs['authorName'])), 40)); + if(empty($authorName)) $authorName = 'Reupload'; + if(empty($folderID[$authorName])) { + $folderID[$authorName] = $c; + $library['authors'][$serverIDs[null]. 0 .$folderID[$authorName]] = $gdpsLibrary['authors'][$serverIDs[null]. 0 .$folderID[$authorName]] = [ + 'authorID' => (int)($serverIDs[null]. 0 .$folderID[$authorName]), + 'name' => $authorName, + 'link' => ' ', + 'yt' => ' ' + ]; + } + if(empty($accIDs[$customSongs['reuploadID']])) { + $c++; + $accIDs[$customSongs['reuploadID']] = $c; + $library['tags'][$serverIDs[null]. 0 .$accIDs[$customSongs['reuploadID']]] = $gdpsLibrary['tags'][$serverIDs[null]. 0 .$accIDs[$customSongs['reuploadID']]] = [ + 'ID' => (int)($serverIDs[null]. 0 .$accIDs[$customSongs['reuploadID']]), + 'name' => ExploitPatch::rucharclean(ExploitPatch::escapedat($customSongs['userName']), 30), + ]; + } + $customSongs['name'] = trim(ExploitPatch::rucharclean(ExploitPatch::escapedat(ExploitPatch::translit($customSongs['name'])), 40)); + $library['songs'][$customSongs['ID']] = $gdpsLibrary['songs'][$customSongs['ID']] = [ + 'ID' => ($customSongs['ID']), + 'name' => !empty($customSongs['name']) ? $customSongs['name'] : 'Unnamed', + 'authorID' => (int)($serverIDs[null]. 0 .$folderID[$authorName]), + 'size' => $customSongs['size'] * 1024 * 1024, + 'seconds' => $customSongs['duration'], + 'tags' => '.'.$serverIDs[null].'.'.$serverIDs[null]. 0 .$accIDs[$customSongs['reuploadID']].'.', + 'ncs' => 0, + 'artists' => '', + 'externalLink' => urlencode($customSongs['download']), + 'new' => ($customSongs['reuploadTime'] > time() - 604800 ? 1 : 0), + 'priorityOrder' => 0 + ]; + } + $authorsEncrypted = $songsEncrypted = $tagsEncrypted = []; + foreach($library['authors'] AS &$authorList) { + unset($authorList['server']); + unset($authorList['type']); + unset($authorList['originalID']); + $authorsEncrypted[] = implode(',', $authorList); + } + foreach($library['songs'] AS &$songsList) { + unset($songsList['server']); + unset($songsList['type']); + unset($songsList['originalID']); + $songsEncrypted[] = implode(',', $songsList); + } + foreach($library['tags'] AS &$tagsList) { + unset($tagsList['server']); + unset($tagsList['type']); + unset($tagsList['originalID']); + $tagsEncrypted[] = implode(',', $tagsList); + } + $encrypted = $version."|".implode(';', $authorsEncrypted).";|" .implode(';', $songsEncrypted).";|" .implode(';', $tagsEncrypted).';'; + $authorsEncrypted = $songsEncrypted = $tagsEncrypted = []; + foreach($gdpsLibrary['authors'] AS &$authorList) { + unset($authorList['server']); + unset($authorList['type']); + unset($authorList['originalID']); + $authorsEncrypted[] = implode(',', $authorList); + } + foreach($gdpsLibrary['songs'] AS &$songsList) { + unset($songsList['server']); + unset($songsList['type']); + unset($songsList['originalID']); + $songsEncrypted[] = implode(',', $songsList); + } + foreach($gdpsLibrary['tags'] AS &$tagsList) { + unset($tagsList['server']); + unset($tagsList['type']); + unset($tagsList['originalID']); + $tagsEncrypted[] = implode(',', $tagsList); + } + $gdpsEncrypted = $version."|".implode(';', $authorsEncrypted).";|" .implode(';', $songsEncrypted).";|" .implode(';', $tagsEncrypted).';'; + } + file_put_contents(__DIR__.'/../../'.$types[$type].'/ids.json', json_encode($idsConverter, JSON_PRETTY_PRINT | JSON_INVALID_UTF8_IGNORE)); + $encrypted = zlib_encode($encrypted, ZLIB_ENCODING_DEFLATE); + $encrypted = ExploitPatch::url_base64_encode($encrypted); + file_put_contents(__DIR__.'/../../'.$types[$type].'/gdps.dat', $encrypted); + $gdpsEncrypted = zlib_encode($gdpsEncrypted, ZLIB_ENCODING_DEFLATE); + $gdpsEncrypted = ExploitPatch::url_base64_encode($gdpsEncrypted); + file_put_contents(__DIR__.'/../../'.$types[$type].'/standalone.dat', $gdpsEncrypted); + } + public function getAudioDuration($file) { + require_once(__DIR__.'/../../config/getid3/getid3.php'); + $getID3 = new getID3; + $info = $getID3->analyze($file); + $result = (isset($info['playtime_seconds']) ? (int)($info['playtime_seconds']) : false); + return $result; + } + public function convertSFX($file, $server, $name, $token) { + require __DIR__."/../../config/dashboard.php"; + if(!$convertEnabled) return false; + $link = $convertSFXAPI[rand(0, count($convertSFXAPI) - 1)]; + $filePath = $file['tmp_name']; + $type = $file['type']; + $post = [ + 'name' => $name, + 'token' => $token, + 'server' => $server, + 'file' => new CURLFile(realpath($filePath), $type, $filePath) + ]; + $curl = curl_init($link.'/convert'); + curl_setopt_array($curl, [ + CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $post + ]); + $response = json_decode(curl_exec($curl), true); + $result = isset($response['success']) ? $response['success'] : false; + return $result; + } + public function getLibrarySongInfo($id, $type = 'music', $extraLibrary = false) { + require __DIR__."/../../config/dashboard.php"; + if(!file_exists(__DIR__.'/../../'.$type.'/ids.json')) return false; + $servers = $serverIDs = $serverNames = []; + foreach($customLibrary AS $customLib) { + $servers[$customLib[0]] = $customLib[2]; + $serverNames[$customLib[0]] = $customLib[1]; + $serverIDs[$customLib[2]] = $customLib[0]; + } + $library = $extraLibrary ? $extraLibrary : json_decode(file_get_contents(__DIR__.'/../../'.$type.'/ids.json'), true); + if(!isset($library['IDs'][$id]) || ($type == 'music' && $library['IDs'][$id]['type'] != 1)) return false; + if($type == 'music') { + $song = $library['IDs'][$id]; + $author = $library['IDs'][$song['authorID']]; + $token = $this->randomString(11); + $expires = time() + 3600; + $link = $servers[$song['server']].'/music/'.$song['originalID'].'.ogg?token='.$token.'&expires='.$expires; + return ['server' => $song['server'], 'ID' => $id, 'name' => $song['name'], 'authorID' => $song['authorID'], 'authorName' => $author['name'], 'size' => round($song['size'] / 1024 / 1024, 2), 'download' => $link, 'seconds' => $song['seconds'], 'tags' => $song['tags'], 'ncs' => $song['ncs'], 'artists' => $song['artists'], 'externalLink' => $song['externalLink'], 'new' => $song['new'], 'priorityOrder' => $song['priorityOrder']]; + } else { + $SFX = $library['IDs'][$id]; + $token = $this->randomString(11); + $expires = time() + 3600; + $link = $servers[$SFX['server']] != null ? $servers[$SFX['server']].'/sfx/s'.$SFX['ID'].'.ogg?token='.$token.'&expires='.$expires : $this->getSFXInfo($SFX['ID'], 'download'); + return ['isLocalSFX' => $servers[$SFX['server']] == null, 'server' => $SFX['server'], 'ID' => $id, 'name' => $song['name'], 'download' => $link, 'originalID' => $SFX['ID']]; + } + } + public function getLibrarySongAuthorInfo($id) { + require __DIR__."/../../config/dashboard.php"; + if(!file_exists(__DIR__.'/../../music/ids.json')) return false; + $library = json_decode(file_get_contents(__DIR__.'/../../music/ids.json'), true); + if(!isset($library['IDs'][$id])) return false; + return $library['IDs'][$id]; + } + public function sendRateWebhook($modAccID, $levelID) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require_once __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($levelID) OR !in_array("rate", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($rateWebhook); + $level = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $level->execute([':levelID' => $levelID]); + $level = $level->fetch(); + if(!$level) return false; + $modUsername = $this->getAccountName($modAccID); + $modHasDiscord = $this->hasDiscord($modAccID); + $modFormattedUsername = $modHasDiscord ? "<@".$modHasDiscord.">" : "**".$modUsername."**"; + $creatorAccID = $level['extID']; + $creatorUsername = $this->getAccountName($creatorAccID); + $creatorHasDiscord = $this->hasDiscord($creatorAccID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $isRated = $level['starStars'] != 0; + $difficulty = $this->getDifficulty($level['starDifficulty'], $level['starAuto'], $level['starDemon'], $level['starDemonDiff']); + $starsIcon = 'stars'; + $diffIcon = 'na'; + switch(true) { + case ($level['starEpic'] > 0): + $starsArray = ['', 'epic', 'legendary', 'mythic']; + $starsIcon = $starsArray[$level['starEpic']]; + break; + case ($level['starFeatured'] > 0): + $starsIcon = 'featured'; + break; + } + $diffArray = ['n/a' => 'na', 'auto' => 'auto', 'easy' => 'easy', 'normal' => 'normal', 'hard' => 'hard', 'harder' => 'harder', 'insane' => 'insane', 'demon' => 'demon-hard', 'easy demon' => 'demon-easy', 'medium demon' => 'demon-medium', 'hard demon' => 'demon-hard', 'insane demon' => 'demon-insane', 'extreme demon' => 'demon-extreme']; + $diffIcon = $diffArray[strtolower($difficulty)] ?? 'na'; + $originalDiffColorArray = ['na' => 'a9a9a9', 'auto' => 'f5c96b', 'easy' => '00e0ff', 'normal' => '00ff3a', 'hard' => 'ffb438', 'harder' => 'fc1f1f', 'insane' => 'f91ffc', 'demon-easy' => 'aa6bf5', 'demon-medium' => 'ac2974', 'demon-hard' => 'ff0000', 'demon-insane' => 'b31548', 'demon-extreme' => '8e0505']; + $originalDiffColor = $originalDiffColor && empty($successColor); + if($level['starStars'] != 0) { + $setColor = empty($successColor) ? $originalDiffColorArray[$diffIcon] : $successColor; + $setTitle = $this->webhookLanguage('rateSuccessTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('rateSuccessTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('rateSuccessDesc', $webhookLangArray), $modFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('rateSuccessDescDM', $webhookLangArray), $modFormattedUsername, $tadaEmoji); + $setNotificationText = $rateNotificationText; + } else { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('rateFailTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('rateFailTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('rateFailDesc', $webhookLangArray), $modFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('rateFailDescDM', $webhookLangArray), $modFormattedUsername, $sobEmoji); + $setNotificationText = $unrateNotificationText; + } + $stats = $downloadEmoji.' '.$level['downloads'].' • '.($level['likes'] - $level['dislikes'] >= 0 ? $likeEmoji.' '.abs($level['likes'] - $level['dislikes']) : $dislikeEmoji.' '.abs($level['likes'] - $level['dislikes'])); + $levelField = [$this->webhookLanguage('levelTitle', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$level['levelName'].'**', $creatorFormattedUsername), true]; + $IDField = [$this->webhookLanguage('levelIDTitle', $webhookLangArray), $level['levelID'], true]; + if($level['starStars'] == 1) $action = 0; elseif(($level['starStars'] < 5 AND $level['starStars'] != 0) AND !($level['starStars'] > 9 AND $level['starStars'] < 20)) $action = 1; else $action = 2; + $difficultyField = [$this->webhookLanguage('difficultyTitle', $webhookLangArray), sprintf($this->webhookLanguage('difficultyDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $difficulty, $level['starStars']), true]; + $statsField = [$this->webhookLanguage('statsTitle', $webhookLangArray), $stats, true]; + if($level['requestedStars'] == 1) $action = 0; elseif(($level['requestedStars'] < 5 AND $level['requestedStars'] != 0) AND !($level['requestedStars'] > 9 AND $level['requestedStars'] < 20)) $action = 1; else $action = 2; + $requestedField = $level['requestedStars'] > 0 ? [$this->webhookLanguage('requestedTitle', $webhookLangArray), sprintf($this->webhookLanguage('requestedDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $level['requestedStars']), true] : []; + $descriptionField = [$this->webhookLanguage('descTitle', $webhookLangArray), (!empty($level['levelDesc']) ? ExploitPatch::url_base64_decode($level['levelDesc']) : $this->webhookLanguage('descDesc', $webhookLangArray)), false]; + $setThumbnail = $difficultiesURL.$starsIcon.'/'.$diffIcon.'.png'; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $rateTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($levelField, $IDField, $difficultyField, $statsField, $requestedField, $descriptionField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + if($dmNotifications && $creatorHasDiscord) { + $embed = $this->generateEmbedArray( + [$gdps, $authorURL, $authorIconURL], + $setColor, + [$dmTitle, $rateTitleURL], + $dmDescription, + $setThumbnail, + [$levelField, $IDField, $difficultyField, $statsField, $requestedField, $descriptionField], + [$setFooter, $footerIconURL] + ); + $json = json_encode([ + "content" => "", + "tts" => false, + "embeds" => [$embed] + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $this->sendDiscordPM($creatorHasDiscord, $json, true); + } + } + public function generateEmbedArray($author, $color, $title, $description, $thumbnail, $fieldsArray, $footer) { + if(!is_array($author) || !is_array($title) || !is_array($fieldsArray) || !is_array($footer)) return false; + $fields = []; + $author = [ + "name" => $author[0], + "url" => $author[1], + "icon_url" => $author[2] + ]; + foreach($fieldsArray AS &$field) { + if(!empty($field)) $fields[] = [ + "name" => $field[0], + "value" => $field[1], + "inline" => $field[2] + ]; + } + $footer = [ + "text" => $footer[0], + "icon_url" => $footer[1] + ]; + return [ + "type" => "rich", + "timestamp" => date("c", time()), + "title" => $title[0], + "url" => $title[1], + "color" => hexdec($color), + "description" => $description, + "thumbnail" => ["url" => $thumbnail], + "footer" => $footer, + "author" => $author, + "fields" => $fields + ]; + } + public function webhookStartLanguage($lang) { + $fileExists = file_exists(__DIR__."/../../config/webhooks/lang/".$lang.".php"); + if(!$fileExists) return false; + require __DIR__."/../../config/webhooks/lang/".$lang.".php"; + return $webhookLang; + } + public function webhookLanguage($langString, $webhookLangArray) { + if(isset($webhookLangArray[$langString])) { + if(is_array($webhookLangArray[$langString])) return $webhookLangArray[$langString][rand(0, count($webhookLangArray[$langString]) - 1)]; + else return $webhookLangArray[$langString]; + } + return $langString; + } + public function changeDifficulty($accountID, $levelID, $difficulty, $auto, $demon) { + if(!is_numeric($accountID)) return false; + require __DIR__ . "/connection.php"; + $query = "UPDATE levels SET starDemon=:demon, starAuto=:auto, starDifficulty=:diff, rateDate=:now WHERE levelID=:levelID"; + $query = $db->prepare($query); + $query->execute([':demon' => $demon, ':auto' => $auto, ':diff' => $difficulty, ':levelID'=>$levelID, ':now' => time()]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) VALUES ('1', :value, :value2, :levelID, :timestamp, :id)"); + $query->execute([':value' => $diffName, ':timestamp' => time(), ':id' => $accountID, ':value2' => 0, ':levelID' => $levelID]); + } + public function sendSuggestWebhook($modAccID, $levelID, $difficulty, $stars, $featured, $auto, $demon) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($levelID) OR !in_array("suggest", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($suggestWebhook); + $level = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $level->execute([':levelID' => $levelID]); + $level = $level->fetch(); + if(!$level) return false; + $modUsername = $this->getAccountName($modAccID); + $modHasDiscord = $this->hasDiscord($modAccID); + $modFormattedUsername = $modHasDiscord ? "<@".$modHasDiscord.">" : "**".$modUsername."**"; + $creatorAccID = $level['extID']; + $creatorUsername = $this->getAccountName($creatorAccID); + $creatorHasDiscord = $this->hasDiscord($creatorAccID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $difficulty = $this->getDifficulty($difficulty, $auto, $demon); + $starsArray = ['stars', 'featured', 'epic', 'legendary', 'mythic']; + $starsIcon = $starsArray[$featured] ?? 'stars'; + $diffArray = ['n/a' => 'na', 'auto' => 'auto', 'easy' => 'easy', 'normal' => 'normal', 'hard' => 'hard', 'harder' => 'harder', 'insane' => 'insane', 'demon' => 'demon-hard', 'easy demon' => 'demon-easy', 'medium demon' => 'demon-medium', 'hard demon' => 'demon-hard', 'insane demon' => 'demon-insane', 'extreme demon' => 'demon-extreme']; + $diffIcon = $diffArray[strtolower($difficulty)] ?? 'na'; + $originalDiffColorArray = ['na' => 'a9a9a9', 'auto' => 'f5c96b', 'easy' => '00e0ff', 'normal' => '00ff3a', 'hard' => 'ffb438', 'harder' => 'fc1f1f', 'insane' => 'f91ffc', 'demon-easy' => 'aa6bf5', 'demon-medium' => 'ac2974', 'demon-hard' => 'ff0000', 'demon-insane' => 'b31548', 'demon-extreme' => '8e0505']; + $originalDiffColor = $originalDiffColor && empty($successColor); + $setColor = empty($successColor) ? $originalDiffColorArray[$diffIcon] : $successColor; + $setTitle = $this->webhookLanguage('suggestTitle', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('suggestDesc', $webhookLangArray), $modFormattedUsername); + $stats = $downloadEmoji.' '.$level['downloads'].' • '.($level['likes'] - $level['dislikes'] >= 0 ? $likeEmoji.' '.abs($level['likes'] - $level['dislikes']) : $dislikeEmoji.' '.abs($level['likes'] - $level['dislikes'])); + $levelField = [$this->webhookLanguage('levelTitle', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$level['levelName'].'**', $creatorFormattedUsername), true]; + $IDField = [$this->webhookLanguage('levelIDTitle', $webhookLangArray), $level['levelID'], true]; + if($stars == 1) $action = 0; elseif(($stars < 5 AND $stars != 0) AND !($stars > 9 AND $stars < 20)) $action = 1; else $action = 2; + $difficultyField = [$this->webhookLanguage('difficultyTitle', $webhookLangArray), sprintf($this->webhookLanguage('difficultyDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $difficulty, $stars), true]; + $statsField = [$this->webhookLanguage('statsTitle', $webhookLangArray), $stats, true]; + if($level['requestedStars'] == 1) $action = 0; elseif(($level['requestedStars'] < 5 AND $level['requestedStars'] != 0) AND !($level['requestedStars'] > 9 AND $level['requestedStars'] < 20)) $action = 1; else $action = 2; + $requestedField = $level['requestedStars'] > 0 ? [$this->webhookLanguage('requestedTitle', $webhookLangArray), sprintf($this->webhookLanguage('requestedDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $level['requestedStars']), true] : []; + $descriptionField = [$this->webhookLanguage('descTitle', $webhookLangArray), (!empty($level['levelDesc']) ? ExploitPatch::url_base64_decode($level['levelDesc']) : $this->webhookLanguage('descDesc', $webhookLangArray)), false]; + $setThumbnail = $difficultiesURL.$starsIcon.'/'.$diffIcon.'.png'; + $setFooter = sprintf($this->webhookLanguage('footerSuggest', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($suggestNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $rateTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($levelField, $IDField, $difficultyField, $statsField, $requestedField, $descriptionField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendBanWebhook($banID, $modAccID) { + require __DIR__."/connection.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($banID) OR !in_array("ban", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($banWebhook); + $ban = $this->getBanByID($banID); + switch($ban['personType']) { + case 0: + $playerAccID = $ban['person']; + $user = $db->prepare('SELECT * FROM users WHERE extID = :ID'); + $user->execute([':ID' => $playerAccID]); + $user = $user->fetch(); + if(!$user) return false; + $playerUsername = $this->getAccountName($playerAccID); + $playerHasDiscord = $this->hasDiscord($playerAccID); + $playerFormattedUsername = $playerHasDiscord ? "<@".$playerHasDiscord.">" : "**".$playerUsername."**"; + break; + case 1: + $playerFormattedUsername = "**".$this->getUserName($ban['person'])."**"; + break; + case 2: + $playerFormattedUsername = "||**".$ban['person']."**||"; + break; + } + $modUsername = $this->getAccountName($modAccID); + $modHasDiscord = $this->hasDiscord($modAccID); + $modFormattedUsername = $modHasDiscord ? "<@".$modHasDiscord.">" : "**".$modUsername."**"; + switch($ban['banType']) { + case 0: + if($ban['isActive']) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('playerBanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerBanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerBanTopDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerBanTopDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $banThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footerBan', $webhookLangArray), $gdps); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('playerUnbanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerUnbanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerUnbanTopDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerUnbanTopDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $unbanThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + } + break; + case 1: + if($ban['isActive']) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('playerBanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerBanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerBanCreatorDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerBanCreatorDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $banThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footerBan', $webhookLangArray), $gdps); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('playerUnbanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerUnbanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerUnbanCreatorDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerUnbanCreatorDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $unbanThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + } + break; + case 2: + if($ban['isActive']) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('playerBanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerBanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerBanUploadDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerBanUploadDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $banThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footerBan', $webhookLangArray), $gdps); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('playerUnbanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerUnbanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerUnbanUploadDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerUnbanUploadDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $unbanThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + } + break; + case 3: + if($ban['isActive']) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('playerBanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerBanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerBanCommentDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerBanCommentDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $banThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footerBan', $webhookLangArray), $gdps); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('playerUnbanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerUnbanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerUnbanCommentDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerUnbanCommentDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $unbanThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + } + break; + case 4: + if($ban['isActive']) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('playerBanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerBanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerBanAccountDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerBanAccountDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $banThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footerBan', $webhookLangArray), $gdps); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('playerUnbanTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('playerUnbanTitleDM', $webhookLangArray); + $setDescription = sprintf($this->webhookLanguage('playerUnbanAccountDesc', $webhookLangArray), $modFormattedUsername, $playerFormattedUsername); + $dmDescription = sprintf($this->webhookLanguage('playerUnbanAccountDescDM', $webhookLangArray), $modFormattedUsername); + $setThumbnail = $unbanThumbnailURL; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + } + break; + } + $modField = [$this->webhookLanguage('playerModTitle', $webhookLangArray), $modFormattedUsername, true]; + $expiresField = $ban['isActive'] ? [$this->webhookLanguage('playerExpiresTitle', $webhookLangArray), '', true] : []; + $personTypeField = [$this->webhookLanguage('playerTypeTitle', $webhookLangArray), $this->webhookLanguage('playerTypeName'.$ban['personType'], $webhookLangArray), true]; + $reasonField = [$this->webhookLanguage('playerReasonTitle', $webhookLangArray), !empty($ban['reason']) ? base64_decode($ban['reason']) : $this->webhookLanguage('playerBanReason', $webhookLangArray)]; + $dw->newMessage() + ->setContent($banNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $demonlistTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($modField, $expiresField, $personTypeField, $reasonField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + if($dmNotifications && $playerHasDiscord) { + $embed = $this->generateEmbedArray( + [$gdps, $authorURL, $authorIconURL], + $setColor, + [$dmTitle, $demonlistTitleURL], + $dmDescription, + $setThumbnail, + [$modField, $expiresField, $personTypeField, $reasonField], + [$setFooter, $footerIconURL] + ); + $json = json_encode([ + "content" => "", + "tts" => false, + "embeds" => [$embed] + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $this->sendDiscordPM($playerHasDiscord, $json, true); + } + } + public function sendDailyWebhook($levelID, $type) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($levelID) OR !is_numeric($type) OR !in_array("daily", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($dailyWebhook); + $level = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $level->execute([':levelID' => $levelID]); + $level = $level->fetch(); + if(!$level) return false; + switch($type) { + case 0: + case 1: + $daily = $db->prepare('SELECT * FROM dailyfeatures WHERE levelID = :levelID AND type = :type'); + $daily->execute([':levelID' => $levelID, ':type' => $type]); + break; + case 2: + $daily = $db->prepare('SELECT * FROM events WHERE levelID = :levelID'); + $daily->execute([':levelID' => $levelID]); + break; + } + $daily = $daily->fetch(); + if(!$daily) return false; + $creatorAccID = $level['extID']; + $creatorUsername = $this->getAccountName($creatorAccID); + $creatorHasDiscord = $this->hasDiscord($creatorAccID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $difficulty = $this->getDifficulty($level['starDifficulty'], $level['starAuto'], $level['starDemon'], $level['starDemonDiff']); + $starsIcon = 'stars'; + $diffIcon = 'na'; + switch(true) { + case ($level['starEpic'] > 0): + $starsArray = ['', 'epic', 'legendary', 'mythic']; + $starsIcon = $starsArray[$level['starEpic']]; + break; + case ($level['starFeatured'] > 0): + $starsIcon = 'featured'; + break; + } + $diffArray = ['n/a' => 'na', 'auto' => 'auto', 'easy' => 'easy', 'normal' => 'normal', 'hard' => 'hard', 'harder' => 'harder', 'insane' => 'insane', 'demon' => 'demon-hard', 'easy demon' => 'demon-easy', 'medium demon' => 'demon-medium', 'hard demon' => 'demon-hard', 'insane demon' => 'demon-insane', 'extreme demon' => 'demon-extreme']; + $diffIcon = $diffArray[strtolower($difficulty)] ?? 'na'; + switch($type) { + case 0: + $setColor = $dailyColor; + $setTitle = $this->webhookLanguage('dailyTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('dailyTitleDM', $webhookLangArray); + $setDescription = $this->webhookLanguage('dailyDesc', $webhookLangArray); + $dmDescription = sprintf($this->webhookLanguage('dailyDescDM', $webhookLangArray), $tadaEmoji); + $setNotificationText = $dailyNotificationText; + break; + case 1: + $setColor = $weeklyColor; + $setTitle = $this->webhookLanguage('weeklyTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('weeklyTitleDM', $webhookLangArray); + $setDescription = $this->webhookLanguage('weeklyDesc', $webhookLangArray); + $dmDescription = sprintf($this->webhookLanguage('weeklyDescDM', $webhookLangArray), $tadaEmoji); + $setNotificationText = $weeklyNotificationText; + break; + case 2: + $setColor = $eventColor; + $setTitle = $this->webhookLanguage('eventTitle', $webhookLangArray); + $dmTitle = $this->webhookLanguage('eventTitleDM', $webhookLangArray); + $setDescription = $this->webhookLanguage('eventDesc', $webhookLangArray); + $dmDescription = sprintf($this->webhookLanguage('eventDescDM', $webhookLangArray), $tadaEmoji); + $setNotificationText = $eventNotificationText; + break; + } + $stats = $downloadEmoji.' '.$level['downloads'].' • '.($level['likes'] - $level['dislikes'] >= 0 ? $likeEmoji.' '.abs($level['likes'] - $level['dislikes']) : $dislikeEmoji.' '.abs($level['likes'] - $level['dislikes'])); + $levelField = [$this->webhookLanguage('levelTitle', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$level['levelName'].'**', $creatorFormattedUsername), true]; + $IDField = [$this->webhookLanguage('levelIDTitle', $webhookLangArray), $level['levelID'], true]; + if($level['starStars'] == 1) $action = 0; elseif(($level['starStars'] < 5 AND $level['starStars'] != 0) AND !($level['starStars'] > 9 AND $level['starStars'] < 20)) $action = 1; else $action = 2; + $difficultyField = [$this->webhookLanguage('difficultyTitle', $webhookLangArray), sprintf($this->webhookLanguage('difficultyDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $difficulty, $level['starStars']), true]; + $statsField = [$this->webhookLanguage('statsTitle', $webhookLangArray), $stats, true]; + if($level['requestedStars'] == 1) $action = 0; elseif(($level['requestedStars'] < 5 AND $level['requestedStars'] != 0) AND !($level['requestedStars'] > 9 AND $level['requestedStars'] < 20)) $action = 1; else $action = 2; + $requestedField = $level['requestedStars'] > 0 ? [$this->webhookLanguage('requestedTitle', $webhookLangArray), sprintf($this->webhookLanguage('requestedDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $level['requestedStars']), true] : []; + $descriptionField = [$this->webhookLanguage('descTitle', $webhookLangArray), (!empty($level['levelDesc']) ? ExploitPatch::url_base64_decode($level['levelDesc']) : $this->webhookLanguage('descDesc', $webhookLangArray)), false]; + $setThumbnail = $difficultiesURL.$starsIcon.'/'.$diffIcon.'.png'; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $rateTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($levelField, $IDField, $difficultyField, $statsField, $requestedField, $descriptionField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + if($dmNotifications && $creatorHasDiscord) { + $embed = $this->generateEmbedArray( + [$gdps, $authorURL, $authorIconURL], + $setColor, + [$dmTitle, $rateTitleURL], + $dmDescription, + $setThumbnail, + [$levelField, $IDField, $difficultyField, $statsField, $requestedField, $descriptionField], + [$setFooter, $footerIconURL] + ); + $json = json_encode([ + "content" => "", + "tts" => false, + "embeds" => [$embed] + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + $this->sendDiscordPM($creatorHasDiscord, $json, true); + } + } + public function getAllBans($onlyActive = true) { + require __DIR__."/connection.php"; + $bans = $db->prepare('SELECT * FROM bans'.($onlyActive ? ' AND isActive = 1' : '').' ORDER BY timestamp DESC'); + $bans->execute(); + return $bans->fetchAll(); + } + public function getAllBansFromPerson($person, $personType, $onlyActive = true) { + require __DIR__."/connection.php"; + $bans = $db->prepare('SELECT * FROM bans WHERE person = :person AND personType = :personType'.($onlyActive ? ' AND isActive = 1' : '').' ORDER BY timestamp DESC'); + $bans->execute([':person' => $person, ':personType' => $personType]); + return $bans->fetchAll(); + } + public function getAllBansOfPersonType($personType, $onlyActive = true) { + require __DIR__."/connection.php"; + $bans = $db->prepare('SELECT * FROM bans WHERE personType = :personType'.($onlyActive ? ' AND isActive = 1' : '').' ORDER BY timestamp DESC'); + $bans->execute([':personType' => $personType]); + return $bans->fetchAll(); + } + public function getAllBansOfBanType($banType, $onlyActive = true) { + require __DIR__."/connection.php"; + $bans = $db->prepare('SELECT * FROM bans WHERE banType = :banType'.($onlyActive ? ' AND isActive = 1' : '').' ORDER BY timestamp DESC'); + $bans->execute([':banType' => $banType]); + return $bans->fetchAll(); + } + public function banPerson($modID, $person, $reason, $banType, $personType, $expires) { + require __DIR__."/connection.php"; + if($banType == 4) { + switch($personType) { + case 0: + $removeAuth = $db->prepare('UPDATE accounts SET auth = "none" WHERE accountID = :accountID'); + $removeAuth->execute([':accountID' => $person]); + break; + case 2: + $banIP = $db->prepare("INSERT INTO bannedips (IP) VALUES (:IP)"); + $banIP->execute([':IP' => $person]); + break; + } + } + if($personType == 2) $person = $this->IPForBan($person); + $check = $this->getBan($person, $personType, $banType); + if($check) { + if($check['expires'] <= $expires) return $check['banID']; + $this->unbanPerson($check['banID'], $modID); + } + $reason = base64_encode($reason); + $ban = $db->prepare('INSERT INTO bans (modID, person, reason, banType, personType, expires, timestamp) VALUES (:modID, :person, :reason, :banType, :personType, :expires, :timestamp)'); + $ban->execute([':modID' => $modID, ':person' => $person, ':reason' => $reason, ':banType' => $banType, ':personType' => $personType, ':expires' => $expires, ':timestamp' => ($modID != 0 ? time() : 0)]); + $banID = $db->lastInsertId(); + if($modID != 0) { + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, value4, value5, value6, timestamp, account) VALUES ('28', :value, :value2, :value3, :value4, :value5, :value6, :timestamp, :account)"); + $query->execute([':value' => $person, ':value2' => $reason, ':value3' => $personType, ':value4' => $banType, ':value5' => $expires, ':value6' => 1, ':timestamp' => time(), ':account' => $_SESSION['accountID']]); + $this->sendBanWebhook($banID, $modID); + } + return $banID; + } + public function getBan($person, $personType, $banType) { + require __DIR__."/connection.php"; + $ban = $db->prepare('SELECT * FROM bans WHERE person = :person AND personType = :personType AND banType = :banType AND isActive = 1 ORDER BY timestamp DESC'); + $ban->execute([':person' => $person, ':personType' => $personType, ':banType' => $banType]); + return $ban->fetch(); + } + public function unbanPerson($banID, $modID) { + require __DIR__."/connection.php"; + $ban = $this->getBanByID($banID); + if($ban) { + if($ban['personType'] == 2 && $ban['banType'] == 4) { + $banIP = $db->prepare("DELETE FROM bannedips WHERE IP = :IP"); + $banIP->execute([':IP' => $ban['person']]); + } + $unban = $db->prepare('UPDATE bans SET isActive = 0 WHERE banID = :banID'); + $unban->execute([':banID' => $banID]); + if($modID != 0) { + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, value4, value5, value6, timestamp, account) VALUES ('28', :value, :value2, :value3, :value4, :value5, :value6, :timestamp, :account)"); + $query->execute([':value' => $ban['person'], ':value2' => $ban['reason'], ':value3' => $ban['personType'], ':value4' => $ban['banType'], ':value5' => $ban['expires'], ':value6' => 0, ':timestamp' => time(), ':account' => $modID]); + $this->sendBanWebhook($banID, $modID); + } + return true; + } + return false; + } + public function getBanByID($banID) { + require __DIR__."/connection.php"; + $ban = $db->prepare('SELECT * FROM bans WHERE banID = :banID'); + $ban->execute([':banID' => $banID]); + return $ban->fetch(); + } + public function getPersonBan($accountID, $userID, $banType, $IP = false) { + require __DIR__."/connection.php"; + $IP = $IP ? $this->IPForBan($IP) : $this->IPForBan($this->getIP()); + $ban = $db->prepare('SELECT * FROM bans WHERE ((person = :accountID AND personType = 0) OR (person = :userID AND personType = 1) OR (person = :IP AND personType = 2)) AND banType = :banType AND isActive = 1 ORDER BY expires DESC'); + $ban->execute([':accountID' => $accountID, ':userID' => $userID, ':IP' => $IP, ':banType' => $banType]); + return $ban->fetch(); + } + public function IPForBan($IP, $isSearch = false) { + $IP = explode('.', $IP); + return $IP[0].'.'.$IP[1].'.'.$IP[2].($isSearch ? '' : '.0'); + } + public function changeBan($banID, $modID, $reason, $expires) { + require __DIR__."/connection.php"; + $ban = $this->getBanByID($banID); + $reason = base64_encode($reason); + if($ban && $ban['isActive'] != 0) { + $unban = $db->prepare('UPDATE bans SET reason = :reason, expires = :expires WHERE banID = :banID'); + $unban->execute([':banID' => $banID, ':reason' => $reason, ':expires' => $expires]); + $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, value4, value5, value6, timestamp, account) VALUES ('28', :value, :value2, :value3, :value4, :value5, :value6, :timestamp, :account)"); + $query->execute([':value' => $ban['person'], ':value2' => $reason, ':value3' => $ban['personType'], ':value4' => $ban['banType'], ':value5' => $expires, ':value6' => 2, ':timestamp' => time(), ':account' => $modID]); + $this->sendBanWebhook($banID, $modID); + return true; + } + return false; + } + public function sendLogsRegisterWebhook($accountID) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($accountID) OR !in_array("register", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsRegisterWebhook); + $account = $db->prepare('SELECT * FROM accounts WHERE accountID = :accountID'); + $account->execute([':accountID' => $accountID]); + $account = $account->fetch(); + if(!$account) return false; + $setTitle = $this->webhookLanguage('logsRegisterTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsRegisterDesc', $webhookLangArray); + $usernameField = [$this->webhookLanguage('logsUsernameField', $webhookLangArray), $account['userName'], true]; + $playerIDField = [$this->webhookLanguage('logsPlayerIDField', $webhookLangArray), $accountID.' • '.$this->getUserID($accountID, $account['userName']), true]; // Yes, this line creates userID for account. Yes, i did this on purpose. + $isActivatedField = [$this->webhookLanguage('logsIsActivatedField', $webhookLangArray), $this->webhookLanguage('logsRegister'.($account['isActive'] ? 'Yes' : 'No'), $webhookLangArray), true]; + $registerTimeField = [$this->webhookLanguage('logsRegisterTimeField', $webhookLangArray), '', false]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($logsRegisterNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($logsRegisterColor) + ->setTitle($setTitle, $logsRegisterTitleURL) + ->setDescription($setDescription) + ->setThumbnail($logsRegisterThumbnailURL) + ->addFields($usernameField, $playerIDField, $isActivatedField, $registerTimeField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendLogsLevelChangeWebhook($levelID, $whoChangedID, $levelData = []) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($levelID) OR !in_array("levels", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsLevelChangeWebhook); + $level = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $level->execute([':levelID' => $levelID]); + $level = $level->fetch(); + $isDeleted = false; + if(!$level) { + if(empty($levelData)) return false; + $isDeleted = true; + $level = $levelData; + } + $creatorAccID = $level['extID']; + $creatorUsername = $this->getAccountName($creatorAccID); + $creatorHasDiscord = $this->hasDiscord($creatorAccID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $whoChangedUsername = $this->getAccountName($whoChangedID); + $whoChangedHasDiscord = $this->hasDiscord($whoChangedID); + $whoChangedFormattedUsername = $whoChangedHasDiscord ? "<@".$whoChangedHasDiscord.">" : "**".$whoChangedUsername."**"; + $difficulty = $this->getDifficulty($level['starDifficulty'], $level['starAuto'], $level['starDemon'], $level['starDemonDiff']); + $starsIcon = 'stars'; + $diffIcon = 'na'; + switch(true) { + case ($level['starEpic'] > 0): + $starsArray = ['', 'epic', 'legendary', 'mythic']; + $starsIcon = $starsArray[$level['starEpic']]; + break; + case ($level['starFeatured'] > 0): + $starsIcon = 'featured'; + break; + } + $diffArray = ['n/a' => 'na', 'auto' => 'auto', 'easy' => 'easy', 'normal' => 'normal', 'hard' => 'hard', 'harder' => 'harder', 'insane' => 'insane', 'demon' => 'demon-hard', 'easy demon' => 'demon-easy', 'medium demon' => 'demon-medium', 'hard demon' => 'demon-hard', 'insane demon' => 'demon-insane', 'extreme demon' => 'demon-extreme']; + $diffIcon = $diffArray[strtolower($difficulty)] ?? 'na'; + $newLevelNameField = $newExtIDField = $newLevelDescField = $newSongIDField = $newAudioTrackField = $newPasswordField = $newStarCoinsField = $newUnlistedField = $newUnlisted2Field = $newUpdateLockedField = $newCommentLockedField = []; + $whoChangedField = [$this->webhookLanguage('logsLevelChangeWhoField', $webhookLangArray), $whoChangedFormattedUsername, false]; + $whatWasChangedField = [$this->webhookLanguage('logsWhatWasChangedField', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$level['levelName'].'**', $creatorFormattedUsername).', *'.$level['levelID'].'*', false]; + $setNotificationText = $logsLevelChangedNotificationText; + if($isDeleted || empty($levelData)) { + if($isDeleted) { + $whatWasChangedField = []; + $setColor = $failColor; + $setTitle = $this->webhookLanguage('logsLevelDeletedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsLevelDeletedDesc', $webhookLangArray); + } else { + $whoChangedField = $whatWasChangedField = []; + $setColor = $successColor; + $setTitle = $this->webhookLanguage('logsLevelUploadedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsLevelUploadedDesc', $webhookLangArray); + } + $stats = $downloadEmoji.' '.$level['downloads'].' • '.($level['likes'] - $level['dislikes'] >= 0 ? $likeEmoji.' '.abs($level['likes'] - $level['dislikes']) : $dislikeEmoji.' '.abs($level['likes'] - $level['dislikes'])); + $levelField = [$this->webhookLanguage('levelTitle', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$level['levelName'].'**', $creatorFormattedUsername), true]; + $IDField = [$this->webhookLanguage('levelIDTitle', $webhookLangArray), $level['levelID'], true]; + if($level['starStars'] == 1) $action = 0; elseif(($level['starStars'] < 5 AND $level['starStars'] != 0) AND !($level['starStars'] > 9 AND $level['starStars'] < 20)) $action = 1; else $action = 2; + $difficultyField = [$this->webhookLanguage('difficultyTitle', $webhookLangArray), sprintf($this->webhookLanguage('difficultyDesc' . ($level['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $difficulty, $level['starStars']), true]; + $statsField = [$this->webhookLanguage('statsTitle', $webhookLangArray), $stats, true]; + if($level['requestedStars'] == 1) $action = 0; elseif(($level['requestedStars'] < 5 AND $level['requestedStars'] != 0) AND !($level['requestedStars'] > 9 AND $level['requestedStars'] < 20)) $action = 1; else $action = 2; + $requestedField = $level['requestedStars'] > 0 ? [$this->webhookLanguage('requestedTitle', $webhookLangArray), sprintf($this->webhookLanguage('requestedDesc'.$action, $webhookLangArray), $level['requestedStars']), true] : []; + $descriptionField = [$this->webhookLanguage('descTitle', $webhookLangArray), (!empty($level['levelDesc']) ? ExploitPatch::url_base64_decode($level['levelDesc']) : $this->webhookLanguage('descDesc', $webhookLangArray)), false]; + $songInfo = $this->getSongInfo($level['songID']); + $newSongIDField = [$this->webhookLanguage('songTitle', $webhookLangArray), (!empty($level['songID']) ? '**'.$songInfo['authorName'].'** — **'.$songInfo['name'].'**, *'.$songInfo['ID'].'*' : '**'.str_replace(' by ', '** by **', $this->getAudioTrack($level['audioTrack'])).'**'), true]; + $unlistedArray = [$this->webhookLanguage('levelIsPublic', $webhookLangArray), $this->webhookLanguage('levelOnlyForFriends', $webhookLangArray), $this->webhookLanguage('levelIsUnlisted', $webhookLangArray)]; + $unlistedText = $unlistedArray[$level['unlisted']]; + $newUnlistedField = [$this->webhookLanguage('unlistedTitle', $webhookLangArray), $unlistedText, true]; + } else { + $setColor = $pendingColor; + $setTitle = $this->webhookLanguage('logsLevelChangedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsLevelChangedDesc', $webhookLangArray); + if($levelData['levelName'] != $level['levelName']) $newLevelNameField = [$this->webhookLanguage('logsLevelChangeNameField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeNameValue', $webhookLangArray), '`'.$levelData['levelName'].'`', '`'.$level['levelName'].'`'), false]; + if($levelData['extID'] != $level['extID']) $newExtIDField = [$this->webhookLanguage('logsLevelChangeExtIDField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeExtIDValue', $webhookLangArray), '`'.$levelData['extID'].'`', '`'.$level['extID'].'`'), false]; + if($levelData['levelDesc'] != $level['levelDesc']) $newLevelDescField = [$this->webhookLanguage('logsLevelChangeDescField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeDescValue', $webhookLangArray), (!empty($levelData['levelDesc']) ? '`'.ExploitPatch::url_base64_decode($levelData['levelDesc']).'`' : $this->webhookLanguage('descDesc', $webhookLangArray)), (!empty($level['levelDesc']) ? '`'.ExploitPatch::url_base64_decode($level['levelDesc']).'`' : $this->webhookLanguage('descDesc', $webhookLangArray))), false]; + if($levelData['songID'] != $level['songID']) $newSongIDField = [$this->webhookLanguage('logsLevelChangeSongIDField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeSongIDValue', $webhookLangArray), '`'.$levelData['songID'].'`', '`'.$level['songID'].'`'), false]; + if($levelData['audioTrack'] != $level['audioTrack']) $newAudioTrackField = [$this->webhookLanguage('logsLevelChangeAudioTrackField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeAudioTrackValue', $webhookLangArray), '`'.$this->getAudioTrack($levelData['audioTrack']).'`', '`'.$this->getAudioTrack($level['audioTrack']).'`'), false]; + if($levelData['password'] != $level['password']) $newPasswordField = [$this->webhookLanguage('logsLevelChangePasswordField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangePasswordValue', $webhookLangArray), '`'.$levelData['password'].'`', '`'.$level['password'].'`'), false]; + if($levelData['starCoins'] != $level['starCoins']) $newStarCoinsField = [$this->webhookLanguage('logsLevelChangeCoinsField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeCoinsValue', $webhookLangArray), ($levelData['starCoins'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray)), ($level['starCoins'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray))), false]; + if($levelData['unlisted'] != $level['unlisted']) { + $unlistedArray = [$this->webhookLanguage('levelIsPublic', $webhookLangArray), $this->webhookLanguage('levelOnlyForFriends', $webhookLangArray), $this->webhookLanguage('levelIsUnlisted', $webhookLangArray)]; + $oldUnlistedText = $unlistedArray[$levelData['unlisted']]; + $newUnlistedText = $unlistedArray[$level['unlisted']]; + $newUnlistedField = [$this->webhookLanguage('logsLevelChangeUnlistedField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeUnlistedValue', $webhookLangArray), $oldUnlistedText, $newUnlistedText), true]; + } + // I don't think we need this, but i already made it lol // if($levelData['unlisted2'] != $level['unlisted2']) $newUnlisted2Field = [$this->webhookLanguage('logsLevelChangeUnlisted2Field', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeUnlisted2Value', $webhookLangArray), ($levelData['unlisted2'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray)), ($level['unlisted2'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray))), false]; + if($levelData['updateLocked'] != $level['updateLocked']) $newUpdateLockedField = [$this->webhookLanguage('logsLevelChangeUpdateLockedField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeUpdateLockedValue', $webhookLangArray), ($levelData['updateLocked'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray)), ($level['updateLocked'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray))), false]; + if($levelData['commentLocked'] != $level['commentLocked']) $newCommentLockedField = [$this->webhookLanguage('logsLevelChangeCommentLockedField', $webhookLangArray), sprintf($this->webhookLanguage('logsLevelChangeCommentLockedValue', $webhookLangArray), ($levelData['commentLocked'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray)), ($level['commentLocked'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray))), false]; + } + $setThumbnail = $difficultiesURL.$starsIcon.'/'.$diffIcon.'.png'; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $logsLevelChangeTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($whoChangedField, $whatWasChangedField, $levelField, $IDField, $difficultyField, $statsField, $requestedField, $descriptionField, $newLevelNameField, $newExtIDField, $newLevelDescField, $newSongIDField, $newAudioTrackField, $newPasswordField, $newStarCoinsField, $newUnlistedField, $newUnlisted2Field, $newUpdateLockedField, $newCommentLockedField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendLogsAccountChangeWebhook($accountID, $whoChangedID, $accountData) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($accountID) OR empty($accountData) OR !in_array("account", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsAccountChangeWebhook); + $newAccountData = $db->prepare('SELECT * FROM accounts WHERE accountID = :accountID'); + $newAccountData->execute([':accountID' => $accountID]); + $newAccountData = $newAccountData->fetch(); + $creatorUsername = $this->getAccountName($accountID); + $creatorHasDiscord = $this->hasDiscord($accountID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $whoChangedUsername = $this->getAccountName($whoChangedID); + $whoChangedHasDiscord = $this->hasDiscord($whoChangedID); + $whoChangedFormattedUsername = $whoChangedHasDiscord ? "<@".$whoChangedHasDiscord.">" : "**".$whoChangedUsername."**"; + $whoChangedField = [$this->webhookLanguage('logsAccountChangeWhoField', $webhookLangArray), $whoChangedFormattedUsername, false]; + $whatWasChangedField = [$this->webhookLanguage('logsWhatWasChangedField', $webhookLangArray), $creatorFormattedUsername.', *'.$newAccountData['accountID'].'*', false]; + $newUsernameField = $newPasswordField = $newMSField = $newFRSField = $newCSField = $newYoutubeField = $newTwitterField = $newTwitchField = $newIsActivatedField = []; + $setColor = $pendingColor; + $setTitle = $this->webhookLanguage('logsAccountChangedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsAccountChangedDesc', $webhookLangArray); + $setNotificationText = $logsAccountChangedNotificationText; + if($newAccountData['userName'] != $accountData['userName']) $newUsernameField = [$this->webhookLanguage('logsAccountChangeUsernameField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeUsernameValue'), $accountData['userName'], $newAccountData['userName']), false]; + if($newAccountData['password'] != $accountData['password']) $newPasswordField = [$this->webhookLanguage('logsAccountChangePasswordField', $webhookLangArray), $this->webhookLanguage('logsAccountChangePasswordValue', $webhookLangArray), false]; + if($newAccountData['mS'] != $accountData['mS']) { + $msArray = [$this->webhookLanguage('mS0', $webhookLangArray), $this->webhookLanguage('mS1', $webhookLangArray), $this->webhookLanguage('mS2', $webhookLangArray)]; + $oldMS = $msArray[$accountData['mS']]; + $newMS = $msArray[$newAccountData['mS']]; + $newMSField = [$this->webhookLanguage('logsAccountChangeMSField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeMSValue', $webhookLangArray), $oldMS, $newMS), false]; + } + if($newAccountData['frS'] != $accountData['frS']) { + $frsArray = [$this->webhookLanguage('frS0', $webhookLangArray), $this->webhookLanguage('frS1', $webhookLangArray)]; + $oldFRS = $frsArray[$accountData['frS']]; + $newFRS = $frsArray[$newAccountData['frS']]; + $newFRSField = [$this->webhookLanguage('logsAccountChangeFRSField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeFRSValue', $webhookLangArray), $oldFRS, $newFRS), false]; + } + if($newAccountData['cS'] != $accountData['cS']) { + $csArray = [$this->webhookLanguage('cS0', $webhookLangArray), $this->webhookLanguage('cS1', $webhookLangArray), $this->webhookLanguage('cS2', $webhookLangArray)]; + $oldCS = $csArray[$accountData['cS']]; + $newCS = $csArray[$newAccountData['cS']]; + $newCSField = [$this->webhookLanguage('logsAccountChangeCSField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeCSValue', $webhookLangArray), $oldCS, $newCS), false]; + } + if($newAccountData['youtubeurl'] != $accountData['youtubeurl']) $newYoutubeField = [$this->webhookLanguage('logsAccountChangeYTField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeYTValue', $webhookLangArray), (!empty($accountData['youtubeurl']) ? $accountData['youtubeurl'] : $this->webhookLanguage('logsAccountChangeNoYT', $webhookLangArray)), (!empty($newAccountData['youtubeurl']) ? $newAccountData['youtubeurl'] : $this->webhookLanguage('logsAccountChangeNoYT', $webhookLangArray))), false]; + if($newAccountData['twitter'] != $accountData['twitter']) $newTwitterField = [$this->webhookLanguage('logsAccountChangeTWField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeTWValue', $webhookLangArray), (!empty($accountData['twitter']) ? $accountData['twitter'] : $this->webhookLanguage('logsAccountChangeNoTW', $webhookLangArray)), (!empty($newAccountData['twitter']) ? $newAccountData['twitter'] : $this->webhookLanguage('logsAccountChangeNoTW', $webhookLangArray))), false]; + if($newAccountData['twitch'] != $accountData['twitch']) $newTwitchField = [$this->webhookLanguage('logsAccountChangeTTVField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeTTVValue', $webhookLangArray), (!empty($accountData['twitch']) ? $accountData['twitch'] : $this->webhookLanguage('logsAccountChangeNoTTV', $webhookLangArray)), (!empty($newAccountData['twitch']) ? $newAccountData['twitch'] : $this->webhookLanguage('logsAccountChangeNoTTV', $webhookLangArray))), false]; + if($newAccountData['isActive'] != $accountData['isActive']) $newIsActivatedField = [$this->webhookLanguage('logsAccountChangeActiveField', $webhookLangArray), sprintf($this->webhookLanguage('logsAccountChangeActiveValue', $webhookLangArray), $this->webhookLanguage('logsRegister'.($accountData['isActive'] ? 'Yes' : 'No'), $webhookLangArray), $this->webhookLanguage('logsRegister'.($newAccountData['isActive'] ? 'Yes' : 'No'), $webhookLangArray)), true]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $logsAccountChangeTitleURL) + ->setDescription($setDescription) + ->setThumbnail($logsAccountChangeThumbnailURL) + ->addFields($whoChangedField, $whatWasChangedField, $newUsernameField, $newPasswordField, $newMSField, $newFRSField, $newCSField, $newYoutubeField, $newTwitterField, $newTwitchField, $newIsActivatedField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendLogsListChangeWebhook($listID, $whoChangedID, $listData = []) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($listID) OR !in_array("lists", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsListChangeWebhook); + $newListData = $db->prepare('SELECT * FROM lists WHERE listID = :listID'); + $newListData->execute([':listID' => $listID]); + $newListData = $newListData->fetch(); + $isDeleted = false; + if(!$newListData) { + if(empty($listData)) return false; + $isDeleted = true; + $newListData = $listData; + } + $accountID = $newListData['accountID']; + $creatorUsername = $this->getAccountName($accountID); + $creatorHasDiscord = $this->hasDiscord($accountID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $whoChangedUsername = $this->getAccountName($whoChangedID); + $whoChangedHasDiscord = $this->hasDiscord($whoChangedID); + $whoChangedFormattedUsername = $whoChangedHasDiscord ? "<@".$whoChangedHasDiscord.">" : "**".$whoChangedUsername."**"; + $whoChangedField = [$this->webhookLanguage('logsListChangeWhoField', $webhookLangArray), $whoChangedFormattedUsername, false]; + $whatWasChangedField = [$this->webhookLanguage('logsWhatWasChangedField', $webhookLangArray), $creatorFormattedUsername.', *'.$newListData['listID'].'*', false]; + $setNotificationText = $logsListChangedNotificationText; + $diffArray = ['n/a' => 'na', 'auto' => 'auto', 'easy' => 'easy', 'normal' => 'normal', 'hard' => 'hard', 'harder' => 'harder', 'insane' => 'insane', 'demon' => 'demon-hard', 'easy demon' => 'demon-easy', 'medium demon' => 'demon-medium', 'hard demon' => 'demon-hard', 'insane demon' => 'demon-insane', 'extreme demon' => 'demon-extreme']; + $starsIcon = $newListData['starFeatured'] > 0 ? 'featured' : 'stars'; + $diffIcon = $diffArray[strtolower($this->getListDiffName($newListData['starDifficulty']))] ?? 'na'; + if($isDeleted || empty($listData)) { + if($isDeleted) { + $whatWasChangedField = []; + $setColor = $failColor; + $setTitle = $this->webhookLanguage('logsListDeletedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsListDeletedDesc', $webhookLangArray); + } else { + $whoChangedField = $whatWasChangedField = []; + $setColor = $successColor; + $setTitle = $this->webhookLanguage('logsListUploadedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsListUploadedDesc', $webhookLangArray); + } + $stats = $downloadEmoji.' '.$newListData['downloads'].' • '.($newListData['likes'] - $newListData['dislikes'] >= 0 ? $likeEmoji.' '.abs($newListData['likes'] - $newListData['dislikes']) : $dislikeEmoji.' '.abs($newListData['likes'] - $newListData['dislikes'])); + $listField = [$this->webhookLanguage('listTitle', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$newListData['listName'].'**', $creatorFormattedUsername), true]; + $IDField = [$this->webhookLanguage('listIDTitle', $webhookLangArray), $newListData['listID'], true]; + $actions = $newListData['starStars'][strlen($newListData['starStars'])-1] ?? $newListData['starStars']; + if($actions == 1) $action = 0; elseif($actions < 5 AND $actions != 0 AND !($newListData['starStars'] > 9 AND $newListData['starStars'] < 20)) $action = 1; else $action = 2; + $difficultyField = [$this->webhookLanguage('difficultyTitle', $webhookLangArray), sprintf($this->webhookLanguage('difficultyListDesc'.$action, $webhookLangArray), $this->getListDiffName($newListData['starDifficulty']), $newListData['starStars']), true]; + $statsField = [$this->webhookLanguage('statsTitle', $webhookLangArray), $stats, true]; + $descriptionField = [$this->webhookLanguage('descTitle', $webhookLangArray), (!empty($newListData['listDesc']) ? ExploitPatch::url_base64_decode($newListData['listDesc']) : $this->webhookLanguage('descDesc', $webhookLangArray)), false]; + $unlistedArray = [$this->webhookLanguage('listIsPublic', $webhookLangArray), $this->webhookLanguage('listOnlyForFriends', $webhookLangArray), $this->webhookLanguage('listIsUnlisted', $webhookLangArray)]; + $unlistedText = $unlistedArray[$newListData['unlisted']] ?? $unlistedArray[1]; + $newUnlistedField = [$this->webhookLanguage('unlistedListTitle', $webhookLangArray), $unlistedText, true]; + } else { + $setColor = $pendingColor; + $setTitle = $this->webhookLanguage('logsListChangedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsListChangedDesc', $webhookLangArray); + if($listData['listName'] != $newListData['listName']) $newListNameField = [$this->webhookLanguage('logsListChangeNameField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeNameValue', $webhookLangArray), '`'.$listData['listName'].'`', '`'.$newListData['listName'].'`'), true]; + if($listData['accountID'] != $newListData['accountID']) $newExtIDField = [$this->webhookLanguage('logsListChangeAccountIDField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeAccountIDValue', $webhookLangArray), '`'.$listData['accountID'].'`', '`'.$newListData['accountID'].'`'), true]; + if($listData['listDesc'] != $newListData['listDesc']) $newListDescField = [$this->webhookLanguage('logsListChangeDescField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeDescValue', $webhookLangArray), (!empty($listData['listDesc']) ? '`'.ExploitPatch::url_base64_decode($listData['listDesc']).'`' : $this->webhookLanguage('descDesc', $webhookLangArray)), (!empty($newListData['listDesc']) ? '`'.ExploitPatch::url_base64_decode($newListData['listDesc']).'`' : $this->webhookLanguage('descDesc', $webhookLangArray))), false]; + if($listData['starStars'] != $newListData['starStars']) { + $oldReward = $listData['starStars'][strlen($listData['starStars'])-1] ?? $listData['starStars']; + if($oldReward == 1) $action = 0; elseif($oldReward < 5 AND $oldReward != 0 AND !($listData['starStars'] > 9 AND $listData['starStars'] < 20)) $action = 1; else $action = 2; + $oldReward = sprintf($this->webhookLanguage('logsListChangeReward'.$action, $webhookLangArray), $listData['starStars']); + $newReward = $newListData['starStars'][strlen($newListData['starStars'])-1] ?? $newListData['starStars']; + if($newReward == 1) $action = 0; elseif($newReward < 5 AND $newReward != 0 AND !($newListData['starStars'] > 9 AND $newListData['starStars'] < 20)) $action = 1; else $action = 2; + $newReward = sprintf($this->webhookLanguage('logsListChangeReward'.$action, $webhookLangArray), $newListData['starStars']); + $newRewardField = [$this->webhookLanguage('logsListChangeRewardField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeRewardValue', $webhookLangArray), $oldReward, $newReward), true]; + } + if($listData['unlisted'] != $newListData['unlisted']) { + $unlistedArray = [$this->webhookLanguage('listIsPublic', $webhookLangArray), $this->webhookLanguage('listOnlyForFriends', $webhookLangArray), $this->webhookLanguage('listIsUnlisted', $webhookLangArray)]; + $oldUnlistedText = $unlistedArray[$listData['unlisted']] ?? $unlistedArray[1]; + $newUnlistedText = $unlistedArray[$newListData['unlisted']] ?? $unlistedArray[1]; + $newUnlistedField = [$this->webhookLanguage('logsListChangeUnlistedField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeUnlistedValue', $webhookLangArray), $oldUnlistedText, $newUnlistedText), false]; + } + if($listData['starDifficulty'] != $newListData['starDifficulty']) { + $oldDiffText = $this->getListDiffName($listData['starDifficulty']); + $newDiffText = $this->getListDiffName($newListData['starDifficulty']); + $newDiffField = [$this->webhookLanguage('logsListChangeDiffField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeDiffValue', $webhookLangArray), $oldDiffText, $newDiffText), true]; + } + if($listData['listlevels'] != $newListData['listlevels']) $newLevelsField = [$this->webhookLanguage('logsListChangeLevelsField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeLevelsValue', $webhookLangArray), $listData['listlevels'], $newListData['listlevels']), false]; + if($listData['countForReward'] != $newListData['countForReward']) { + $oldReward = $listData['countForReward'][strlen($listData['countForReward'])-1] ?? $listData['countForReward']; + if($oldReward == 1) $action = 0; elseif($oldReward < 5 AND $oldReward != 0 AND !($listData['countForReward'] > 9 AND $listData['countForReward'] < 20)) $action = 1; else $action = 2; + $oldReward = sprintf($this->webhookLanguage('logsListChangeRewardCount'.$action, $webhookLangArray), $listData['countForReward']); + $newReward = $newListData['countForReward'][strlen($newListData['countForReward'])-1] ?? $newListData['countForReward']; + if($newReward == 1) $action = 0; elseif($newReward < 5 AND $newReward != 0 AND !($newListData['countForReward'] > 9 AND $newListData['countForReward'] < 20)) $action = 1; else $action = 2; + $newReward = sprintf($this->webhookLanguage('logsListChangeRewardCount'.$action, $webhookLangArray), $newListData['countForReward']); + $newRewardCountField = [$this->webhookLanguage('logsListChangeRewardCountField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeRewardCountValue', $webhookLangArray), $oldReward, $newReward), true]; + } + if($listData['commentLocked'] != $newListData['commentLocked']) $newCommentLockedField = [$this->webhookLanguage('logsListChangeCommentLockedField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeCommentLockedValue', $webhookLangArray), ($listData['commentLocked'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray)), ($newListData['commentLocked'] ? $this->webhookLanguage('logsRegisterYes', $webhookLangArray) : $this->webhookLanguage('logsRegisterNo', $webhookLangArray))), false]; + } + $setThumbnail = $difficultiesURL.$starsIcon.'/'.$diffIcon.'.png'; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $logsListChangeTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($whoChangedField, $whatWasChangedField, $listField, $IDField, $difficultyField, $statsField, $descriptionField, $newUnlistedField, $newListNameField, $newExtIDField, $newListDescField, $newLevelsField, $newRewardField, $newDiffField, $newRewardCountField, $newCommentLockedField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendLogsModChangeWebhook($modID, $whoChangedID, $assignID, $modData = []) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($modID) OR !is_numeric($assignID) OR !in_array("mods", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsModChangeWebhook); + $newAssignData = $db->prepare('SELECT * FROM roleassign WHERE assignID = :assignID'); + $newAssignData->execute([':assignID' => $assignID]); + $newAssignData = $newAssignData->fetch(); + $isDeleted = false; + if(!$newAssignData) { + if(empty($modData)) return false; + $isDeleted = true; + $newAssignData = $modData; + } + $creatorUsername = $this->getAccountName($modID); + $creatorHasDiscord = $this->hasDiscord($modID); + $creatorFormattedUsername = $creatorHasDiscord ? "<@".$creatorHasDiscord.">" : "**".$creatorUsername."**"; + $whoChangedUsername = $this->getAccountName($whoChangedID); + $whoChangedHasDiscord = $this->hasDiscord($whoChangedID); + $whoChangedFormattedUsername = $whoChangedHasDiscord ? "<@".$whoChangedHasDiscord.">" : "**".$whoChangedUsername."**"; + $whoChangedField = [$this->webhookLanguage('logsModChangeWhoField', $webhookLangArray), $whoChangedFormattedUsername, false]; + $whatWasChangedField = [$this->webhookLanguage('logsWhatWasChangedField', $webhookLangArray), $creatorFormattedUsername.', *'.$modID.'*', false]; + $setNotificationText = $logsModChangedNotificationText; + if($isDeleted || empty($modData)) { + if($isDeleted) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('logsModDemotedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsModDemotedDesc', $webhookLangArray); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('logsModPromotedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsModPromotedDesc', $webhookLangArray); + } + $getRole = $db->prepare("SELECT roleName FROM roles WHERE roleID = :roleID"); + $getRole->execute([':roleID' => $newAssignData['roleID']]); + $getRole = $getRole->fetchColumn(); + if(empty($getRole)) $getRole = $this->webhookLanguage('logsModChangeRoleUnknown', $webhookLangArray); + $roleField = [$this->webhookLanguage('roleField', $webhookLangArray), $getRole, true]; + } else { + $setColor = $pendingColor; + $setTitle = $this->webhookLanguage('logsModChangedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsModChangedDesc', $webhookLangArray); + if($modData['roleID'] != $newAssignData['roleID']) { + $getRole = $db->prepare("SELECT roleName FROM roles WHERE roleID = :roleID"); + $getRole->execute([':roleID' => $modData['roleID']]); + $oldRole = $getRole->fetchColumn(); + if(empty($oldRole)) $oldRole = $this->webhookLanguage('logsModChangeRoleUnknown', $webhookLangArray); + $getRole = $db->prepare("SELECT roleName FROM roles WHERE roleID = :roleID"); + $getRole->execute([':roleID' => $newAssignData['roleID']]); + $newRole = $getRole->fetchColumn(); + if(empty($newRole)) $newRole = $this->webhookLanguage('logsModChangeRoleUnknown', $webhookLangArray); + $roleField = [$this->webhookLanguage('logsModChangeRoleField', $webhookLangArray), sprintf($this->webhookLanguage('logsModChangeRoleValue', $webhookLangArray), $oldRole, $newRole), true]; + } + } + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $logsModChangeTitleURL) + ->setDescription($setDescription) + ->setThumbnail($logsModChangeThumbnailURL) + ->addFields($whoChangedField, $whatWasChangedField, $roleField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendLogsGauntletChangeWebhook($gauntletID, $whoChangedID, $gauntletData = []) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($gauntletID) OR !in_array("gauntlets", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsGauntletChangeWebhook); + $newGauntletData = $db->prepare('SELECT * FROM gauntlets WHERE ID = :gauntletID'); + $newGauntletData->execute([':gauntletID' => $gauntletID]); + $newGauntletData = $newGauntletData->fetch(); + $isDeleted = false; + if(!$newGauntletData) { + if(empty($gauntletData)) return false; + $isDeleted = true; + $newGauntletData = $gauntletData; + } + $whoChangedUsername = $this->getAccountName($whoChangedID); + $whoChangedHasDiscord = $this->hasDiscord($whoChangedID); + $whoChangedFormattedUsername = $whoChangedHasDiscord ? "<@".$whoChangedHasDiscord.">" : "**".$whoChangedUsername."**"; + $whoChangedField = [$this->webhookLanguage('logsGauntletChangeWhoField', $webhookLangArray), $whoChangedFormattedUsername, false]; + $whatWasChangedField = [$this->webhookLanguage('logsWhatWasChangedField', $webhookLangArray), $this->getGauntletName($newGauntletData['ID']).' Gauntlet', false]; + $setNotificationText = $logsGauntletChangedNotificationText; + if($isDeleted || empty($gauntletData)) { + $whatWasChangedField = []; + if($isDeleted) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('logsGauntletDeletedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsGauntletDeletedDesc', $webhookLangArray); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('logsGauntletCreatedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsGauntletCreatedDesc', $webhookLangArray); + } + $gauntletName = [$this->webhookLanguage('gauntletNameField', $webhookLangArray), $this->getGauntletName($newGauntletData['ID']).' Gauntlet', false]; + $getLevels = $db->prepare('SELECT levelName, levelID, extID FROM levels WHERE levelID IN ('.$newGauntletData['level1'].', '.$newGauntletData['level2'].', '.$newGauntletData['level3'].', '.$newGauntletData['level4'].', '.$newGauntletData['level5'].')'); + $getLevels->execute(); + $getLevels = $getLevels->fetchAll(); + $level1Author = $getLevels[0]['extID']; + $level1AuthorUsername = $this->getAccountName($level1Author); + $level1AuthorHasDiscord = $this->hasDiscord($level1Author); + $level1AuthorFormattedUsername = $level1AuthorHasDiscord ? "<@".$level1AuthorHasDiscord.">" : "**".$level1AuthorUsername."**"; + $level2Author = $getLevels[1]['extID']; + $level2AuthorUsername = $this->getAccountName($level2Author); + $level2AuthorHasDiscord = $this->hasDiscord($level2Author); + $level2AuthorFormattedUsername = $level2AuthorHasDiscord ? "<@".$level2AuthorHasDiscord.">" : "**".$level2AuthorUsername."**"; + $level3Author = $getLevels[2]['extID']; + $level3AuthorUsername = $this->getAccountName($level3Author); + $level3AuthorHasDiscord = $this->hasDiscord($level3Author); + $level3AuthorFormattedUsername = $level3AuthorHasDiscord ? "<@".$level3AuthorHasDiscord.">" : "**".$level3AuthorUsername."**"; + $level4Author = $getLevels[3]['extID']; + $level4AuthorUsername = $this->getAccountName($level4Author); + $level4AuthorHasDiscord = $this->hasDiscord($level4Author); + $level4AuthorFormattedUsername = $level4AuthorHasDiscord ? "<@".$level4AuthorHasDiscord.">" : "**".$level4AuthorUsername."**"; + $level5Author = $getLevels[4]['extID']; + $level5AuthorUsername = $this->getAccountName($level5Author); + $level5AuthorHasDiscord = $this->hasDiscord($level5Author); + $level5AuthorFormattedUsername = $level5AuthorHasDiscord ? "<@".$level5AuthorHasDiscord.">" : "**".$level5AuthorUsername."**"; + $level1Field = [$this->webhookLanguage('level1Field', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $getLevels[0]['levelName'], $level1AuthorFormattedUsername).', *'.$getLevels[0]['levelID'].'*', false]; + $level2Field = [$this->webhookLanguage('level2Field', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $getLevels[1]['levelName'], $level2AuthorFormattedUsername).', *'.$getLevels[1]['levelID'].'*', false]; + $level3Field = [$this->webhookLanguage('level3Field', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $getLevels[2]['levelName'], $level3AuthorFormattedUsername).', *'.$getLevels[2]['levelID'].'*', false]; + $level4Field = [$this->webhookLanguage('level4Field', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $getLevels[3]['levelName'], $level4AuthorFormattedUsername).', *'.$getLevels[3]['levelID'].'*', false]; + $level5Field = [$this->webhookLanguage('level5Field', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $getLevels[4]['levelName'], $level5AuthorFormattedUsername).', *'.$getLevels[4]['levelID'].'*', false]; + } else { + $setColor = $pendingColor; + $setTitle = $this->webhookLanguage('logsGauntletChangedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsGauntletChangedDesc', $webhookLangArray); + if($gauntletData['ID'] != $newGauntletData['ID']) $level1Field = [$this->webhookLanguage('logsGauntletChangeGauntletField', $webhookLangArray), sprintf($this->webhookLanguage('logsGauntletChangeGauntletValue', $webhookLangArray), $this->getGauntletName($gauntletData['ID']).' Gauntlet', $this->getGauntletName($newGauntletData['ID']).' Gauntlet'), false]; + if($gauntletData['level1'] != $newGauntletData['level1']) { + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $gauntletData['level1']]); + $oldLevel1 = $getLevel->fetch(); + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $newGauntletData['level1']]); + $newLevel1 = $getLevel->fetch(); + $oldLevel1AuthorUsername = $this->getAccountName($oldLevel1['extID']); + $oldLevel1AuthorHasDiscord = $this->hasDiscord($oldLevel1['extID']); + $oldLevel1AuthorFormattedUsername = $oldLevel1AuthorHasDiscord ? "<@".$oldLevel1AuthorHasDiscord.">" : "**".$oldLevel1AuthorUsername."**"; + $newLevel1AuthorUsername = $this->getAccountName($newLevel1['extID']); + $newLevel1AuthorHasDiscord = $this->hasDiscord($newLevel1['extID']); + $newLevel1AuthorFormattedUsername = $newLevel1AuthorHasDiscord ? "<@".$newLevel1AuthorHasDiscord.">" : "**".$newLevel1AuthorUsername."**"; + $level1Field = [$this->webhookLanguage('logsGauntletChangeLevel1Field', $webhookLangArray), sprintf($this->webhookLanguage('logsGauntletChangeLevelValue', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $oldLevel1['levelName'], $oldLevel1AuthorFormattedUsername).', *'.$oldLevel1['levelID'].'*', sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $newLevel1['levelName'], $newLevel1AuthorFormattedUsername).', *'.$newLevel1['levelID'].'*'), false]; + } + if($gauntletData['level2'] != $newGauntletData['level2']) { + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $gauntletData['level2']]); + $oldLevel2 = $getLevel->fetch(); + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $newGauntletData['level2']]); + $newLevel2 = $getLevel->fetch(); + $oldLevel2AuthorUsername = $this->getAccountName($oldLevel2['extID']); + $oldLevel2AuthorHasDiscord = $this->hasDiscord($oldLevel2['extID']); + $oldLevel2AuthorFormattedUsername = $oldLevel2AuthorHasDiscord ? "<@".$oldLevel2AuthorHasDiscord.">" : "**".$oldLevel2AuthorUsername."**"; + $newLevel2AuthorUsername = $this->getAccountName($newLevel2['extID']); + $newLevel2AuthorHasDiscord = $this->hasDiscord($newLevel2['extID']); + $newLevel2AuthorFormattedUsername = $newLevel2AuthorHasDiscord ? "<@".$newLevel2AuthorHasDiscord.">" : "**".$newLevel2AuthorUsername."**"; + $level2Field = [$this->webhookLanguage('logsGauntletChangeLevel2Field', $webhookLangArray), sprintf($this->webhookLanguage('logsGauntletChangeLevelValue', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $oldLevel2['levelName'], $oldLevel2AuthorFormattedUsername).', *'.$oldLevel2['levelID'].'*', sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $newLevel2['levelName'], $newLevel2AuthorFormattedUsername).', *'.$newLevel2['levelID'].'*'), false]; + } + if($gauntletData['level3'] != $newGauntletData['level3']) { + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $gauntletData['level3']]); + $oldLevel3 = $getLevel->fetch(); + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $newGauntletData['level3']]); + $newLevel3 = $getLevel->fetch(); + $oldLevel3AuthorUsername = $this->getAccountName($oldLevel3['extID']); + $oldLevel3AuthorHasDiscord = $this->hasDiscord($oldLevel3['extID']); + $oldLevel3AuthorFormattedUsername = $oldLevel3AuthorHasDiscord ? "<@".$oldLevel3AuthorHasDiscord.">" : "**".$oldLevel3AuthorUsername."**"; + $newLevel3AuthorUsername = $this->getAccountName($newLevel3['extID']); + $newLevel3AuthorHasDiscord = $this->hasDiscord($newLevel3['extID']); + $newLevel3AuthorFormattedUsername = $newLevel3AuthorHasDiscord ? "<@".$newLevel3AuthorHasDiscord.">" : "**".$newLevel3AuthorUsername."**"; + $level3Field = [$this->webhookLanguage('logsGauntletChangeLevel3Field', $webhookLangArray), sprintf($this->webhookLanguage('logsGauntletChangeLevelValue', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $oldLevel3['levelName'], $oldLevel3AuthorFormattedUsername).', *'.$oldLevel3['levelID'].'*', sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $newLevel3['levelName'], $newLevel3AuthorFormattedUsername).', *'.$newLevel3['levelID'].'*'), false]; + } + if($gauntletData['level4'] != $newGauntletData['level4']) { + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $gauntletData['level4']]); + $oldLevel4 = $getLevel->fetch(); + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $newGauntletData['level4']]); + $newLevel4 = $getLevel->fetch(); + $oldLevel4AuthorUsername = $this->getAccountName($oldLevel4['extID']); + $oldLevel4AuthorHasDiscord = $this->hasDiscord($oldLevel4['extID']); + $oldLevel4AuthorFormattedUsername = $oldLevel4AuthorHasDiscord ? "<@".$oldLevel4AuthorHasDiscord.">" : "**".$oldLevel4AuthorUsername."**"; + $newLevel4AuthorUsername = $this->getAccountName($newLevel4['extID']); + $newLevel4AuthorHasDiscord = $this->hasDiscord($newLevel4['extID']); + $newLevel4AuthorFormattedUsername = $newLevel4AuthorHasDiscord ? "<@".$newLevel4AuthorHasDiscord.">" : "**".$newLevel4AuthorUsername."**"; + $level4Field = [$this->webhookLanguage('logsGauntletChangeLevel4Field', $webhookLangArray), sprintf($this->webhookLanguage('logsGauntletChangeLevelValue', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $oldLevel4['levelName'], $oldLevel4AuthorFormattedUsername).', *'.$oldLevel4['levelID'].'*', sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $newLevel4['levelName'], $newLevel4AuthorFormattedUsername).', *'.$newLevel4['levelID'].'*'), false]; + } + if($gauntletData['level5'] != $newGauntletData['level5']) { + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $gauntletData['level5']]); + $oldLevel5 = $getLevel->fetch(); + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $newGauntletData['level5']]); + $newLevel5 = $getLevel->fetch(); + $oldLevel5AuthorUsername = $this->getAccountName($oldLevel5['extID']); + $oldLevel5AuthorHasDiscord = $this->hasDiscord($oldLevel5['extID']); + $oldLevel5AuthorFormattedUsername = $oldLevel5AuthorHasDiscord ? "<@".$oldLevel5AuthorHasDiscord.">" : "**".$oldLevel5AuthorUsername."**"; + $newLevel5AuthorUsername = $this->getAccountName($newLevel5['extID']); + $newLevel5AuthorHasDiscord = $this->hasDiscord($newLevel5['extID']); + $newLevel5AuthorFormattedUsername = $newLevel5AuthorHasDiscord ? "<@".$newLevel5AuthorHasDiscord.">" : "**".$newLevel5AuthorUsername."**"; + $level5Field = [$this->webhookLanguage('logsGauntletChangeLevel5Field', $webhookLangArray), sprintf($this->webhookLanguage('logsGauntletChangeLevelValue', $webhookLangArray), sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $oldLevel5['levelName'], $oldLevel5AuthorFormattedUsername).', *'.$oldLevel5['levelID'].'*', sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), $newLevel5['levelName'], $newLevel5AuthorFormattedUsername).', *'.$newLevel5['levelID'].'*'), false]; + } + } + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $logsGauntletChangeTitleURL) + ->setDescription($setDescription) + ->setThumbnail($logsGauntletChangeThumbnailURL) + ->addFields($whoChangedField, $whatWasChangedField, $gauntletName, $level1Field, $level2Field, $level3Field, $level4Field, $level5Field) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendLogsMapPackChangeWebhook($packID, $whoChangedID, $packData = []) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($packID) OR !in_array("mappacks", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($logsMapPackChangeWebhook); + $newPackData = $db->prepare('SELECT * FROM mappacks WHERE ID = :packID'); + $newPackData->execute([':packID' => $packID]); + $newPackData = $newPackData->fetch(); + $isDeleted = false; + if(!$newPackData) { + if(empty($packData)) return false; + $isDeleted = true; + $newPackData = $packData; + } + $whoChangedUsername = $this->getAccountName($whoChangedID); + $whoChangedHasDiscord = $this->hasDiscord($whoChangedID); + $whoChangedFormattedUsername = $whoChangedHasDiscord ? "<@".$whoChangedHasDiscord.">" : "**".$whoChangedUsername."**"; + $whoChangedField = [$this->webhookLanguage('logsMapPackChangeWhoField', $webhookLangArray), $whoChangedFormattedUsername, false]; + $whatWasChangedField = [$this->webhookLanguage('logsWhatWasChangedField', $webhookLangArray), $newPackData['name'].', *'.$packID.'*', false]; + $setNotificationText = $logsMapPackChangedNotificationText; + if($isDeleted || empty($packData)) { + $whatWasChangedField = []; + if($isDeleted) { + $setColor = $failColor; + $setTitle = $this->webhookLanguage('logsMapPackDeletedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsMapPackDeletedDesc', $webhookLangArray); + } else { + $setColor = $successColor; + $setTitle = $this->webhookLanguage('logsMapPackCreatedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsMapPackCreatedDesc', $webhookLangArray); + } + $packField = [$this->webhookLanguage('packField', $webhookLangArray), $newPackData['name'], true]; + if($newPackData['stars'] == 1) $action = 0; elseif(($newPackData['stars'] < 5 AND $newPackData['stars'] != 0)) $action = 1; else $action = 2; + $starsText = sprintf($this->webhookLanguage('requestedDesc'.$action, $webhookLangArray), $newPackData['stars']); + if($newPackData['coins'] == 1) $action = 0; elseif(($newPackData['coins'] < 5 AND $newPackData['coins'] != 0)) $action = 1; else $action = 2; + $coinsText = sprintf($this->webhookLanguage('packRewardCoins'.$action, $webhookLangArray), $newPackData['coins']); + $packRewardField = [$this->webhookLanguage('packRewardField', $webhookLangArray), sprintf($this->webhookLanguage('packRewardValue', $webhookLangArray), $starsText, $coinsText), true]; + $packLevels = explode(',', $newPackData['levels']); + $packLevelsValue = ''; + foreach($packLevels AS &$packLevel) { + $getLevel = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $getLevel->execute([':levelID' => $packLevel]); + $getLevel = $getLevel->fetch(); + if(!$getLevel) { + $packLevelsValue .= $this->webhookLanguage('undefinedLevel', $webhookLangArray).PHP_EOL; + continue; + } + $levelAuthorUsername = $this->getAccountName($getLevel['extID']); + $levelAuthorHasDiscord = $this->hasDiscord($getLevel['extID']); + $levelAuthorFormattedUsername = $levelAuthorHasDiscord ? "<@".$levelAuthorHasDiscord.">" : "**".$levelAuthorUsername."**"; + $difficulty = $this->getDifficulty($getLevel['starDifficulty'], $getLevel['starAuto'], $getLevel['starDemon'], $getLevel['starDemonDiff']); + if($getLevel['starStars'] == 1) $action = 0; elseif(($getLevel['starStars'] < 5 AND $getLevel['starStars'] != 0) AND !($getLevel['starStars'] > 9 AND $getLevel['starStars'] < 20)) $action = 1; else $action = 2; + $packLevelsValue .= sprintf($this->webhookLanguage('levelDesc', $webhookLangArray), '**'.$getLevel['levelName'].'**', $levelAuthorFormattedUsername).' • '.sprintf($this->webhookLanguage('difficultyDesc' . ($getLevel['levelLength'] == 5 ? 'Moon' : '') . $action, $webhookLangArray), $difficulty, $getLevel['starStars']).' (*'.$packLevel.'*)'.PHP_EOL; + } + $packLevelsField = [$this->webhookLanguage('packLevelsField', $webhookLangArray), $packLevelsValue, false]; + $packColorsField = [$this->webhookLanguage('packColorsField', $webhookLangArray), sprintf($this->webhookLanguage('packColorsValue', $webhookLangArray), $newPackData['rgbcolors'], $newPackData['colors2']), true]; + $packTimestampField = [$this->webhookLanguage('packTimestampField', $webhookLangArray), '', false]; + } else { + $setColor = $pendingColor; + $setTitle = $this->webhookLanguage('logsMapPackChangedTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('logsMapPackChangedDesc', $webhookLangArray); + if($packData['name'] != $newPackData['name']) $packField = [$this->webhookLanguage('logsMapPackChangeNameField', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeNameValue', $webhookLangArray), $packData['name'], $newPackData['name']), false]; + if($packData['levels'] != $newPackData['levels']) $packLevelsField = [$this->webhookLanguage('logsMapPackChangeLevelsField', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeLevelsValue', $webhookLangArray), $packData['levels'], $newPackData['levels']), false]; + if($packData['stars'] != $newPackData['stars']) { + if($packData['stars'] == 1) $action = 0; elseif(($packData['stars'] < 5 AND $packData['stars'] != 0)) $action = 1; else $action = 2; + $oldStarsText = sprintf($this->webhookLanguage('requestedDesc'.$action, $webhookLangArray), $packData['stars']); + if($newPackData['stars'] == 1) $action = 0; elseif(($newPackData['stars'] < 5 AND $newPackData['stars'] != 0)) $action = 1; else $action = 2; + $newStarsText = sprintf($this->webhookLanguage('requestedDesc'.$action, $webhookLangArray), $newPackData['stars']); + $packStarsField = [$this->webhookLanguage('logsMapPackChangeStarsField', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeStarsValue', $webhookLangArray), $oldStarsText, $newStarsText), false]; + } + if($packData['coins'] != $newPackData['coins']) { + if($packData['coins'] == 1) $action = 0; elseif(($packData['coins'] < 5 AND $packData['coins'] != 0)) $action = 1; else $action = 2; + $oldCoinsText = sprintf($this->webhookLanguage('packRewardCoins'.$action, $webhookLangArray), $packData['coins']); + if($newPackData['coins'] == 1) $action = 0; elseif(($newPackData['coins'] < 5 AND $newPackData['coins'] != 0)) $action = 1; else $action = 2; + $newCoinsText = sprintf($this->webhookLanguage('packRewardCoins'.$action, $webhookLangArray), $newPackData['coins']); + $packCoinsField = [$this->webhookLanguage('logsMapPackChangeCoinsField', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeCoinsValue', $webhookLangArray), $oldCoinsText, $newCoinsText), false]; + } + if($packData['difficulty'] != $newPackData['difficulty']) { + $diffarray = ['Auto', 'Easy', 'Normal', 'Hard', 'Harder', 'Insane', 'Demon']; + $packDifficultyField = [$this->webhookLanguage('logsMapPackChangeDifficultyField', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeDifficultyValue', $webhookLangArray), $diffarray[$packData['difficulty']], $diffarray[$newPackData['difficulty']]), false]; + } + if($packData['rgbcolors'] != $newPackData['rgbcolors']) $packColor1Field = [$this->webhookLanguage('logsMapPackChangeColor1Field', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeColorValue', $webhookLangArray), $packData['rgbcolors'], $newPackData['rgbcolors']), false]; + if($packData['colors2'] != $newPackData['colors2']) $packColor2Field = [$this->webhookLanguage('logsMapPackChangeColor2Field', $webhookLangArray), sprintf($this->webhookLanguage('logsMapPackChangeColorValue', $webhookLangArray), $packData['colors2'], $newPackData['colors2']), false]; + } + $diffarray = ['auto', 'easy', 'normal', 'hard', 'harder', 'insane', 'demon-hard']; + $setThumbnail = $difficultiesURL.'stars/'.$diffarray[$newPackData['difficulty']].'.png'; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($setNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($setColor) + ->setTitle($setTitle, $logsMapPackChangeTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($whoChangedField, $whatWasChangedField, $packField, $packRewardField, $packStarsField, $packCoinsField, $packDifficultyField, $packColor1Field, $packColor2Field, $packColorsField, $packLevelsField, $packTimestampField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function logAction($accountID, $type, $value1 = '', $value2 = '', $value3 = 0, $value4 = 0, $value5 = 0, $value6 = 0) { + require __DIR__."/connection.php"; + $insertAction = $db->prepare('INSERT INTO actions (account, type, timestamp, value, value2, value3, value4, value5, value6, IP) VALUES (:account, :type, :timestamp, :value, :value2, :value3, :value4, :value5, :value6, :IP)'); + $insertAction->execute([':account' => $accountID, ':type' => $type, ':value' => $value1, ':value2' => $value2, ':value3' => $value3, ':value4' => $value4, ':value5' => $value5, ':value6' => $value6, ':timestamp' => time(), ':IP' => $this->getIP()]); + return $db->lastInsertId(); + } + public function sendLevelsWarningWebhook($levelsYesterday, $levelsToday) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($levelsYesterday) OR !is_numeric($levelsToday) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('levelsWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('levelsWarningDesc', $webhookLangArray); + $lYchar = $levelsYesterday[strlen($levelsYesterday)-1] ?? $levelsYesterday; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($levelsYesterday > 9 AND $levelsYesterday < 20)) $action = 1; else $action = 2; + $levelsYesterdayField = [$this->webhookLanguage('levelsYesterdayField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeRewardCount'.$action, $webhookLangArray), $levelsYesterday), true]; + $lTchar = $levelsToday[strlen($levelsToday)-1] ?? $levelsToday; + if($lTchar == 1) $action = 0; elseif($lTchar < 5 AND $lTchar != 0 AND !($levelsToday > 9 AND $levelsToday < 20)) $action = 1; else $action = 2; + $levelsTodayField = [$this->webhookLanguage('levelsTodayField', $webhookLangArray), sprintf($this->webhookLanguage('logsListChangeRewardCount'.$action, $webhookLangArray), $levelsToday), true]; + if($levelsYesterday != 0) $levelsPercent = ceil($levelsToday / $levelsYesterday * 10) / 10; + else $levelsPercent = '∞'; + $levelsCompareField = [$this->webhookLanguage('levelsCompareField', $webhookLangArray), sprintf($this->webhookLanguage('levelsCompareValue', $webhookLangArray), $levelsPercent), true]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($levelsYesterdayField, $levelsTodayField, $levelsCompareField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendAccountsWarningWebhook($accountsYesterday, $accountsToday) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($accountsYesterday) OR !is_numeric($accountsToday) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('accountsWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('accountsWarningDesc', $webhookLangArray); + $lYchar = $accountsYesterday[strlen($accountsYesterday)-1] ?? $accountsYesterday; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($accountsYesterday > 9 AND $accountsYesterday < 20)) $action = 1; else $action = 2; + $accountsYesterdayField = [$this->webhookLanguage('accountsYesterdayField', $webhookLangArray), sprintf($this->webhookLanguage('accountsCountValue'.$action, $webhookLangArray), $accountsYesterday), true]; + $lTchar = $accountsToday[strlen($accountsToday)-1] ?? $accountsToday; + if($lTchar == 1) $action = 0; elseif($lTchar < 5 AND $lTchar != 0 AND !($accountsToday > 9 AND $accountsToday < 20)) $action = 1; else $action = 2; + $accountsTodayField = [$this->webhookLanguage('accountsTodayField', $webhookLangArray), sprintf($this->webhookLanguage('accountsCountValue'.$action, $webhookLangArray), $accountsToday), true]; + if($accountsYesterday != 0) $accountsPercent = ceil($accountsToday / $accountsYesterday * 10) / 10; + else $accountsPercent = '∞'; + $accountsCompareField = [$this->webhookLanguage('levelsCompareField', $webhookLangArray), sprintf($this->webhookLanguage('levelsCompareValue', $webhookLangArray), $accountsPercent), true]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($accountsYesterdayField, $accountsTodayField, $accountsCompareField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendCommentsSpammingWarningWebhook($similarCommentsCount, $similarCommentsAuthors) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($similarCommentsCount) OR !is_array($similarCommentsAuthors) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('commentsSpammingWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('commentsSpammingWarningDesc', $webhookLangArray); + $lYchar = $similarCommentsCount[strlen($similarCommentsCount)-1] ?? $similarCommentsCount; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($similarCommentsCount > 9 AND $similarCommentsCount < 20)) $action = 1; else $action = 2; + $similarCommentsField = [$this->webhookLanguage('similarCommentsField', $webhookLangArray), sprintf($this->webhookLanguage('similarCommentsValue'.$action, $webhookLangArray), $similarCommentsCount), true]; + $similarCommentsAuthorsText = ''; + foreach($similarCommentsAuthors AS &$commentAuthor) { + $commentAuthorID = $this->getExtID($commentAuthor); + $commentAuthorUsername = $this->getAccountName($commentAuthorID); + $commentAuthorHasDiscord = $this->hasDiscord($commentAuthorID); + $commentAuthorFormattedUsername = $commentAuthorHasDiscord ? "<@".$commentAuthorHasDiscord.">" : "**".$commentAuthorUsername."**"; + $similarCommentsAuthorsText .= $commentAuthorFormattedUsername.', '.$commentAuthorID.' • '.$commentAuthor.PHP_EOL; + } + $similarCommentsAuthorsField = [$this->webhookLanguage('similarCommentsAuthorsField', $webhookLangArray), $similarCommentsAuthorsText, false]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($similarCommentsField, $similarCommentsAuthorsField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendCommentsSpammerWarningWebhook($similarCommentsCount, $commentSpammer) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($similarCommentsCount) OR !is_numeric($commentSpammer) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('commentsSpammerWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('commentsSpammerWarningDesc', $webhookLangArray); + $lYchar = $similarCommentsCount[strlen($similarCommentsCount)-1] ?? $similarCommentsCount; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($similarCommentsCount > 9 AND $similarCommentsCount < 20)) $action = 1; else $action = 2; + $similarCommentsField = [$this->webhookLanguage('similarCommentsField', $webhookLangArray), sprintf($this->webhookLanguage('similarCommentsValue'.$action, $webhookLangArray), $similarCommentsCount), true]; + $commentAuthorID = $this->getExtID($commentSpammer); + $commentAuthorUsername = $this->getAccountName($commentAuthorID); + $commentAuthorHasDiscord = $this->hasDiscord($commentAuthorID); + $commentAuthorFormattedUsername = $commentAuthorHasDiscord ? "<@".$commentAuthorHasDiscord.">" : "**".$commentAuthorUsername."**"; + $similarCommentsAuthorsField = [$this->webhookLanguage('commentSpammerField', $webhookLangArray), $commentAuthorFormattedUsername.', '.$commentAuthorID.' • '.$commentSpammer, true]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($similarCommentsField, $similarCommentsAuthorsField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendAccountPostsSpammingWarningWebhook($similarCommentsCount, $similarCommentsAuthors) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($similarCommentsCount) OR !is_array($similarCommentsAuthors) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('accountPostsSpammingWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('accountPostsSpammingWarningDesc', $webhookLangArray); + $lYchar = $similarCommentsCount[strlen($similarCommentsCount)-1] ?? $similarCommentsCount; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($similarCommentsCount > 9 AND $similarCommentsCount < 20)) $action = 1; else $action = 2; + $similarCommentsField = [$this->webhookLanguage('similarAccountPostsField', $webhookLangArray), sprintf($this->webhookLanguage('similarAccountPostsValue'.$action, $webhookLangArray), $similarCommentsCount), true]; + $similarCommentsAuthorsText = ''; + foreach($similarCommentsAuthors AS &$commentAuthor) { + $commentAuthorID = $this->getExtID($commentAuthor); + $commentAuthorUsername = $this->getAccountName($commentAuthorID); + $commentAuthorHasDiscord = $this->hasDiscord($commentAuthorID); + $commentAuthorFormattedUsername = $commentAuthorHasDiscord ? "<@".$commentAuthorHasDiscord.">" : "**".$commentAuthorUsername."**"; + $similarCommentsAuthorsText .= $commentAuthorFormattedUsername.', '.$commentAuthorID.' • '.$commentAuthor.PHP_EOL; + } + $similarCommentsAuthorsField = [$this->webhookLanguage('similarAccountPostsAuthorsField', $webhookLangArray), $similarCommentsAuthorsText, false]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($similarCommentsField, $similarCommentsAuthorsField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendAccountPostsSpammerWarningWebhook($similarCommentsCount, $commentSpammer) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($similarCommentsCount) OR !is_numeric($commentSpammer) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('accountPostsSpammerWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('accountPostsSpammerWarningDesc', $webhookLangArray); + $lYchar = $similarCommentsCount[strlen($similarCommentsCount)-1] ?? $similarCommentsCount; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($similarCommentsCount > 9 AND $similarCommentsCount < 20)) $action = 1; else $action = 2; + $similarCommentsField = [$this->webhookLanguage('similarAccountPostsField', $webhookLangArray), sprintf($this->webhookLanguage('similarAccountPostsValue'.$action, $webhookLangArray), $similarCommentsCount), true]; + $commentAuthorID = $this->getExtID($commentSpammer); + $commentAuthorUsername = $this->getAccountName($commentAuthorID); + $commentAuthorHasDiscord = $this->hasDiscord($commentAuthorID); + $commentAuthorFormattedUsername = $commentAuthorHasDiscord ? "<@".$commentAuthorHasDiscord.">" : "**".$commentAuthorUsername."**"; + $similarCommentsAuthorsField = [$this->webhookLanguage('accountPostsSpammerField', $webhookLangArray), $commentAuthorFormattedUsername.', '.$commentAuthorID.' • '.$commentSpammer, true]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($similarCommentsField, $similarCommentsAuthorsField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendRepliesSpammingWarningWebhook($similarCommentsCount, $similarCommentsAuthors) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($similarCommentsCount) OR !is_array($similarCommentsAuthors) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('repliesSpammingWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('repliesSpammingWarningDesc', $webhookLangArray); + $lYchar = $similarCommentsCount[strlen($similarCommentsCount)-1] ?? $similarCommentsCount; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($similarCommentsCount > 9 AND $similarCommentsCount < 20)) $action = 1; else $action = 2; + $similarCommentsField = [$this->webhookLanguage('similarRepliesField', $webhookLangArray), sprintf($this->webhookLanguage('similarRepliesValue'.$action, $webhookLangArray), $similarCommentsCount), true]; + $similarCommentsAuthorsText = ''; + foreach($similarCommentsAuthors AS &$commentAuthorID) { + $commentAuthorUsername = $this->getAccountName($commentAuthorID); + $commentAuthor = $this->getUserID($commentAuthorID, $commentAuthorUsername); + $commentAuthorHasDiscord = $this->hasDiscord($commentAuthorID); + $commentAuthorFormattedUsername = $commentAuthorHasDiscord ? "<@".$commentAuthorHasDiscord.">" : "**".$commentAuthorUsername."**"; + $similarCommentsAuthorsText .= $commentAuthorFormattedUsername.', '.$commentAuthorID.' • '.$commentAuthor.PHP_EOL; + } + $similarCommentsAuthorsField = [$this->webhookLanguage('similarRepliesAuthorsField', $webhookLangArray), $similarCommentsAuthorsText, false]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($similarCommentsField, $similarCommentsAuthorsField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function sendRepliesSpammerWarningWebhook($similarCommentsCount, $commentAuthorID) { + require __DIR__."/connection.php"; + if(!class_exists('ExploitPatch')) require __DIR__."/exploitPatch.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/discord.php"; + if(!$webhooksEnabled OR !is_numeric($similarCommentsCount) OR !is_numeric($commentAuthorID) OR !in_array("warnings", $webhooksToEnable)) return false; + require_once __DIR__."/../../config/webhooks/DiscordWebhook.php"; + $webhookLangArray = $this->webhookStartLanguage($webhookLanguage); + $dw = new DiscordWebhook($warningsWebhook); + $setTitle = $this->webhookLanguage('repliesSpammerWarningTitle', $webhookLangArray); + $setDescription = $this->webhookLanguage('repliesSpammerWarningDesc', $webhookLangArray); + $lYchar = $similarCommentsCount[strlen($similarCommentsCount)-1] ?? $similarCommentsCount; + if($lYchar == 1) $action = 0; elseif($lYchar < 5 AND $lYchar != 0 AND !($similarCommentsCount > 9 AND $similarCommentsCount < 20)) $action = 1; else $action = 2; + $similarCommentsField = [$this->webhookLanguage('similarRepliesField', $webhookLangArray), sprintf($this->webhookLanguage('similarRepliesValue'.$action, $webhookLangArray), $similarCommentsCount), true]; + $commentAuthorUsername = $this->getAccountName($commentAuthorID); + $commentAuthor = $this->getUserID($commentAuthorID, $commentAuthorUsername); + $commentAuthorHasDiscord = $this->hasDiscord($commentAuthorID); + $commentAuthorFormattedUsername = $commentAuthorHasDiscord ? "<@".$commentAuthorHasDiscord.">" : "**".$commentAuthorUsername."**"; + $similarCommentsAuthorsField = [$this->webhookLanguage('repliesSpammerField', $webhookLangArray), $commentAuthorFormattedUsername.', '.$commentAuthorID.' • '.$commentAuthor, true]; + $setFooter = sprintf($this->webhookLanguage('footer', $webhookLangArray), $gdps); + $dw->newMessage() + ->setContent($warningsNotificationText) + ->setAuthor($gdps, $authorURL, $authorIconURL) + ->setColor($failColor) + ->setTitle($setTitle, $warningsTitleURL) + ->setDescription($setDescription) + ->setThumbnail($setThumbnail) + ->addFields($similarCommentsField, $similarCommentsAuthorsField) + ->setFooter($setFooter, $footerIconURL) + ->setTimestamp() + ->send(); + } + public function getGMDFile($levelID) { + require __DIR__."/connection.php"; + if(!is_numeric($levelID)) return false; + $level = $db->prepare('SELECT * FROM levels WHERE levelID = :levelID'); + $level->execute([':levelID' => $levelID]); + $level = $level->fetch(); + if(!$level) return false; + $levelString = file_get_contents(__DIR__.'/../../data/levels/'.$levelID) ?? $level['levelString']; + $gmdFile = ''; + + $gmdFile .= 'k1'.$levelID.''; + $gmdFile .= 'k2'.$level['levelName'].''; + $gmdFile .= 'k3'.$level['levelDesc'].''; + $gmdFile .= 'k4'.$levelString.''; + $gmdFile .= 'k5'.$level['userName'].''; + $gmdFile .= 'k6'.$level['userID'].''; + $gmdFile .= 'k8'.$level['audioTrack'].''; + $gmdFile .= 'k11'.$level['downloads'].''; + $gmdFile .= 'k13'; + $gmdFile .= 'k16'.$level['levelVersion'].''; + $gmdFile .= 'k212'; + $gmdFile .= 'k23'.$level['levelLength'].''; + $gmdFile .= 'k42'.$level['levelID'].''; + $gmdFile .= 'k45'.$level['songID'].''; + $gmdFile .= 'k47'; + $gmdFile .= 'k48'.$level['objects'].''; + $gmdFile .= 'k50'.$level['binaryVersion'].''; + $gmdFile .= 'k87556365614873111'; + $gmdFile .= 'k1010,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'; + $gmdFile .= 'kl10'; + $gmdFile .= 'kl20'; + $gmdFile .= 'kl31'; + $gmdFile .= 'kl51'; + $gmdFile .= 'kl6kI60000000000000000000000000000'; + + $gmdFile .= ''; + return $gmdFile; + } + public function getDownloadLinkWithCobalt($link) { + require __DIR__."/../../config/dashboard.php"; + if(!$useCobalt) return false; + $cobalt = $cobaltAPI[rand(0, count($cobaltAPI) - 1)]; + $data = array( + "url" => $link, + "audioFormat" => "mp3", + "downloadMode" => "audio" + ); + $postdata = json_encode($data); + $ch = curl_init($cobalt); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json']); + $result = json_decode(curl_exec($ch)); + curl_close($ch); + $url = $result->url; + if(!$url) return false; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + curl_setopt($ch, CURLOPT_USERAGENT, ""); + return curl_exec($ch); + } + public function getRewardTypes() { + return [ + 0 => '', + 1 => 'Fire Shard', + 2 => 'Ice Shard', + 3 => 'Poison Shard', + 4 => 'Shadow Shard', + 5 => 'Lava Shard', + 6 => 'Demon Key', + 7 => 'Orbs', + 8 => 'Diamond', + 9 => '', + 10 => 'Earth Shard', + 11 => 'Blood Shard', + 12 => 'Metal Shard', + 13 => 'Light Shard', + 14 => 'Soul Shard', + 15 => 'Gold Key', + 1001 => 'Cube', + 1002 => 'Color 1', + 1003 => 'Color 2', + 1004 => 'Ship', + 1005 => 'Ball', + 1006 => 'UFO', + 1007 => 'Wave', + 1008 => 'Robot', + 1009 => 'Spider', + 1010 => 'Trail', + 1011 => 'Death Effect', + 1012 => 'Items', + 1013 => 'Swing', + 1014 => 'Jetpack', + 1015 => 'Ship fire', + ]; + } + public function mail($mail = '', $user = '', $isForgotPass = false) { + if(empty($mail) OR empty($user)) return; + require __DIR__."/../../config/mail.php"; + if($mailEnabled) { + require __DIR__."/connection.php"; + require __DIR__."/../../config/dashboard.php"; + require __DIR__."/../../config/mail/PHPMailer.php"; + require __DIR__."/../../config/mail/SMTP.php"; + require __DIR__."/../../config/mail/Exception.php"; + $m = new PHPMailer\PHPMailer\PHPMailer(); + $m->CharSet = 'utf-8'; + $m->isSMTP(); + $m->SMTPAuth = true; + $m->Host = $mailbox; + $m->Username = $mailuser; + $m->Password = $mailpass; + $m->Port = $mailport; + if($mailtype) $m->SMTPSecure = $mailtype; + else { + $m->SMTPSecure = 'tls'; + $m->SMTPOptions = array( + 'ssl' => array( + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ) + ); + } + $m->setFrom($yourmail, $gdps); + $m->addAddress($mail, $user); + $m->isHTML(true); + if(!$isForgotPass) { + $string = $this->randomString(4); + $query = $db->prepare("UPDATE accounts SET mail = :mail WHERE userName = :user"); + $query->execute([':mail' => $string, ':user' => $user]); + $m->Subject = 'Confirm link'; + $m->Body = '

Hello, '.$user.'!


+

It seems, that you wanna register new account in '.$gdps.'


+

Here is your link!


+

'.dirname('https://'.$_SERVER["HTTP_HOST"].$_SERVER['REQUEST_URI']).'/activate.php?mail='.$string.'

'; + } else { + $m->Subject = 'Forgot password?'; + $m->Body = '

Hello, '.$user.'!


+

It seems, that you forgot your password in '.$gdps.'...


+

Here is your link!


+

https://'.$_SERVER["HTTP_HOST"].$_SERVER['REQUEST_URI'].'?code='.$isForgotPass.'

'; + } + return $m->send(); + } + } } diff --git a/incl/lib/songReup.php b/incl/lib/songReup.php index bf6c9d1cb..f63c33099 100644 --- a/incl/lib/songReup.php +++ b/incl/lib/songReup.php @@ -1,7 +1,7 @@ prepare("INSERT INTO songs (ID, name, authorID, authorName, size, download) diff --git a/incl/messages/deleteGJMessages.php b/incl/messages/deleteGJMessages.php index 46b2c1b9d..823c7e2b1 100644 --- a/incl/messages/deleteGJMessages.php +++ b/incl/messages/deleteGJMessages.php @@ -2,7 +2,7 @@ //TODO: unify the queries //TODO: does real geometry dash only delete messages for one person? if so, implement this chdir(dirname(__FILE__)); -include "../lib/connection.php"; +require "../lib/connection.php"; require_once "../lib/GJPCheck.php"; require_once "../lib/exploitPatch.php"; if(isset($_POST['messageID'])){$messageID = ExploitPatch::remove($_POST["messageID"]);} diff --git a/incl/messages/downloadGJMessage.php b/incl/messages/downloadGJMessage.php index 195828437..16f77d904 100644 --- a/incl/messages/downloadGJMessage.php +++ b/incl/messages/downloadGJMessage.php @@ -1,30 +1,34 @@ prepare("SELECT accID, toAccountID, timestamp, userName, messageID, subject, isNew, body FROM messages WHERE messageID = :messageID AND (accID = :accID OR toAccountID = :accID) LIMIT 1"); +$query = $db->prepare("SELECT accID, toAccountID, timestamp, userName, messageID, subject, isNew, body FROM messages WHERE messageID = :messageID AND (accID = :accID OR toAccountID = :accID) LIMIT 1"); $query->execute([':messageID' => $messageID, ':accID' => $accountID]); $result = $query->fetch(); -if($query->rowCount() == 0){ - exit("-1"); -} -if(empty($_POST["isSender"])){ - $query=$db->prepare("UPDATE messages SET isNew=1 WHERE messageID = :messageID AND toAccountID = :accID"); - $query->execute([':messageID' => $messageID, ':accID' =>$accountID]); +if($query->rowCount() == 0) exit("-1"); +if(empty($_POST["isSender"])) { + $query = $db->prepare("UPDATE messages SET isNew = 1, readTime = :readTime WHERE messageID = :messageID AND toAccountID = :accID AND readTime = 0"); + $query->execute([':messageID' => $messageID, ':accID' => $accountID, ':readTime' => time()]); $accountID = $result["accID"]; $isSender = 0; -}else{ +} else { $isSender = 1; $accountID = $result["toAccountID"]; } -$query=$db->prepare("SELECT userName,userID,extID FROM users WHERE extID = :accountID"); +$query = $db->prepare("SELECT userName, userID, extID, clan FROM users WHERE extID = :accountID"); $query->execute([':accountID' => $accountID]); $result12 = $query->fetch(); -$uploadDate = date("d/m/Y G.i", $result["timestamp"]); +$uploadDate = $gs->makeTime($result["timestamp"]); +$result12["userName"] = $gs->makeClanUsername($result12); +$result["subject"] = ExploitPatch::url_base64_encode(ExploitPatch::translit(ExploitPatch::url_base64_decode($result["subject"]))); +$result["body"] = ExploitPatch::url_base64_encode(XORCipher::cipher(ExploitPatch::translit(XORCipher::cipher(ExploitPatch::url_base64_decode($result["body"]), 14251)), 14251)); echo "6:".$result12["userName"].":3:".$result12["userID"].":2:".$result12["extID"].":1:".$result["messageID"].":4:".$result["subject"].":8:".$result["isNew"].":9:".$isSender.":5:".$result["body"].":7:".$uploadDate.""; ?> \ No newline at end of file diff --git a/incl/messages/getGJMessages.php b/incl/messages/getGJMessages.php index 4d55c66e4..abf2ad3b0 100644 --- a/incl/messages/getGJMessages.php +++ b/incl/messages/getGJMessages.php @@ -1,13 +1,14 @@ makeTime($message1["timestamp"]); if($getSent == 1){ $accountID = $message1["toAccountID"]; }else{ @@ -39,6 +40,8 @@ $query=$db->prepare("SELECT * FROM users WHERE extID = :accountID"); $query->execute([':accountID' => $accountID]); $result12 = $query->fetchAll()[0]; + $result12["userName"] = $gs->makeClanUsername($result12); + $message1['subject'] = ExploitPatch::url_base64_encode(ExploitPatch::translit(ExploitPatch::url_base64_decode($message1["subject"]))); $msgstring .= "6:".$result12["userName"].":3:".$result12["userID"].":2:".$result12["extID"].":1:".$message1["messageID"].":4:".$message1["subject"].":8:".$message1["isNew"].":9:".$getSent.":7:".$uploadDate."|"; } } diff --git a/incl/messages/uploadGJMessage.php b/incl/messages/uploadGJMessage.php index 6a24c8241..0f8844f32 100644 --- a/incl/messages/uploadGJMessage.php +++ b/incl/messages/uploadGJMessage.php @@ -1,10 +1,11 @@ prepare($query3); $query3->execute([':accID' => $accID]); @@ -26,6 +25,13 @@ $userID = $gs->getUserID($id); $uploadDate = time(); +$checkBan = $gs->getPersonBan($accID, $userID, 3); +if($checkBan) exit('-1'); + +$checkExistence = $db->prepare("SELECT count(*) FROM accounts WHERE accountID = :toAccountID"); +$checkExistence->execute([':toAccountID' => $toAccountID]); +if(!$checkExistence->fetchColumn()) exit('-1'); + $blocked = $db->query("SELECT ID FROM `blocks` WHERE person1 = $toAccountID AND person2 = $accID")->fetchAll(PDO::FETCH_COLUMN); $mSOnly = $db->query("SELECT mS FROM `accounts` WHERE accountID = $toAccountID AND mS > 0")->fetchAll(PDO::FETCH_COLUMN); $friend = $db->query("SELECT ID FROM `friendships` WHERE (person1 = $accID AND person2 = $toAccountID) || (person2 = $accID AND person1 = $toAccountID)")->fetchAll(PDO::FETCH_COLUMN); diff --git a/incl/misc/getAccountURL.php b/incl/misc/getAccountURL.php index fb83f3c4f..d370c4f66 100644 --- a/incl/misc/getAccountURL.php +++ b/incl/misc/getAccountURL.php @@ -2,4 +2,4 @@ if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443) $https = 'https'; else $https = 'http'; echo dirname($https."://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"); -?> +?> \ No newline at end of file diff --git a/incl/misc/getGJSongInfo.php b/incl/misc/getGJSongInfo.php index 251f124a6..709cfd1a0 100644 --- a/incl/misc/getGJSongInfo.php +++ b/incl/misc/getGJSongInfo.php @@ -1,17 +1,21 @@ prepare("SELECT ID,name,authorID,authorName,size,isDisabled,download FROM songs WHERE ID = :songid LIMIT 1"); $query3->execute([':songid' => $songid]); +$librarySong = $gs->getLibrarySongInfo($songid); //todo: move this logic away from this file -if($query3->rowCount() == 0) { - $url = 'http://www.boomlings.com/database/getGJSongInfo.php'; +if($query3->rowCount() == 0 && !$librarySong) { + $url = 'https://www.boomlings.com/database/getGJSongInfo.php'; $data = array('songID' => $songid, 'secret' => 'Wmfd2893gb7'); $options = array( 'http' => array( @@ -23,7 +27,7 @@ $context = stream_context_create($options); $result = file_get_contents($url, false, $context); if ($result == "-2" OR $result == "-1" OR $result == "") { - $url = 'http://www.boomlings.com/database/getGJLevels21.php'; + $url = 'https://www.boomlings.com/database/getGJLevels21.php'; $data = array( 'gameVersion' => '21', 'binaryVersion' => '33', @@ -47,6 +51,12 @@ ); $ch = curl_init($url); + if($proxytype == 1) curl_setopt($ch, CURLOPT_PROXY, $host); + elseif($proxytype == 2) { + curl_setopt($ch, CURLOPT_PROXY, $host); + curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + } + if(!empty($auth)) curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); @@ -55,7 +65,13 @@ if(substr_count($result, "1~|~".$songid."~|~2") != 0){ $result = explode('#',$result)[2]; }else{ - $ch = curl_init(); + $ch = curl_init(); + if($proxytype == 1) curl_setopt($ch, CURLOPT_PROXY, $host); + elseif($proxytype == 2) { + curl_setopt($ch, CURLOPT_PROXY, $host); + curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + } + if(!empty($auth)) curl_setopt($ch, CURLOPT_PROXYUSERPWD, $auth); curl_setopt($ch, CURLOPT_URL, "https://www.newgrounds.com/audio/listen/".$songid); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $songinfo = curl_exec($ch); @@ -75,15 +91,25 @@ } echo $result; $reup = SongReup::reup($result); -}else{ - $result4 = $query3->fetch(); - if($result4["isDisabled"] == 1){ - exit("-2"); - } +} else { + $result4 = !$librarySong ? $query3->fetch() : $librarySong; + if($result4["isDisabled"] == 1) exit("-2"); $dl = $result4["download"]; - if(strpos($dl, ':') !== false){ - $dl = urlencode($dl); + if(strpos($dl, ':') !== false) $dl = urlencode($dl); + echo "1~|~".$result4["ID"]."~|~2~|~".ExploitPatch::translit($result4["name"])."~|~3~|~".$result4["authorID"]."~|~4~|~".ExploitPatch::translit($result4["authorName"])."~|~5~|~".$result4["size"]."~|~6~|~~|~7~|~~|~8~|~0~|~10~|~".$dl.""; + if($librarySong) { + $artistsNames = []; + $artistsArray = explode('.', $result4['artists']); + if(count($artistsArray) > 0) { + foreach($artistsArray AS &$artistID) { + $artistData = $gs->getLibrarySongAuthorInfo($artistID); + if(!$artistData) continue; + $artistsNames[] = $artistID; + $artistsNames[] = $artistData['name']; + } + } + $artistsNames = implode(',', $artistsNames); + echo '~|~9~|~'.$result4['priorityOrder'].'~|~11~|~'.$result4['ncs'].'~|~12~|~'.$result4['artists'].'~|~13~|~'.($result4['new'] ? 1 : 0).'~|~14~|~'.$result4['new'].'~|~15~|~'.$artistsNames; } - echo "1~|~".$result4["ID"]."~|~2~|~".$result4["name"]."~|~3~|~".$result4["authorID"]."~|~4~|~".$result4["authorName"]."~|~5~|~".$result4["size"]."~|~6~|~~|~10~|~".$dl."~|~7~|~~|~8~|~0"; } -?> +?> \ No newline at end of file diff --git a/incl/misc/getTopArtists.php b/incl/misc/getTopArtists.php index 44d0afba5..f9290f08f 100644 --- a/incl/misc/getTopArtists.php +++ b/incl/misc/getTopArtists.php @@ -1,6 +1,8 @@ prepare("SELECT extID FROM users WHERE IP = :ip ORDER BY lastPlayed DESC"); + $IPcheck->execute([':ip' => $gs->getIP()]); + $IPcheck = $IPcheck->fetch(); + $querywhat = "SELECT * FROM favsongs INNER JOIN songs on favsongs.songID = songs.ID WHERE favsongs.accountID = :id ORDER BY favsongs.ID DESC LIMIT 20 OFFSET $offset"; $query = $db->prepare($querywhat); - $query->execute(); + $query->execute([':id' => $IPcheck["extID"]]); $res = $query->fetchAll(); - // count - $countquery = $db->prepare("SELECT count(DISTINCT(authorName)) FROM songs WHERE (authorName NOT LIKE '%Reupload%' AND authorName NOT LIKE 'unknown')"); - $countquery->execute(); - $totalCount = $countquery->fetchColumn(); - // parse foreach($res as $sel){ - $str .= "4:$sel[0]"; - // TO-DO: Fetch YouTube links from RobTop's servers, as we are unable to auto-determine YouTube links. - // Also credit to @Intelligent-Cat for this piece of code - if (substr($sel[1], 0, 26) == "https://api.soundcloud.com") { - if (strpos(urlencode($sel[0]), '+' ) !== false) { - $str .= ":7:../redirect?q=https%3A%2F%2Fsoundcloud.com%2Fsearch%2Fpeople?q=$sel[0]"; - // search is used instead of directly redirecting the user due to how user links work with spaces in them - } else { - $str .= ":7:../redirect?q=https%3A%2F%2Fsoundcloud.com%2F$sel[0]"; - // unlikely to hit a different account if there are multiple users with the same name. - } - } + $str .= "4:".$sel["authorName"]." - ".$sel["name"].", ".$sel["ID"]; + $str .= ":7:../redirect?q=".urlencode($sel["download"]); $str .= "|"; } + if(empty($str)) $str = "4:There is no songs!|4:If you liked some...|4:Update your IP!|4:Go to your profile to do that."; $str = rtrim($str, "|"); + $querywhat = "SELECT * FROM favsongs INNER JOIN songs on favsongs.songID = songs.ID WHERE favsongs.accountID = :id ORDER BY favsongs.ID DESC"; + $query = $db->prepare($querywhat); + $query->execute([':id' => $IPcheck["extID"]]); + $res = $query->fetchAll(); + $totalCount = count($res); $str .= "#$totalCount:$offset:20"; - // send result echo "$str"; } ?> diff --git a/incl/misc/likeGJItem.php b/incl/misc/likeGJItem.php index 53edcba7c..8ce1dba5c 100644 --- a/incl/misc/likeGJItem.php +++ b/incl/misc/likeGJItem.php @@ -1,15 +1,15 @@ getIP(); diff --git a/incl/misc/restoreGJItems.php b/incl/misc/restoreGJItems.php new file mode 100644 index 000000000..31c9ca51f --- /dev/null +++ b/incl/misc/restoreGJItems.php @@ -0,0 +1,12 @@ +getIDFromPost(); + +if(!file_exists("../../data/info/$id")) exit("-1"); +echo XORCipher::cipher(ExploitPatch::url_base64_decode(file_get_contents("../../data/info/$id")), 24157); +?> \ No newline at end of file diff --git a/incl/misc/submitGJUserInfo.php b/incl/misc/submitGJUserInfo.php new file mode 100644 index 000000000..3edc73c49 --- /dev/null +++ b/incl/misc/submitGJUserInfo.php @@ -0,0 +1,14 @@ +getIDFromPost(); +if(empty($_POST["levelsInfo"])) exit('-2'); +/* GD doesn't XOR encrypts this data, i just want to encrypt it */ +$data = ExploitPatch::url_base64_encode(XORCipher::cipher($_POST["levelsInfo"], 24157)); +file_put_contents("../../data/info/$id", $data); +echo 1; +?> \ No newline at end of file diff --git a/incl/mods/requestUserAccess.php b/incl/mods/requestUserAccess.php index c04624310..510ec80c4 100644 --- a/incl/mods/requestUserAccess.php +++ b/incl/mods/requestUserAccess.php @@ -15,4 +15,4 @@ } echo $permState; } -?> +?> \ No newline at end of file diff --git a/incl/profiles/getGJUserInfo.php b/incl/profiles/getGJUserInfo.php index ba5f4f818..9c97fb655 100644 --- a/incl/profiles/getGJUserInfo.php +++ b/incl/profiles/getGJUserInfo.php @@ -1,6 +1,6 @@ prepare($query); $query->execute([':extid' => $extid, ':me' => $me]); -if($query->fetchColumn() > 0) - exit("-1"); +if($query->fetchColumn() > 0) exit("-1"); $query = "SELECT * FROM users WHERE extID = :extid"; $query = $db->prepare($query); $query->execute([':extid' => $extid]); -if($query->rowCount() == 0) - exit("-1"); +if($query->rowCount() == 0) exit("-1"); $user = $query->fetch(); //placeholders @@ -29,25 +27,46 @@ $e = "SET @rownum := 0;"; $query = $db->prepare($e); $query->execute(); -/*$f = "SELECT rank FROM ( - SELECT @rownum := @rownum + 1 AS rank, extID - FROM users WHERE isBanned = '0' AND gameVersion > 19 AND stars > 25 ORDER BY stars DESC - ) as result WHERE extID=:extid";*/ -$f = "SELECT count(*) FROM users WHERE stars > :stars AND isBanned = 0"; //I can do this, since I already know the stars amount beforehand -$query = $db->prepare($f); -$query->execute([':stars' => $user["stars"]]); -if($query->rowCount() > 0) { - $rank = $query->fetchColumn() + 1; -} else { - $rank = 0; +$bans = $gs->getAllBansOfBanType(0); +$extIDs = $userIDs = $bannedIPs = []; +foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } } -if($user['isBanned'] != 0) $rank = 0; +$extIDsString = implode("','", $extIDs); +$userIDsString = implode("','", $userIDs); +$bannedIPsString = implode("|", $bannedIPs); +$queryArray = []; +if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; +if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; +if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; +$queryText = !empty($queryArray) ? '('.implode(' AND ', $queryArray).') AND' : ''; +$f = "SELECT count(*) FROM users WHERE ".$queryText." stars + moons > :stars + :moons"; +$query = $db->prepare($f); +$query->execute([':stars' => $user["stars"], ':moons' => $user["moons"]]); +if($query->rowCount() > 0) $rank = $query->fetchColumn() + 1; +else $rank = 0; +$isBanned = $gs->getPersonBan($user['extID'], $user['userID'], 0, $user['IP']); +if($isBanned) $rank = 0; //var_dump($leaderboard); //accinfo $query = "SELECT youtubeurl,twitter,twitch, frS, mS, cS FROM accounts WHERE accountID = :extID"; $query = $db->prepare($query); $query->execute([':extID' => $extid]); $accinfo = $query->fetch(); + $accinfo['youtubeurl'] = mb_ereg_replace("(?!^@)[^a-zA-Z0-9_]", "", $accinfo['youtubeurl']); + $accinfo['twitter'] = mb_ereg_replace("[^a-zA-Z0-9_]", "", $accinfo['twitter']); + $accinfo['twitch'] = mb_ereg_replace("[^a-zA-Z0-9_]", "", $accinfo['twitch']); + if(substr($accinfo['youtubeurl'], 0, 1) == "@") $accinfo['youtubeurl'] = "../".$accinfo['youtubeurl']; $reqsstate = $accinfo["frS"]; $msgstate = $accinfo["mS"]; $commentstate = $accinfo["cS"]; @@ -113,6 +132,7 @@ $appendix = ":32:".$INCrequestinfo["ID"].":35:".$INCrequestinfo["comment"].":37:".$uploaddate; } } +$user["userName"] = $gs->makeClanUsername($user); $user['extID'] = is_numeric($user['extID']) ? $user['extID'] : 0; echo "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":10:".$user["color1"].":11:".$user["color2"].":51:".$user["color3"].":3:".$user["stars"].":46:".$user["diamonds"].":52:".$user["moons"].":4:".$user["demons"].":8:".$creatorpoints.":18:".$msgstate.":19:".$reqsstate.":50:".$commentstate.":20:".$accinfo["youtubeurl"].":21:".$user["accIcon"].":22:".$user["accShip"].":23:".$user["accBall"].":24:".$user["accBird"].":25:".$user["accDart"].":26:".$user["accRobot"].":28:".$user["accGlow"].":43:".$user["accSpider"].":48:".$user["accExplosion"].":53:".$user["accSwing"].":54:".$user["accJetpack"].":30:".$rank.":16:".$user["extID"].":31:".$friendstate.":44:".$accinfo["twitter"].":45:".$accinfo["twitch"].":49:".$badge.":55:".$user["dinfo"].":56:".$user["sinfo"].":57:".$user["pinfo"].$appendix.":29:1"; -?> +?> \ No newline at end of file diff --git a/incl/profiles/getGJUsers.php b/incl/profiles/getGJUsers.php index 3d812f71e..5228180c2 100644 --- a/incl/profiles/getGJUsers.php +++ b/incl/profiles/getGJUsers.php @@ -1,23 +1,24 @@ prepare($query); $query->execute([':str' => $str]); $result = $query->fetchAll(); -if(count($result) < 1) { - exit("-1"); -} +if(count($result) < 1) exit("-1"); $countquery = "SELECT count(*) FROM users WHERE userName LIKE CONCAT('%', :str, '%')"; $countquery = $db->prepare($countquery); $countquery->execute([':str' => $str]); $usercount = $countquery->fetchColumn(); foreach($result as &$user) { + $user["userName"] = $gs->makeClanUsername($user); $user['extID'] = is_numeric($user['extID']) ? $user['extID'] : 0; $userstring .= "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":51:".$user["color3"].":14:".$user["iconType"].":15:".$user["special"].":16:".$user["extID"].":3:".$user["stars"].":8:".round($user["creatorPoints"],0,PHP_ROUND_HALF_DOWN).":4:".$user["demons"].":46:".$user["diamonds"].":52:".$user["moons"]."|"; } diff --git a/incl/profiles/updateGJAccSettings.php b/incl/profiles/updateGJAccSettings.php index cab359d2f..015044e63 100644 --- a/incl/profiles/updateGJAccSettings.php +++ b/incl/profiles/updateGJAccSettings.php @@ -1,9 +1,10 @@ prepare("UPDATE accounts SET mS=:mS, frS=:frS, cS=:cS, youtubeurl=:youtubeurl, twitter=:twitter, twitch=:twitch WHERE accountID=:accountID"); +$getAccountData = $db->prepare("SELECT * FROM accounts WHERE accountID = :accountID"); +$getAccountData->execute([':accountID' => $accountID]); +$getAccountData = $getAccountData->fetch(); + +if(substr($youtubeurl, 0, 4) == "../@") $youtubeurl = "@" . substr($youtubeurl, 4); +$youtubeurl = mb_ereg_replace("(?!^@)[^a-zA-Z0-9_]", "", $youtubeurl); +$twitter = mb_ereg_replace("[^a-zA-Z0-9_]", "", $twitter); +$twitch = mb_ereg_replace("[^a-zA-Z0-9_]", "", $twitch); + +$query = $db->prepare("UPDATE accounts SET mS = :mS, frS = :frS, cS = :cS, youtubeurl = :youtubeurl, twitter = :twitter, twitch = :twitch WHERE accountID = :accountID"); $query->execute([':mS' => $mS, ':frS' => $frS, ':cS' => $cS, ':youtubeurl' => $youtubeurl, ':accountID' => $accountID, ':twitch' => $twitch, ':twitter' => $twitter]); echo 1; +$gs->logAction($accountID, 27, $mS, $frS, $cS); +$gs->sendLogsAccountChangeWebhook($accountID, $accountID, $getAccountData); ?> \ No newline at end of file diff --git a/incl/relationships/acceptGJFriendRequest.php b/incl/relationships/acceptGJFriendRequest.php index 28d276ca8..0a9159928 100644 --- a/incl/relationships/acceptGJFriendRequest.php +++ b/incl/relationships/acceptGJFriendRequest.php @@ -1,30 +1,27 @@ prepare("SELECT accountID, toAccountID FROM friendreqs WHERE ID = :requestID"); $query->execute([':requestID' => $requestID]); $request = $query->fetch(); $reqAccountID = $request["accountID"]; $toAccountID = $request["toAccountID"]; -if($toAccountID != $accountID OR $reqAccountID == $accountID){ - exit("-1"); -} -$query = $db->prepare("INSERT INTO friendships (person1, person2, isNew1, isNew2) -VALUES (:accountID, :targetAccountID, 1, 1)"); - +if($toAccountID != $accountID OR $reqAccountID == $accountID) exit("-1"); +$query = $db->prepare("INSERT INTO friendships (person1, person2, isNew1, isNew2) VALUES (:accountID, :targetAccountID, 1, 1)"); $query->execute([':accountID' => $reqAccountID, ':targetAccountID' => $toAccountID]); -//REMOVING THE REQUEST -$query = $db->prepare("DELETE from friendreqs WHERE ID=:requestID LIMIT 1"); +$gs->logAction($accountID, 28, $reqAccountID); +$query = $db->prepare("DELETE from friendreqs WHERE ID = :requestID LIMIT 1"); $query->execute([':requestID' => $requestID]); -//RESPONSE SO IT DOESNT SAY "FAILED" +if($automaticCron) Cron::updateFriendsCount($accountID, false); echo "1"; ?> \ No newline at end of file diff --git a/incl/relationships/blockGJUser.php b/incl/relationships/blockGJUser.php index 4b177e7e3..b13f50e82 100644 --- a/incl/relationships/blockGJUser.php +++ b/incl/relationships/blockGJUser.php @@ -1,19 +1,24 @@ prepare("INSERT INTO blocks (person1, person2) VALUES (:accountID, :targetAccountID)"); $query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); +// Remove from friend list if the two users were friends +$query = $db->prepare("DELETE FROM friendships WHERE (person1 = :accountID AND person2 = :targetAccountID) OR (person1 = :targetAccountID AND person2 = :accountID)"); +$query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); +if($automaticCron) Cron::updateFriendsCount($accountID, false); +$gs->logAction($accountID, 29, $targetAccountID); echo 1; \ No newline at end of file diff --git a/incl/relationships/deleteGJFriendRequests.php b/incl/relationships/deleteGJFriendRequests.php index 6ced40edf..260742221 100644 --- a/incl/relationships/deleteGJFriendRequests.php +++ b/incl/relationships/deleteGJFriendRequests.php @@ -1,18 +1,13 @@ prepare("DELETE from friendreqs WHERE accountID=:accountID AND toAccountID=:targetAccountID LIMIT 1"); -}else{ - $query = $db->prepare("DELETE from friendreqs WHERE toAccountID=:accountID AND accountID=:targetAccountID LIMIT 1"); -} -$query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); +$query = $db->prepare("DELETE FROM friendreqs WHERE (accountID = :accountID AND toAccountID = :targetAccountID) OR (toAccountID = :accountID AND accountID = :targetAccountID) LIMIT 1"); +if($query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID])) $gs->logAction($accountID, 30, $targetAccountID); echo "1"; \ No newline at end of file diff --git a/incl/relationships/getGJFriendRequests.php b/incl/relationships/getGJFriendRequests.php index 4bfceacdf..5ef919674 100644 --- a/incl/relationships/getGJFriendRequests.php +++ b/incl/relationships/getGJFriendRequests.php @@ -1,54 +1,51 @@ 21) ? $_POST["gjp2"] : $_POST["gjp"]; // Backwards Compatible GJP -if(empty($_POST["accountID"]) OR (!isset($_POST["page"]) OR !is_numeric($_POST["page"])) OR empty($bcgjp)){ +if(empty($_POST["accountID"]) OR (!isset($_POST["page"]) OR !is_numeric($_POST["page"])) OR empty($bcgjp)) { exit("-1"); } - $accountID = GJPCheck::getAccountIDOrDie(); $page = ExploitPatch::number($_POST["page"]); $offset = $page*10; -if($getSent == 0){ +if($getSent == 0) { $query = "SELECT accountID, toAccountID, uploadDate, ID, comment, isNew FROM friendreqs WHERE toAccountID = :accountID LIMIT 10 OFFSET $offset"; $countquery = "SELECT count(*) FROM friendreqs WHERE toAccountID = :accountID"; -}elseif($getSent == 1){ +} elseif($getSent == 1) { $query = "SELECT * FROM friendreqs WHERE accountID = :accountID LIMIT 10 OFFSET $offset"; $countquery = "SELECT count(*) FROM friendreqs WHERE accountID = :accountID"; -}else{ - exit("-1"); -} +} else exit("-1"); $query = $db->prepare($query); $query->execute([':accountID' => $accountID]); $result = $query->fetchAll(); $countquery = $db->prepare($countquery); $countquery->execute([':accountID' => $accountID]); $reqcount = $countquery->fetchColumn(); -if($reqcount == 0){ - exit("-2"); -} +if($reqcount == 0) exit("-2"); foreach($result as &$request) { - if($getSent == 0){ + if($getSent == 0) { $requester = $request["accountID"]; - }else if($getSent == 1){ + } elseif($getSent == 1) { $requester = $request["toAccountID"]; } - $query = "SELECT userName, userID, icon, color1, color2, iconType, special, extID FROM users WHERE extID = :requester"; + $query = "SELECT userName, userID, icon, color1, color2, iconType, special, extID, clan FROM users WHERE extID = :requester"; $query = $db->prepare($query); $query->execute([':requester' => $requester]); - $result2 = $query->fetchAll(); - $user = $result2[0]; - $uploadTime = date("d/m/Y G.i", $request["uploadDate"]); - $extid = is_numeric($user['extID']) ? $user['extID'] : 0; + $user = $query->fetch(); + $uploadTime = $gs->makeTime($request["uploadDate"]); + $extid = is_numeric($user["extID"]) ? $user['extID'] : 0; + $user["userName"] = $gs->makeClanUsername($user); + $request["comment"] = ExploitPatch::url_base64_encode(ExploitPatch::translit(ExploitPatch::url_base64_decode($request["comment"]))); $reqstring .= "1:".$user["userName"].":2:".$user["userID"].":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":14:".$user["iconType"].":15:".$user["special"].":16:".$extid.":32:".$request["ID"].":35:".$request["comment"].":41:".$request["isNew"].":37:".$uploadTime."|"; } $reqstring = substr($reqstring, 0, -1); echo $reqstring; echo "#${reqcount}:${offset}:10"; -?> +?> \ No newline at end of file diff --git a/incl/relationships/getGJUserList.php b/incl/relationships/getGJUserList.php index 34eb56685..cbb74a378 100644 --- a/incl/relationships/getGJUserList.php +++ b/incl/relationships/getGJUserList.php @@ -1,9 +1,10 @@ prepare("SELECT userName, userID, icon, color1, color2, iconType, special, extID FROM users WHERE extID IN ($people) ORDER BY userName ASC"); + $query = $db->prepare("SELECT userName, userID, icon, color1, color2, iconType, special, extID, clan FROM users WHERE extID IN ($people) ORDER BY userName ASC"); $query->execute(); $result = $query->fetchAll(); - foreach($result as &$user){ + foreach($result as &$user) { + $user["userName"] = $gs->makeClanUsername($user); $user['extID'] = is_numeric($user['extID']) ? $user['extID'] : 0; $peoplestring .= "1:".$user["userName"].":2:".$user["userID"].":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":14:".$user["iconType"].":15:".$user["special"].":16:".$user["extID"].":18:0:41:".$new[$user["extID"]]."|"; } diff --git a/incl/relationships/readGJFriendRequest.php b/incl/relationships/readGJFriendRequest.php index b4bdea11e..23667432b 100644 --- a/incl/relationships/readGJFriendRequest.php +++ b/incl/relationships/readGJFriendRequest.php @@ -1,16 +1,13 @@ prepare("UPDATE friendreqs SET isNew='0' WHERE ID = :requestID AND toAccountID = :targetAcc"); +$query = $db->prepare("UPDATE friendreqs SET isNew='0' WHERE ID = :requestID AND toAccountID = :targetAcc"); $query->execute([':requestID' => $requestID, ':targetAcc' => $accountID]); echo "1"; \ No newline at end of file diff --git a/incl/relationships/removeGJFriend.php b/incl/relationships/removeGJFriend.php index 1bd8c718c..0ad746c63 100644 --- a/incl/relationships/removeGJFriend.php +++ b/incl/relationships/removeGJFriend.php @@ -1,21 +1,16 @@ prepare($query); -$query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); - -$query = "DELETE FROM friendships WHERE person2 = :accountID AND person1 = :targetAccountID"; -$query = $db->prepare($query); -$query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); - +$query = $db->prepare("DELETE FROM friendships WHERE (person1 = :accountID AND person2 = :targetAccountID) OR (person2 = :accountID AND person1 = :targetAccountID)"); +if($query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID])) $gs->logAction($accountID, 31, $targetAccountID); +if($automaticCron) Cron::updateFriendsCount($accountID, false); echo "1"; \ No newline at end of file diff --git a/incl/relationships/unblockGJUser.php b/incl/relationships/unblockGJUser.php index 19e21e4e6..6710629e7 100644 --- a/incl/relationships/unblockGJUser.php +++ b/incl/relationships/unblockGJUser.php @@ -1,17 +1,15 @@ prepare($query); -$query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID]); - +$query = $db->prepare("DELETE FROM blocks WHERE person1 = :accountID AND person2 = :targetAccountID"); +if($query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID])) $gs->logAction($accountID, 32, $targetAccountID); echo "1"; \ No newline at end of file diff --git a/incl/relationships/uploadFriendRequest.php b/incl/relationships/uploadFriendRequest.php index 015723f6a..8d0a15457 100644 --- a/incl/relationships/uploadFriendRequest.php +++ b/incl/relationships/uploadFriendRequest.php @@ -1,30 +1,25 @@ query("SELECT ID FROM `blocks` WHERE person1 = $toAccountID AND person2 = $accountID")->fetchAll(PDO::FETCH_COLUMN); $frSOnly = $db->query("SELECT frS FROM `accounts` WHERE accountID = $toAccountID AND frS = 1")->fetchAll(PDO::FETCH_COLUMN); $query = $db->prepare("SELECT count(*) FROM friendreqs WHERE (accountID=:accountID AND toAccountID=:toAccountID) OR (toAccountID=:accountID AND accountID=:toAccountID)"); $query->execute([':accountID' => $accountID, ':toAccountID' => $toAccountID]); -if($query->fetchColumn() == 0 and empty($blocked[0]) and empty($frSOnly[0])){ +if($query->fetchColumn() == 0 && empty($blocked[0]) && empty($frSOnly[0])) { $query = $db->prepare("INSERT INTO friendreqs (accountID, toAccountID, comment, uploadDate) VALUES (:accountID, :toAccountID, :comment, :uploadDate)"); $query->execute([':accountID' => $accountID, ':toAccountID' => $toAccountID, ':comment' => $comment, ':uploadDate' => $uploadDate]); + $gs->logAction($accountID, 33, $toAccountID, $comment); echo 1; -}else{ - echo -1; -} +} else echo '-1'; ?> diff --git a/incl/rewards/getGJChallenges.php b/incl/rewards/getGJChallenges.php index 13623930e..389906800 100644 --- a/incl/rewards/getGJChallenges.php +++ b/incl/rewards/getGJChallenges.php @@ -1,6 +1,6 @@ getUserID($udid); } -$chk = XORCipher::cipher(base64_decode(substr($chk, 5)),19847); +$chk = XORCipher::cipher(ExploitPatch::url_base64_decode(substr($chk, 5)),19847); //Generating quest IDs $from = strtotime('2000-12-17'); $today = time(); @@ -45,7 +45,7 @@ $quest1 = $quest1ID.",".$result[0]["type"].",".$result[0]["amount"].",".$result[0]["reward"].",".$result[0]["name"].""; $quest2 = $quest2ID.",".$result[1]["type"].",".$result[1]["amount"].",".$result[1]["reward"].",".$result[1]["name"].""; $quest3 = $quest3ID.",".$result[2]["type"].",".$result[2]["amount"].",".$result[2]["reward"].",".$result[2]["name"].""; -$string = base64_encode(XORCipher::cipher("SaKuJ:".$userID.":".$chk.":".$udid.":".$accountID.":".$timeleft.":".$quest1.":".$quest2.":".$quest3."",19847)); +$string = ExploitPatch::url_base64_encode(XORCipher::cipher("SaKuJ:".$userID.":".$chk.":".$udid.":".$accountID.":".$timeleft.":".$quest1.":".$quest2.":".$quest3."",19847)); $hash = GenerateHash::genSolo3($string); echo "SaKuJ".$string . "|".$hash; ?> diff --git a/incl/rewards/getGJRewards.php b/incl/rewards/getGJRewards.php index f6757ab8b..cf118cdbb 100644 --- a/incl/rewards/getGJRewards.php +++ b/incl/rewards/getGJRewards.php @@ -1,8 +1,8 @@ getUserID($extID); $udid = ExploitPatch::remove($_POST["udid"]); $accountID = ExploitPatch::remove($_POST["accountID"]); -$chk = XORCipher::cipher(base64_decode(substr($chk, 5)),59182); +$chk = XORCipher::cipher(ExploitPatch::url_base64_decode(substr($chk, 5)),59182); $query=$db->prepare("SELECT chest1time, chest1count, chest2time, chest2count FROM users WHERE extID = :extID"); $query->execute([':extID' => $extID]); @@ -60,8 +60,6 @@ $query->execute([':chest2count' => $chest2count, ':userID' => $userid, ':currenttime' => $currenttime]); $chest2left = $chest2wait; } -$string = base64_encode(XORCipher::cipher("1:".$userid.":".$chk.":".$udid.":".$accountID.":".$chest1left.":".$chest1stuff.":".$chest1count.":".$chest2left.":".$chest2stuff.":".$chest2count.":".$rewardType."",59182)); -$string = str_replace("/","_",$string); -$string = str_replace("+","-",$string); +$string = ExploitPatch::url_base64_encode(XORCipher::cipher("1:".$userid.":".$chk.":".$udid.":".$accountID.":".$chest1left.":".$chest1stuff.":".$chest1count.":".$chest2left.":".$chest2stuff.":".$chest2count.":".$rewardType."",59182)); $hash = GenerateHash::genSolo4($string); echo "SaKuJ".$string . "|".$hash; diff --git a/incl/rewards/getGJSecretReward.php b/incl/rewards/getGJSecretReward.php new file mode 100644 index 000000000..8085b5db3 --- /dev/null +++ b/incl/rewards/getGJSecretReward.php @@ -0,0 +1,35 @@ +getIDFromPost() ?: 0; +$rewardKey = ExploitPatch::charclean($_POST["rewardKey"]); +$chk = XORCipher::cipher(ExploitPatch::url_base64_decode(substr(ExploitPatch::charclean($_POST["chk"]), 5)), 59182); + +$vaultCode = $db->prepare('SELECT * FROM vaultcodes WHERE code = :code'); +$vaultCode->execute([':code' => base64_encode($rewardKey)]); +$vaultCode = $vaultCode->fetch(); + +if(!$vaultCode || $vaultCode['uses'] == 0 || ($vaultCode['duration'] != 0 && $vaultCode['duration'] <= time())) exit('-1'); + +$check = $db->prepare("SELECT count(*) FROM actions WHERE type = 38 AND value = :vaultCode AND account = :extID"); +$check->execute([':vaultCode' => $vaultCode['rewardID'], ':extID' => $extID]); +$check = $check->fetchColumn(); +if($check) exit('-1'); + +if($vaultCode['uses'] > 0) { + $reduceUses = $db->prepare('UPDATE vaultcodes SET uses = uses - 1 WHERE rewardID = :rewardID'); + $reduceUses->execute([':rewardID' => $vaultCode['rewardID']]); +} + +$gs->logAction($extID, 38, $vaultCode['rewardID'], $vaultCode['rewards'], $rewardKey); +$string = ExploitPatch::url_base64_encode(XORCipher::cipher('Sa1nt:'.$chk.':'.$vaultCode['rewardID'].':1:'.$vaultCode['rewards'], 59182)); +$hash = $gh->genSolo4($string); +echo 'Sa1nt'.$string.'|'.$hash; +?> \ No newline at end of file diff --git a/incl/scores/getGJCreators.php b/incl/scores/getGJCreators.php index fda5c5780..e93b08709 100644 --- a/incl/scores/getGJCreators.php +++ b/incl/scores/getGJCreators.php @@ -1,19 +1,44 @@ prepare($query); +$bans = $gs->getAllBansOfBanType(1); +$extIDs = $userIDs = $bannedIPs = []; +foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } +} +$extIDsString = implode("','", $extIDs); +$userIDsString = implode("','", $userIDs); +$bannedIPsString = implode("|", $bannedIPs); +$queryArray = []; +if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; +if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; +if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; +$queryText = !empty($queryArray) ? '('.implode(' AND ', $queryArray).') AND' : ''; +$query = $db->prepare("SELECT * FROM users WHERE ".$queryText." creatorPoints > 0 ORDER BY creatorPoints DESC LIMIT 100"); $query->execute(); $result = $query->fetchAll(); -foreach($result as &$user){ +$xi = 0; +foreach($result as &$user) { + $extid = is_numeric($user["extID"]) ? $user["extID"] : 0; $xi++; - $extid = is_numeric($user['extID']) ? $user['extID'] : 0; + $user["userName"] = $gs->makeClanUsername($user); $pplstring .= "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":6:".$xi.":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":14:".$user["iconType"].":15:".$user["special"].":16:".$extid.":3:".$user["stars"].":8:".round($user["creatorPoints"],0,PHP_ROUND_HALF_DOWN).":4:".$user["demons"].":7:".$extid.":46:".$user["diamonds"]."|"; } $pplstring = substr($pplstring, 0, -1); echo $pplstring; -?> +?> \ No newline at end of file diff --git a/incl/scores/getGJLevelScores.php b/incl/scores/getGJLevelScores.php index fae1b663e..7f7274dec 100644 --- a/incl/scores/getGJLevelScores.php +++ b/incl/scores/getGJLevelScores.php @@ -1,61 +1,54 @@ getUserID($accountID); -$condition = ($dailyID > 0) ? ">" : "="; -$query2 = $db->prepare("SELECT percent FROM levelscores WHERE accountID = :accountID AND levelID = :levelID AND dailyID $condition 0"); -$query2->execute([':accountID' => $accountID, ':levelID' => $levelID]); -$oldPercent = $query2->fetchColumn(); -if($query2->rowCount() == 0) { - $query = $db->prepare("INSERT INTO levelscores (accountID, levelID, percent, uploadDate, coins, attempts, clicks, time, progresses, dailyID) - VALUES (:accountID, :levelID, :percent, :uploadDate, :coins, :attempts, :clicks, :time, :progresses, :dailyID)"); -} else { - if($oldPercent <= $percent){ - $query = $db->prepare("UPDATE levelscores SET percent=:percent, uploadDate=:uploadDate, coins=:coins, attempts=:attempts, clicks=:clicks, time=:time, progresses=:progresses, dailyID=:dailyID WHERE accountID=:accountID AND levelID=:levelID AND dailyID $condition 0"); - }else{ - $query = $db->prepare("SELECT count(*) FROM levelscores WHERE percent=:percent AND uploadDate=:uploadDate AND accountID=:accountID AND levelID=:levelID AND coins = :coins AND attempts = :attempts AND clicks = :clicks AND time = :time AND progresses = :progresses AND dailyID = :dailyID"); +if(!Automod::isLevelsDisabled(2)) { + $condition = ($dailyID > 0) ? ">" : "="; + $query2 = $db->prepare("SELECT percent FROM levelscores WHERE accountID = :accountID AND levelID = :levelID AND dailyID $condition 0"); + $query2->execute([':accountID' => $accountID, ':levelID' => $levelID]); + $oldPercent = $query2->fetchColumn(); + if($query2->rowCount() == 0) { + $gs->logAction($accountID, 34, $levelID, $percent, $coins, $attempts, $clicks, $time); + $query = $db->prepare("INSERT INTO levelscores (accountID, levelID, percent, uploadDate, coins, attempts, clicks, time, progresses, dailyID) + VALUES (:accountID, :levelID, :percent, :uploadDate, :coins, :attempts, :clicks, :time, :progresses, :dailyID)"); + } else { + if($oldPercent < $percent) { + $gs->logAction($accountID, 35, $levelID, $percent, $coins, $attempts, $clicks, $time); + $query = $db->prepare("UPDATE levelscores SET percent=:percent, uploadDate=:uploadDate, coins=:coins, attempts=:attempts, clicks=:clicks, time=:time, progresses=:progresses, dailyID=:dailyID WHERE accountID=:accountID AND levelID=:levelID AND dailyID $condition 0"); + } else { + $query = $db->prepare("SELECT count(*) FROM levelscores WHERE percent=:percent AND uploadDate=:uploadDate AND accountID=:accountID AND levelID=:levelID AND coins = :coins AND attempts = :attempts AND clicks = :clicks AND time = :time AND progresses = :progresses AND dailyID = :dailyID"); + } } -} -$query->execute([':accountID' => $accountID, ':levelID' => $levelID, ':percent' => $percent, ':uploadDate' => $uploadDate, ':coins' => $coins, ':attempts' => $attempts, ':clicks' => $clicks, ':time' => $time, ':progresses' => $progresses, ':dailyID' => $dailyID]); -if($percent > 100){ - $query = $db->prepare("UPDATE users SET isBanned=1 WHERE extID = :accountID"); - $query->execute([':accountID' => $accountID]); + $query->execute([':accountID' => $accountID, ':levelID' => $levelID, ':percent' => $percent, ':uploadDate' => $uploadDate, ':coins' => $coins, ':attempts' => $attempts, ':clicks' => $clicks, ':time' => $time, ':progresses' => $progresses, ':dailyID' => $dailyID]); } - - +if($percent > 100 || $percent < 0) $gs->banPerson(0, $accountID, 'Bro tried to post invalid percent value to level leaderboards. ('.$percent.')', 0, 0, 2147483647); //GETTING SCORES -if(!isset($_POST["type"])){ - $type = 1; -}else{ - $type = $_POST["type"]; -} -switch($type){ +$type = $_POST['type'] ?? 1; +switch($type) { case 0: $friends = $gs->getFriends($accountID); $friends[] = $accountID; - $friends = implode(",",$friends); + $friends = implode(",", $friends); $query2 = $db->prepare("SELECT accountID, uploadDate, percent, coins FROM levelscores WHERE dailyID $condition 0 AND levelID = :levelID AND accountID IN ($friends) ORDER BY percent DESC"); $query2args = [':levelID' => $levelID]; break; @@ -68,29 +61,23 @@ $query2args = [':levelID' => $levelID, ':time' => time() - 604800]; break; default: - return -1; + exit('-1'); break; } - - - $query2->execute($query2args); $result = $query2->fetchAll(); -foreach ($result as &$score) { +foreach($result as &$score) { $extID = $score["accountID"]; - $query2 = $db->prepare("SELECT userName, userID, icon, color1, color2, color3, iconType, special, extID, isBanned FROM users WHERE extID = :extID"); + $query2 = $db->prepare("SELECT userName, extID, userID, icon, color1, color2, color3, iconType, special, clan, IP FROM users WHERE extID = :extID"); $query2->execute([':extID' => $extID]); - $user = $query2->fetchAll(); - $user = $user[0]; - $time = date("d/m/Y G.i", $score["uploadDate"]); - if($user["isBanned"]==0){ - if($score["percent"] == 100){ - $place = 1; - }else if($score["percent"] > 75){ - $place = 2; - }else{ - $place = 3; - } + $user = $query2->fetch(); + $time = $gs->makeTime($score["uploadDate"]); + $isBanned = $gs->getPersonBan($user['extID'], $user['userID'], 0, $user['IP']); + if(!$isBanned) { + if($score["percent"] == 100) $place = 1; + else if($score["percent"] > 75) $place = 2; + else $place = 3; + $user["userName"] = $gs->makeClanUsername($user); echo "1:".$user["userName"].":2:".$user["userID"].":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":51:".$user["color3"].":14:".$user["iconType"].":15:".$user["special"].":16:".$user["extID"].":3:".$score["percent"].":6:".$place.":13:".$score["coins"].":42:".$time."|"; } } diff --git a/incl/scores/getGJLevelScoresPlat.php b/incl/scores/getGJLevelScoresPlat.php index e20ec2330..23cb8778e 100644 --- a/incl/scores/getGJLevelScoresPlat.php +++ b/incl/scores/getGJLevelScoresPlat.php @@ -1,76 +1,80 @@ -prepare("SELECT {$mode} FROM platscores WHERE accountID = :accountID AND levelID = :levelID"); -$query2->execute([':accountID' => $accountID, ':levelID' => $levelID]); -$oldPercent = $query2->fetchColumn(); -if($query2->rowCount() == 0) { - if($scores['time'] > 0) { - $query = $db->prepare("INSERT INTO platscores (accountID, levelID, time, timestamp) VALUES (:accountID, :levelID, :time, :timestamp)"); - $query->execute([':accountID' => $accountID, ':levelID' => $levelID, ":time" => $scores['time'], ':timestamp' => $uploadDate]); - } -} else { - if((($mode == "time" AND $oldPercent > $scores['time']) OR ($mode == "points" AND $oldPercent < $scores['points'])) AND $scores['time'] > 0) { - $query = $db->prepare("UPDATE platscores SET {$mode}=:{$mode}, timestamp=:timestamp WHERE accountID=:accountID AND levelID=:levelID"); - } else { - $query = $db->prepare("SELECT count(*) FROM platscores WHERE {$mode}=:{$mode} AND timestamp=:timestamp AND accountID=:accountID AND levelID=:levelID"); - } - $query->execute([':accountID' => $accountID, ':levelID' => $levelID, ":{$mode}" => $scores[$mode], ':timestamp' => $uploadDate]); -} -//GETTING SCORES -if(!isset($_POST["type"])){ - $type = 1; -}else{ - $type = $_POST["type"]; -} -switch($type){ - case 0: - $friends = $gs->getFriends($accountID); - $friends[] = $accountID; - $friends = implode(",",$friends); - $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND accountID IN ($friends) AND time > 0 ORDER BY {$mode} {$order}"); - $query2args = [':levelID' => $levelID]; - break; - case 1: - $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND time > 0 ORDER BY {$mode} {$order}"); - $query2args = [':levelID' => $levelID]; - break; - case 2: - $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND timestamp > :time AND time > 0 ORDER BY {$mode} {$order}"); - $query2args = [':levelID' => $levelID, ':time' => $uploadDate - 604800]; - break; - default: - return -1; - break; -} -$query2->execute($query2args); -$result = $query2->fetchAll(); -$x = 0; -foreach ($result as &$score) { - $extID = $score["accountID"]; - $query2 = $db->prepare("SELECT userName, userID, icon, color1, color2, color3, iconType, special, extID, isBanned FROM users WHERE extID = :extID"); - $query2->execute([':extID' => $extID]); - $user = $query2->fetchAll(); - $user = $user[0]; - if($user["isBanned"] != 0) continue; - $x++; - $time = date("d/m/Y G.i", $score["timestamp"]); - $scoreType = $score[$mode]; - $lvlstr .= "1:{$user['userName']}:2:{$user['userID']}:9:{$user['icon']}:10:{$user['color1']}:11:{$user['color2']}:14:{$user['iconType']}:15:{$user['color3']}:16:{$extID}:3:{$scoreType}:6:{$x}:42:{$time}|"; -} -echo substr($lvlstr, 0, -1); -?> +prepare("SELECT {$mode} FROM platscores WHERE accountID = :accountID AND levelID = :levelID"); + $query2->execute([':accountID' => $accountID, ':levelID' => $levelID]); + $oldPercent = $query2->fetchColumn(); + if($query2->rowCount() == 0) { + if($scores['time'] > 0) { + $query = $db->prepare("INSERT INTO platscores (accountID, levelID, time, points, timestamp) VALUES (:accountID, :levelID, :time, :points, :timestamp)"); + $query->execute([':accountID' => $accountID, ':levelID' => $levelID, ":time" => $scores['time'], ':points' => $scores['points'], ':timestamp' => $uploadDate]); + $gs->logAction($accountID, 36, $levelID, $mode, $scores['time'], $scores['points']); + } + } else { + if((($mode == "time" AND $oldPercent > $scores['time']) OR ($mode == "points" AND $oldPercent < $scores['points'])) AND $scores['time'] > 0) { + $gs->logAction($accountID, 37, $levelID, $mode, $scores['time'], $scores['points']); + $query = $db->prepare("UPDATE platscores SET {$mode}=:{$mode}, timestamp=:timestamp WHERE accountID=:accountID AND levelID=:levelID"); + } else { + $query = $db->prepare("SELECT count(*) FROM platscores WHERE {$mode}=:{$mode} AND timestamp=:timestamp AND accountID=:accountID AND levelID=:levelID"); + } + $query->execute([':accountID' => $accountID, ':levelID' => $levelID, ":{$mode}" => $scores[$mode], ':timestamp' => $uploadDate]); + } +} +if(!isset($_POST["type"])){ + $type = 1; +}else{ + $type = $_POST["type"]; +} +switch($type){ + case 0: + $friends = $gs->getFriends($accountID); + $friends[] = $accountID; + $friends = implode(",",$friends); + $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND accountID IN ($friends) AND time > 0 ORDER BY {$mode} {$order}"); + $query2args = [':levelID' => $levelID]; + break; + case 1: + $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND time > 0 ORDER BY {$mode} {$order}"); + $query2args = [':levelID' => $levelID]; + break; + case 2: + $query2 = $db->prepare("SELECT * FROM platscores WHERE levelID = :levelID AND timestamp > :time AND time > 0 ORDER BY {$mode} {$order}"); + $query2args = [':levelID' => $levelID, ':time' => $uploadDate - 604800]; + break; + default: + return -1; + break; +} +$query2->execute($query2args); +$result = $query2->fetchAll(); +$x = 0; +foreach ($result as &$score) { + $extID = $score["accountID"]; + $query2 = $db->prepare("SELECT userName, clan, userID, icon, color1, color2, color3, iconType, special, extID, IP FROM users WHERE extID = :extID"); + $query2->execute([':extID' => $extID]); + $user = $query2->fetch(); + $isBanned = $gs->getPersonBan($user['extID'], $user['userID'], 0, $user['IP']); + if($isBanned) continue; + $x++; + $time = $gs->makeTime($score["timestamp"]); + $scoreType = $score[$mode]; + $user["userName"] = $gs->makeClanUsername($user); + $lvlstr .= "1:{$user['userName']}:2:{$user['userID']}:9:{$user['icon']}:10:{$user['color1']}:11:{$user['color2']}:14:{$user['iconType']}:15:{$user['color3']}:16:{$extID}:3:{$scoreType}:6:{$x}:42:{$time}|"; +} +echo substr($lvlstr, 0, -1); +?> \ No newline at end of file diff --git a/incl/scores/getGJScores.php b/incl/scores/getGJScores.php index 2a66de59c..24913ccee 100644 --- a/incl/scores/getGJScores.php +++ b/incl/scores/getGJScores.php @@ -1,135 +1,200 @@ 0"; -}else{ - $sign = "> 19"; -} -if(!empty($_POST["accountID"])){ - $accountID = GJPCheck::getAccountIDOrDie(); -}else{ - $accountID = ExploitPatch::remove($_POST["udid"]); - if(is_numeric($accountID)){ - exit("-1"); - } -} - -$type = ExploitPatch::remove($_POST["type"]); -if($type == "top" OR $type == "creators" OR $type == "relative"){ - if($type == "top"){ - $query = $db->prepare("SELECT * FROM users WHERE isBanned = '0' AND gameVersion $sign AND stars > 0 ORDER BY stars DESC LIMIT 100"); - $query->execute(); - } - if($type == "creators"){ - $query = $db->prepare("SELECT * FROM users WHERE isCreatorBanned = '0' AND creatorPoints > 0 ORDER BY creatorPoints DESC LIMIT 100"); +$accountID = $gs->getIDFromPost(); +$type = ExploitPatch::charclean($_POST["type"]); +switch($type) { + case 'top': + $bans = $gs->getAllBansOfBanType(0); + $extIDs = $userIDs = $bannedIPs = []; + foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } + } + $extIDsString = implode("','", $extIDs); + $userIDsString = implode("','", $userIDs); + $bannedIPsString = implode("|", $bannedIPs); + $queryArray = []; + if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; + if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; + if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; + $queryText = !empty($queryArray) ? '('.implode(' AND ', $queryArray).') AND' : ''; + $query = $db->prepare("SELECT * FROM users WHERE ".$queryText." stars >= :stars ORDER BY stars + moons DESC LIMIT 100"); + $query->execute([':stars' => $leaderboardMinStars]); + break; + case 'creators': + $bans = $gs->getAllBansOfBanType(1); + $extIDs = $userIDs = $bannedIPs = []; + foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } + } + $extIDsString = implode("','", $extIDs); + $userIDsString = implode("','", $userIDs); + $bannedIPsString = implode("|", $bannedIPs); + $queryArray = []; + if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; + if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; + if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; + $queryText = !empty($queryArray) ? '('.implode(' AND ', $queryArray).') AND' : ''; + $query = $db->prepare("SELECT * FROM users WHERE ".$queryText." creatorPoints > 0 ORDER BY creatorPoints DESC LIMIT 100"); $query->execute(); - } - if($type == "relative"){ - $query = "SELECT * FROM users WHERE extID = :accountID"; + break; + case 'relative': + if(!$moderatorsListInGlobal) { + $bans = $gs->getAllBansOfBanType(0); + $extIDs = $userIDs = $bannedIPs = []; + foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } + } + $extIDsString = implode("','", $extIDs); + $userIDsString = implode("','", $userIDs); + $bannedIPsString = implode("|", $bannedIPs); + $queryArray = []; + if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; + if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; + if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; + $queryText = !empty($queryArray) ? 'AND ('.implode(' AND ', $queryArray).')' : ''; + $query = "SELECT * FROM users WHERE extID = :accountID"; + $query = $db->prepare($query); + $query->execute([':accountID' => $accountID]); + $user = $query->fetch(); + $stars = $user["stars"]; + $count = $_POST["count"] ? ExploitPatch::number($_POST["count"]) : 50; + $count = floor($count / 2); + $query = $db->prepare("SELECT A.* FROM ( + ( + SELECT * FROM users + WHERE stars <= :stars + ".$queryText." + ORDER BY stars + moons DESC + LIMIT $count + ) + UNION + ( + SELECT * FROM users + WHERE stars >= :stars + ".$queryText." + ORDER BY stars + moons ASC + LIMIT $count + ) + ) as A + ORDER BY A.stars DESC"); + $query->execute([':stars' => $stars]); + } else { + $query = $db->prepare("SELECT * FROM users INNER JOIN roleassign ON users.extID = roleassign.accountID ORDER BY users.userName ASC"); + $query ->execute(); + } + break; + case 'friends': + $query = "SELECT * FROM friendships WHERE person1 = :accountID OR person2 = :accountID"; $query = $db->prepare($query); $query->execute([':accountID' => $accountID]); $result = $query->fetchAll(); - $user = $result[0]; - $stars = $user["stars"]; - if($_POST["count"]){ - $count = ExploitPatch::remove($_POST["count"]); - }else{ - $count = 50; + $people = ""; + foreach($result as &$friendship) { + $person = $friendship["person2"] == $accountID ? $friendship["person1"] : $friendship["person2"]; + $people .= ",".$person; } - $count = floor($count / 2); - $query = $db->prepare("SELECT A.* FROM ( - ( - SELECT * FROM users - WHERE stars <= :stars - AND isBanned = 0 - AND gameVersion $sign - ORDER BY stars DESC - LIMIT $count - ) - UNION - ( - SELECT * FROM users - WHERE stars >= :stars - AND isBanned = 0 - AND gameVersion $sign - ORDER BY stars ASC - LIMIT $count - ) - ) as A - ORDER BY A.stars DESC"); - $query->execute([':stars' => $stars]); - } - $result = $query->fetchAll(); - if($type == "relative"){ + $query = "SELECT * FROM users WHERE extID IN (:accountID $people ) ORDER BY stars DESC"; + $query = $db->prepare($query); + $query->execute([':accountID' => $accountID]); + break; + case 'week': + $bans = $gs->getAllBansOfBanType(0); + $extIDs = $userIDs = $bannedIPs = []; + foreach($bans AS &$ban) { + switch($ban['personType']) { + case 0: + $extIDs[] = $ban['person']; + break; + case 1: + $userIDs[] = $ban['person']; + break; + case 2: + $bannedIPs[] = $gs->IPForBan($ban['person'], true); + break; + } + } + $extIDsString = implode("','", $extIDs); + $userIDsString = implode("','", $userIDs); + $bannedIPsString = implode("|", $bannedIPs); + $queryArray = []; + if(!empty($extIDsString)) $queryArray[] = "extID NOT IN ('".$extIDsString."')"; + if(!empty($userIDsString)) $queryArray[] = "userID NOT IN ('".$userIDsString."')"; + if(!empty($bannedIPsString)) $queryArray[] = "IP NOT REGEXP '".$bannedIPsString."'"; + $queryText = !empty($queryArray) ? 'AND ('.implode(' AND ', $queryArray).')' : ''; + $query = $db->prepare("SELECT users.*, SUM(actions.value) AS stars, SUM(actions.value2) AS coins, SUM(actions.value3) AS demons FROM actions INNER JOIN users ON actions.account = users.extID WHERE type = '9' AND timestamp > :time ".$queryText." AND actions.value > 0 GROUP BY (stars) DESC ORDER BY stars + moons DESC LIMIT 100"); + $query->execute([':time' => time() - 604800]); + break; +} +$result = $query->fetchAll(); +if($type == "relative") { + if(!$moderatorsListInGlobal) { $user = $result[0]; $extid = $user["extID"]; $e = "SET @rownum := 0;"; $query = $db->prepare($e); $query->execute(); + $queryText = !empty($queryText) && trim($queryText) != 'AND' ? 'WHERE '.substr($queryText, 4) : ''; $f = "SELECT rank, stars FROM ( - SELECT @rownum := @rownum + 1 AS rank, stars, extID, isBanned - FROM users WHERE isBanned = '0' AND gameVersion $sign ORDER BY stars DESC - ) as result WHERE extID=:extid"; + SELECT @rownum := @rownum + 1 AS rank, stars, extID + FROM users ".$queryText." ORDER BY stars + moons DESC + ) as result WHERE extID = :extid"; $query = $db->prepare($f); $query->execute([':extid' => $extid]); $leaderboard = $query->fetchAll(); - //var_dump($leaderboard); $leaderboard = $leaderboard[0]; $xi = $leaderboard["rank"] - 1; - } - foreach($result as &$user) { - $xi++; - $extid = is_numeric($user['extID']) ? $user['extID'] : 0; - if($date == "01-04"){ - $lbstring .= "1:sakujes:2:".$user["userID"].":13:999:17:999:6:".$xi.":9:9:10:9:11:8:14:1:15:3:16:".$extid.":3:999:8:99999:4:999:7:".$extid.":46:99999|"; - }else{ - $lbstring .= "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":6:".$xi.":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":51:".$user["color3"].":14:".$user["iconType"].":15:".$user["special"].":16:".$extid.":3:".$user["stars"].":8:".round($user["creatorPoints"],0,PHP_ROUND_HALF_DOWN).":4:".$user["demons"].":7:".$extid.":46:".$user["diamonds"].":52:".$user["moons"]."|"; - } - } -} -if($type == "friends"){ - $query = "SELECT * FROM friendships WHERE person1 = :accountID OR person2 = :accountID"; - $query = $db->prepare($query); - $query->execute([':accountID' => $accountID]); - $result = $query->fetchAll(); - $people = ""; - foreach ($result as &$friendship) { - $person = $friendship["person1"]; - if($friendship["person1"] == $accountID){ - $person = $friendship["person2"]; - } - $people .= ",".$person; - } - $query = "SELECT * FROM users WHERE extID IN (:accountID $people ) ORDER BY stars DESC"; - $query = $db->prepare($query); - $query->execute([':accountID' => $accountID]); - $result = $query->fetchAll(); - foreach($result as &$user){ - if(is_numeric($user["extID"])){ - $extid = $user["extID"]; - }else{ - $extid = 0; - } - $xi++; - if($date == "01-04"){ - $lbstring .= "1:sakujes:2:".$user["userID"].":13:999:17:999:6:".$xi.":9:9:10:9:11:8:14:1:15:3:16:".$extid.":3:999:8:99999:4:999:7:".$extid.":46:99999|"; - }else{ - $lbstring .= "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":6:".$xi.":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":14:".$user["iconType"].":15:".$user["special"].":16:".$extid.":3:".$user["stars"].":8:".round($user["creatorPoints"],0,PHP_ROUND_HALF_DOWN).":4:".$user["demons"].":7:".$extid.":46:".$user["diamonds"]."|"; - } - } + } else $lbstring = '1:---Moderators---:2:0:13:0:17:0:6:0:9:0:10:0:11:0:51:0:14:0:15:0:16:0:3:0:8:0:4:0:7:0:46:0:52:0|'; } -if($lbstring == ""){ - exit("-1"); +foreach($result as &$user) { + $xi++; + $user["userName"] = $gs->makeClanUsername($user); + $extid = is_numeric($user['extID']) ? $user['extID'] : 0; + if($date == "01-04" && $sakujes) $lbstring .= "1:sakujes:2:".$user["userID"].":13:999:17:999:6:".$xi.":9:9:10:9:11:8:14:1:15:3:16:".$extid.":3:999:8:99999:4:999:7:".$extid.":46:99999|"; + else $lbstring .= "1:".$user["userName"].":2:".$user["userID"].":13:".$user["coins"].":17:".$user["userCoins"].":6:".$xi.":9:".$user["icon"].":10:".$user["color1"].":11:".$user["color2"].":51:".$user["color3"].":14:".$user["iconType"].":15:".$user["special"].":16:".$extid.":3:".$user["stars"].":8:".round($user["creatorPoints"],0,PHP_ROUND_HALF_DOWN).":4:".$user["demons"].":7:".$extid.":46:".$user["diamonds"].":52:".$user["moons"]."|"; } -$lbstring = substr($lbstring, 0, -1); -echo $lbstring; +if($lbstring == "") exit("-1"); +echo substr($lbstring, 0, -1); ?> \ No newline at end of file diff --git a/incl/scores/updateGJUserScore.php b/incl/scores/updateGJUserScore.php index 474633501..ff7f78dc6 100644 --- a/incl/scores/updateGJUserScore.php +++ b/incl/scores/updateGJUserScore.php @@ -1,11 +1,13 @@ getIP(); -$query = $db->prepare("SELECT stars,coins,demons,userCoins,diamonds,moons FROM users WHERE userID=:userID LIMIT 1"); //getting differences +if(Automod::isAccountsDisabled(2)) exit((string)$userID); + +$query = $db->prepare("SELECT stars, coins, demons, userCoins, diamonds, moons FROM users WHERE userID = :userID LIMIT 1"); //getting differences $query->execute([':userID' => $userID]); $old = $query->fetch(); @@ -104,8 +108,7 @@ $ucdiff = $userCoins - $old["userCoins"]; $diadiff = $diamonds - $old["diamonds"]; $moondiff = $moons - $old["moons"]; -$query2 = $db->prepare("INSERT INTO actions (type, value, timestamp, account, value2, value3, value4, value5, value6) - VALUES ('9',:stars,:timestamp,:account,:coinsd, :demon, :usrco, :diamond, :moons)"); //creating the action -$query2->execute([':timestamp' => time(), ':stars' => $starsdiff, ':account' => $userID, ':coinsd' => $coindiff, ':demon' => $demondiff, ':usrco' => $ucdiff, ':diamond' => $diadiff, ':moons' => $moondiff]); +$gs->logAction($id, 9, $starsdiff, $coindiff, $demondiff, $ucdiff, $diadiff, $moondiff); +if($gameVersion < 20 && !is_numeric($id) && $starsdiff + $coindiff + $demondiff + $ucdiff + $diadiff + $moondiff != 0) exit('-9'); echo $userID; ?> \ No newline at end of file diff --git a/likeGJItem.php b/likeGJItem.php index 17626b8ac..5ae38906e 100644 --- a/likeGJItem.php +++ b/likeGJItem.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/likeGJItem19.php b/likeGJItem19.php index 17626b8ac..5ae38906e 100644 --- a/likeGJItem19.php +++ b/likeGJItem19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/likeGJItem20.php b/likeGJItem20.php index 17626b8ac..5ae38906e 100644 --- a/likeGJItem20.php +++ b/likeGJItem20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/likeGJItem21.php b/likeGJItem21.php index 17626b8ac..5ae38906e 100644 --- a/likeGJItem21.php +++ b/likeGJItem21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/likeGJItem211.php b/likeGJItem211.php index 17626b8ac..5ae38906e 100644 --- a/likeGJItem211.php +++ b/likeGJItem211.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/likeGJLevel.php b/likeGJLevel.php new file mode 100644 index 000000000..5ae38906e --- /dev/null +++ b/likeGJLevel.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/music/.htaccess b/music/.htaccess new file mode 100644 index 000000000..55c4942ae --- /dev/null +++ b/music/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ %{REQUEST_URI}/../../music/handler.php?%{QUERY_STRING}&request=%{REQUEST_URI} [L] \ No newline at end of file diff --git a/music/handler.php b/music/handler.php new file mode 100644 index 000000000..682980fad --- /dev/null +++ b/music/handler.php @@ -0,0 +1,54 @@ +prepare('SELECT reuploadTime FROM songs WHERE reuploadTime > 0 ORDER BY reuploadTime DESC LIMIT 1'); + $time->execute(); + $time = $time->fetchColumn(); + $gs->updateLibraries($_GET['token'], $_GET['expires'], $time, 1); + } + echo file_get_contents($datFile); + break; + case 'musiclibrary_version.txt': + case 'musiclibrary_version_02.txt': + $time = $db->prepare('SELECT reuploadTime FROM songs WHERE reuploadTime > 0 ORDER BY reuploadTime DESC LIMIT 1'); + $time->execute(); + $time = $time->fetchColumn(); + if(!$time) $time = 1; + $gs->updateLibraries($_GET['token'], $_GET['expires'], $time, 1); + $times = []; + foreach($customLibrary AS $library) { + if($library[2] !== null) $times[] = explode(', ', file_get_contents('s'.$library[0].'.txt'))[1]; + } + $times[] = $time; + rsort($times); + echo $times[0]; + break; + default: + $servers = []; + foreach($customLibrary AS $library) { + $servers[$library[0]] = $library[2]; + } + if(!file_exists('ids.json')) { + $time = $db->prepare('SELECT reuploadTime FROM songs WHERE reuploadTime > 0 ORDER BY reuploadTime DESC LIMIT 1'); + $time->execute(); + $time = $time->fetchColumn(); + $gs->updateLibraries($_GET['token'], $_GET['expires'], $time, 1); + } + $musicID = explode('.', $file)[0]; + $song = $gs->getLibrarySongInfo($musicID, true); + if($song) $url = urldecode($song['download']); + else $url = urldecode($gs->getSongInfo($musicID, 'download')); + if(empty($url)) header("Location: https://www.newgrounds.com/audio/listen/$musicID"); + header("Location: $url"); + break; +} +?> diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 000000000..cde86d84f --- /dev/null +++ b/nginx.conf @@ -0,0 +1,27 @@ +location ^~ /sfx/ { + try_files $uri /sfx/handler.php?$query_string&request=$uri; +} +location ^~ /music/ { + try_files $uri /music/handler.php?$query_string&request=$uri; +} +location ^~ /dashboard/clan/ { + try_files $uri /dashboard/clan/index.php?id=$request_uri; +} +location ^~ /dashboard/profile/ { + try_files $uri /dashboard/profile/index.php?id=$request_uri; +} +location ^~ /dashboard/demonlist/ { + try_files $uri /dashboard/demonlist/index.php?id=$request_uri; +} +location ^~ /dashboard/messenger/ { + try_files $uri /dashboard/messenger/index.php?id=$request_uri; +} +location ^~ /config/ { + deny all; +} +location ^~ /data/ { + deny all; +} +location ^~ /incl/ { + deny all; +} diff --git a/rateGJDemon21.php b/rateGJDemon21.php index 3297a933e..a4d56798d 100644 --- a/rateGJDemon21.php +++ b/rateGJDemon21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/rateGJLevel.php b/rateGJLevel.php new file mode 100644 index 000000000..5a0a078eb --- /dev/null +++ b/rateGJLevel.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/rateGJStars20.php b/rateGJStars20.php index c6b6313a9..f2b246dca 100644 --- a/rateGJStars20.php +++ b/rateGJStars20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/rateGJStars211.php b/rateGJStars211.php index c6b6313a9..f2b246dca 100644 --- a/rateGJStars211.php +++ b/rateGJStars211.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/readGJFriendRequest20.php b/readGJFriendRequest20.php index 7acdf2cbe..cff46da40 100644 --- a/readGJFriendRequest20.php +++ b/readGJFriendRequest20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/removeGJFriend20.php b/removeGJFriend20.php index 09a430f9f..26f0a4a3d 100644 --- a/removeGJFriend20.php +++ b/removeGJFriend20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/reportGJLevel.php b/reportGJLevel.php index bb2164a82..e96884094 100644 --- a/reportGJLevel.php +++ b/reportGJLevel.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/requestUserAccess.php b/requestUserAccess.php index 29c3dbc0b..62406a134 100644 --- a/requestUserAccess.php +++ b/requestUserAccess.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/restoreGJItems.php b/restoreGJItems.php new file mode 100644 index 000000000..89022fe70 --- /dev/null +++ b/restoreGJItems.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/sfx/.htaccess b/sfx/.htaccess new file mode 100644 index 000000000..1c8fbb99c --- /dev/null +++ b/sfx/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ %{REQUEST_URI}/../../sfx/handler.php?%{QUERY_STRING}&request=%{REQUEST_URI} [L] \ No newline at end of file diff --git a/sfx/handler.php b/sfx/handler.php new file mode 100644 index 000000000..27c10862a --- /dev/null +++ b/sfx/handler.php @@ -0,0 +1,50 @@ +prepare('SELECT reuploadTime FROM sfxs ORDER BY reuploadTime DESC LIMIT 1'); + $time->execute(); + $time = $time->fetchColumn(); + $gs->updateLibraries($_GET['token'], $_GET['expires'], $time, 0); + } + echo file_get_contents($datFile); + break; + case 'sfxlibrary_version.txt': + $time = $db->prepare('SELECT reuploadTime FROM sfxs WHERE reuploadTime > 0 ORDER BY reuploadTime DESC LIMIT 1'); + $time->execute(); + $time = $time->fetchColumn(); + if(!$time) $time = 1; + $gs->updateLibraries($_GET['token'], $_GET['expires'], $time, 0); + $times = []; + foreach($customLibrary AS $library) { + if($library[2] !== null) $times[] = explode(', ', file_get_contents('s'.$library[0].'.txt'))[1]; + } + $times[] = $time; + rsort($times); + echo $times[0]; + break; + default: + $servers = []; + foreach($customLibrary AS $library) { + $servers[$library[0]] = $library[2]; + } + $sfxID = explode('.', substr($file, 1, strlen($file)))[0]; + if(!file_exists('ids.json')) { + $time = $db->prepare('SELECT reuploadTime FROM sfxs ORDER BY reuploadTime DESC LIMIT 1'); + $time->execute(); + $time = $time->fetchColumn(); + $gs->updateLibraries($_GET['token'], $_GET['expires'], $time, 0); + } + $song = $gs->getLibrarySongInfo($sfxID, 'sfx'); + $url = urldecode($song['download']); + header("Location: $url"); + break; +} +?> \ No newline at end of file diff --git a/submitGJUserInfo.php b/submitGJUserInfo.php new file mode 100644 index 000000000..60cf5ae3c --- /dev/null +++ b/submitGJUserInfo.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/suggestGJStars20.php b/suggestGJStars20.php index 788d97f6c..526429973 100644 --- a/suggestGJStars20.php +++ b/suggestGJStars20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/tools/.htaccess b/tools/.htaccess deleted file mode 100644 index 84d959577..000000000 --- a/tools/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Options +Indexes \ No newline at end of file diff --git a/tools/account/activateAccount.php b/tools/account/activateAccount.php deleted file mode 100644 index 9c513b824..000000000 --- a/tools/account/activateAccount.php +++ /dev/null @@ -1,30 +0,0 @@ -prepare("UPDATE accounts SET isActive = 1 WHERE userName LIKE :userName"); - $query->execute(['userName' => $userName]); - echo "Account has been succesfully activated."; - } - elseif ($pass == 1) { - echo "Account is already activated."; - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
- Username:
- Password:
'; - Captcha::displayCaptcha(); - echo '
'; -} -?> \ No newline at end of file diff --git a/tools/account/changePassword.php b/tools/account/changePassword.php deleted file mode 100644 index 5e749cc95..000000000 --- a/tools/account/changePassword.php +++ /dev/null @@ -1,49 +0,0 @@ -prepare("UPDATE accounts SET password=:password, salt=:salt WHERE userName=:userName"); - $query->execute([':password' => $passhash, ':userName' => $userName, ':salt' => $salt]); - GeneratePass::assignGJP2($accid, $pass); - echo "Password changed. Go back to tools"; - //decrypting save - $query = $db->prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - $saveData = file_get_contents("../../data/accounts/$accountID"); - if(file_exists("../../data/accounts/keys/$accountID")){ - $protected_key_encoded = file_get_contents("../../data/accounts/keys/$accountID"); - if($protected_key_encoded != ""){ - $protected_key = KeyProtectedByPassword::loadFromAsciiSafeString($protected_key_encoded); - $user_key = $protected_key->unlockKey($oldpass); - try { - $saveData = Crypto::decrypt($saveData, $user_key); - } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) { - exit("Unable to update save data encryption"); - } - file_put_contents("../../data/accounts/$accountID",$saveData); - file_put_contents("../../data/accounts/keys/$accountID",""); - } - } -}else{ - echo "Invalid old password or nonexistent account. Try again"; - -} -}else{ - echo '
Username:
Old password:
New password:
'; -} -?> \ No newline at end of file diff --git a/tools/account/changeUsername.php b/tools/account/changeUsername.php deleted file mode 100644 index 664eba9ad..000000000 --- a/tools/account/changeUsername.php +++ /dev/null @@ -1,27 +0,0 @@ - 20) - exit("Username too long - 20 characters max. Try again"); - $query = $db->prepare("UPDATE accounts SET username=:newusr WHERE userName=:userName"); - $query->execute([':newusr' => $newusr, ':userName' => $userName]); - if($query->rowCount()==0){ - echo "Invalid password or nonexistant account. Try again"; - }else{ - echo "Username changed. Go back to tools"; - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
Old username:
New username:
Password:
'; -} -?> \ No newline at end of file diff --git a/tools/account/registerAccount.php b/tools/account/registerAccount.php deleted file mode 100644 index 3c7d5f0f6..000000000 --- a/tools/account/registerAccount.php +++ /dev/null @@ -1,56 +0,0 @@ -Username should be more than 3 characters.

Username:
Password:
Repeat Password:
Email:
Repeat Email:
'; - }elseif(strlen($password) < 6){ - // just why did you want to give a short password? do you wanna be hacked? - echo 'Password should be more than 6 characters.

Username:
Password:
Repeat Password:
Email:
Repeat Email:
'; - }else{ - // this checks if there is another account with the same username as your input - $query = $db->prepare("SELECT count(*) FROM accounts WHERE userName LIKE :userName"); - $query->execute([':userName' => $username]); - $registred_users = $query->fetchColumn(); - if($registred_users > 0){ - // why did you want to make a new account with the same username as someone else's - echo 'Username already taken.

Username:
Password:
Repeat Password:
Email:
Repeat Email:
'; - }else{ - if($password != $repeat_password){ - // this is when the passwords do not match - echo 'Passwords do not match.

Username:
Password:
Repeat Password:
Email:
Repeat Email:
'; - }elseif($email != $repeat_email){ - // this is when the emails dont match - echo 'Emails do not match.

Username:
Password:
Repeat Password:
Email:
Repeat Email:
'; - }else{ - // hashing your password and registering your account - $hashpass = password_hash($password, PASSWORD_DEFAULT); - $query2 = $db->prepare("INSERT INTO accounts (userName, password, email, registerDate, isActive, gjp2) - VALUES (:userName, :password, :email, :time, :isActive, :gjp2)"); - $query2->execute([':userName' => $username, ':password' => $hashpass, ':email' => $email,':time' => time(), ':isActive' => $preactivateAccounts ? 1 : 0, ':gjp2' => GeneratePass::GJP2hash($password)]); - // there you go, you are registered. - $activationInfo = $preactivateAccounts ? "No e-mail verification required, you can login." : "Click here to activate it."; - echo "Account registred. ${activationInfo} Go back to tools"; - } - } - } -}else{ - // this is given when we dont have an input - echo '
Username:
Password:
Repeat Password:
Email:
Repeat Email:
'; -} -?> diff --git a/tools/addQuests.php b/tools/addQuests.php deleted file mode 100644 index 6615f5130..000000000 --- a/tools/addQuests.php +++ /dev/null @@ -1,52 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - if($gs->checkPermission($accountID, "toolQuestsCreate") == false){ - echo "This account doesn't have the permissions to access this tool. Try again"; - }else{ - if(!is_numeric($type) OR !is_numeric($amount) OR !is_numeric($reward) OR $type > 3){ - exit("Type/Amount/Reward invalid"); - } - - $query = $db->prepare("INSERT INTO quests (type, amount, reward, name) VALUES (:type,:amount,:reward,:name)"); - $query->execute([':type' => $type, ':amount' => $amount, ':reward' => $reward, ':name' => $name]); - $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3, value4) VALUES ('25',:value,:timestamp,:account,:amount,:reward,:name)"); - $query->execute([':value' => $type, ':timestamp' => time(), ':account' => $accountID, ':amount' => $amount, ':reward' => $reward, ':name' => $name]); - if($db->lastInsertId() < 3) { - exit("Successfully added Quest! It's recommended that you should add a few more."); - } else { - exit("Successfully added Quest!"); - } - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
Username: -
Password: -
Quest Type: -
Amount: (How many orbs/coins/stars you need to collect) -
Reward: (How many Diamonds you get as a reward) -
Quest Name: -
'; -} -?> diff --git a/tools/bot/dailyLevelBot.php b/tools/bot/dailyLevelBot.php deleted file mode 100644 index 0db4b0f0f..000000000 --- a/tools/bot/dailyLevelBot.php +++ /dev/null @@ -1,95 +0,0 @@ -prepare("SELECT timestamp,levelID FROM dailyfeatures WHERE timestamp < :time ORDER BY timestamp DESC LIMIT 1"); //getting level info -$query->execute([':time' => time()]); -$dailyinfo = $query->fetch(); -$query = $db->prepare("SELECT * FROM levels WHERE levelID = :str LIMIT 1"); //getting level info -$query->execute([':str' => $dailyinfo["levelID"]]); -//checking if exists -if($query->rowCount() == 0){ - exit("The level you are searching for doesn't exist"); -} -$levelInfo = $query->fetchAll()[0]; -//getting creator name -$query = $db->prepare("SELECT userName FROM users WHERE userID = :userID"); -$query->execute([':userID' => $levelInfo["userID"]]); -$creator = $query->fetchColumn(); -//getting song name -if($levelInfo["songID"] != 0){ - $query = $db->prepare("SELECT name, authorName, ID FROM songs WHERE ID = :songID"); - $query->execute([':songID' => $levelInfo["songID"]]); - $songInfo = $query->fetchAll()[0]; - $song = $songInfo["name"] . " by " . $songInfo["authorName"] . " (" . $songInfo["ID"] . ")"; -}else{ - $song = $gs->getAudioTrack($levelInfo["audioTrack"]); -} -//getting difficulty -if($levelInfo["starDemon"] == 1){ - $difficulty .= $gs->getDemonDiff($levelInfo["starDemonDiff"]) . " "; -} -$difficulty .= $gs->getDifficulty($levelInfo["starDifficulty"],$levelInfo["starAuto"],$levelInfo["starDemon"]); -$difficulty .= " " . $levelInfo["starStars"] ."* "; -if($levelInfo["starEpic"] != 0){ - $difficulty .= "Epic "; -}else if($levelInfo["starFeatured"] != 0){ - $difficulty .= "Featured "; -} -//getting length -$length = $gs->getLength($levelInfo["levelLength"]); -//times -$uploadDate = date("d-m-Y G-i", $levelInfo["uploadDate"]); -$updateDate = date("d-m-Y G-i", $levelInfo["updateDate"]); -//getting original level -if($levelInfo["original"] != 0){ - $original = "\r\n**Original:** " . $levelInfo["original"] .""; -} -if($levelInfo["originalReup"] != 0){ - $original .= "\r\n**Reupload Original:** ".$levelInfo["originalReup"] . ""; -} -//whorated -$query = $db->prepare("SELECT * FROM modactions WHERE value3 = :lvid AND type = '1'"); -$query->execute([':lvid' => $levelInfo["levelID"]]); -$actionlist = $query->fetchAll(); -if($query->rowCount() == 0){ - $whorated = ""; -} -$x = 0; -foreach($actionlist as &$action){ - $query = $db->prepare("SELECT * FROM accounts WHERE accountID = :id"); - $query->execute([':id' => $action["account"]]); - $result2 = $query->fetchAll(); - $result2 = $result2[0]; - if($x != 0){ - $whorated .= " and "; - }else{ - $whorated = "\r\n**Rated by: **"; - } - $whorated .= $result2["userName"].""; - $x++; -} -//coins -$coins = $levelInfo["coins"]; -if($levelInfo["starCoins"] != 0){ - $coins .= " Verified"; -}else{ - $coins .= " Unverified"; -} -//gameVersion -$gameVersion = $gs->getGameVersion($levelInfo["gameVersion"]); -$dailytime = date("d-m-Y G-i", $dailyinfo["timestamp"]); -//outputting everything -echo "**NAME:** ".$levelInfo["levelName"]." -**ID:** ".$levelInfo["levelID"]." -**Author:** ".$creator." -**Song:** ".$song." -**Difficulty:** ".$difficulty." -**Daily Since:** ".$dailytime." -***DO ***`!level ".$levelInfo["levelID"]."`*** FOR MORE INFO***"; -?> \ No newline at end of file diff --git a/tools/bot/discordLinkReq.php b/tools/bot/discordLinkReq.php deleted file mode 100644 index 6b179274b..000000000 --- a/tools/bot/discordLinkReq.php +++ /dev/null @@ -1,45 +0,0 @@ -prepare("SELECT discordLinkReq FROM accounts WHERE userName = :account"); -$query->execute([':account' => $account]); -$discordLinkReq = $query->fetchColumn(); -if($discordLinkReq != 0){ - exit("This user has an ongoing link request already"); -} -$query = $db->prepare("SELECT count(*) FROM accounts WHERE discordID = :discordID OR discordLinkReq = :discordID"); -$query->execute([':discordID' => $discordID]); -if($query->fetchColumn() != 0){ - exit("You're linked or have sent a link request to a different account already"); -} -$query = $db->prepare("UPDATE accounts SET discordLinkReq = :discordID WHERE userName = :account"); -$query->execute([':account' => $account, ':discordID' => $discordID]); -if($query->rowCount() == 0){ - exit("This account doesn't exist."); -} -//in-game message -$accinfo = $gs->getDiscordAcc($discordID); -//sending the msg -$query = $db->prepare("SELECT accountID FROM accounts WHERE userName = :account"); -$query->execute([':account' => $account]); -$accountID = $query->fetchColumn(); -$message = $xc->cipher("The Discord account '$accinfo' has attempted to link to this GDPS account. If that was you, please comment '!discord accept' on your profile. If it wasn't you, please comment '!discord deny'. If you ever wish to unlink, please comment '!discord unlink' on your profile.", 14251); -$message = base64_encode($message); -$query = $db->prepare("INSERT INTO messages (subject, body, accID, userID, userName, toAccountID, secret, timestamp) -VALUES ('TmV3IEFjY291bnQgTGluayBSZXF1ZXN0', :body, 263, 388, 'GDPS Bot', :toAccountID, 'Automatic Message', :uploadDate)"); -$query->execute([':body' => $message, ':toAccountID' => $accountID, ':uploadDate' => time()]); -echo "Link request has been succesfully sent, please check your in-game messages"; -?> \ No newline at end of file diff --git a/tools/bot/discordLinkResetPass.php b/tools/bot/discordLinkResetPass.php deleted file mode 100644 index 3d8098b14..000000000 --- a/tools/bot/discordLinkResetPass.php +++ /dev/null @@ -1,20 +0,0 @@ -randomString(); -$passhash = password_hash($newpass, PASSWORD_DEFAULT); -$query = $db->prepare("UPDATE accounts SET password=:password, gjp2=:gjp2 WHERE discordID=:discordID"); -$query->execute([':password' => $passhash, ':discordID' => $discordID, ':gjp2' => GeneratePass::GJP2hash($newpass)]); -$gs->sendDiscordPM($discordID, "Password changed to $newpass\r\nFor your security we advise you to go change your password to http://pi.michaelbrabec.cz:9010/a/tools/account/changePassword.php"); -echo "Please check your DMs"; -?> \ No newline at end of file diff --git a/tools/bot/discordLinkTransferRoles.php b/tools/bot/discordLinkTransferRoles.php deleted file mode 100644 index 4164d829c..000000000 --- a/tools/bot/discordLinkTransferRoles.php +++ /dev/null @@ -1,26 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE discordID = :discordID"); -$query->execute([':discordID' => $discordID]); -if($query->rowCount() == 0){ - exit("Your Discord account isn't linked to a GDPS account <@$discordID>"); -} -$accountID = $query->fetchColumn(); -$query = $db->prepare("DELETE FROM roleassign WHERE accountID = :accountID"); -$query->execute([':accountID' => $accountID]); -foreach($rolesarray as &$role){ - $query = $db->prepare("INSERT INTO roleassign (roleID, accountID) VALUES (:roleID, :accountID)"); - $query->execute([':roleID' => $role, ':accountID' => $accountID]); -} -echo "Roles transferred! <@$discordID>"; -?> \ No newline at end of file diff --git a/tools/bot/discordLinkUnlink.php b/tools/bot/discordLinkUnlink.php deleted file mode 100644 index c1c1cf2cf..000000000 --- a/tools/bot/discordLinkUnlink.php +++ /dev/null @@ -1,25 +0,0 @@ -prepare("UPDATE accounts SET discordID=0 WHERE discordID=:discordID"); -$query->execute([':discordID' => $discordID]); -if($query->rowCount() > 0){ - $message = "Your GDPS account has been unlinked."; -} -$query = $db->prepare("UPDATE accounts SET discordLinkReq=0 WHERE discordLinkReq=:discordID"); -$query->execute([':discordID' => $discordID]); -if($query->rowCount() > 0){ - $message = "Your link request has been cancelled."; -} -echo $message; -?> \ No newline at end of file diff --git a/tools/bot/latestSongBot.php b/tools/bot/latestSongBot.php deleted file mode 100644 index fa11856ec..000000000 --- a/tools/bot/latestSongBot.php +++ /dev/null @@ -1,11 +0,0 @@ -prepare("SELECT `AUTO_INCREMENT` - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = :database - AND TABLE_NAME = 'songs';"); -$query->execute([':database' => $dbname]); -echo $query->fetchColumn(); -?> \ No newline at end of file diff --git a/tools/bot/leaderboardsBot.php b/tools/bot/leaderboardsBot.php deleted file mode 100644 index ca2c7f966..000000000 --- a/tools/bot/leaderboardsBot.php +++ /dev/null @@ -1,83 +0,0 @@ -quote($page); -$page = str_replace("'", "", $page); -if($page == ""){ - $page = 0; -} -$page--; -if($page < 0){ - $page = 0; -} -$page = $page * 10; -if($type =="stars" OR $type == "diamonds" OR $type == "usrcoins" OR $type == "coins" OR $type == "demons" OR $type == "cp" OR $type == "orbs" OR $type == "levels" OR $type == "friends"){ - $typename = $type; - switch($type){ - case "stars": - $thing = "stars"; - break; - case "coins": - $thing = "coins"; - break; - case "diamonds": - $thing = "diamonds"; - break; - case "usrcoins": - $typename = "User Coins"; - $thing = "userCoins"; - break; - case "demons": - $thing = "demons"; - break; - case "cp": - $thing = "creatorPoints"; - break; - case "orbs": - $thing = "orbs"; - break; - case "levels": - $typename = "Completed Levels"; - $thing = "completedLvls"; - break; - case "friends": - $thing = "friendsCount"; - } - $query = "SELECT $thing , userName, extID FROM users WHERE isBanned = '0' ORDER BY $thing DESC LIMIT 10 OFFSET $page"; - if($type == "friends"){ - $query = "SELECT userName, friendsCount, accountID FROM accounts ORDER BY friendsCount DESC LIMIT 10 OFFSET $page"; - } - $query = $db->prepare($query); - $query->execute([':page' => $page]); - $result = $query->fetchAll(); - echo "`# | Username | ".str_pad($typename, 16, " ", STR_PAD_LEFT)." | Linked? |`\r\n"; - echo "`-----|-----------------|------------------|---------|`\r\n"; - $xi = $page; - foreach($result as &$user){ - $query = $db->prepare("SELECT discordID FROM accounts WHERE accountID = :extID"); - if($type == "friends"){ - $query->execute([':extID' => $user["accountID"]]); - }else{ - $query->execute([':extID' => $user["extID"]]); - } - if($query->rowCount() == 0){ - $link = "N/A"; - }else{ - if($query->fetchColumn() == 0){ - $link = "No"; - }else{ - $link = "Yes"; - } - } - $xi++; - $xyz = str_pad($xi, 4, " ", STR_PAD_RIGHT); - //$date = date("d/m/Y H:i", $user["lastPlayed"]); - echo "`$xyz | ".str_pad($user["userName"], 15, " ", STR_PAD_LEFT)." | ".str_pad($user[$thing], 16, " ", STR_PAD_LEFT)." | " . str_pad($link, 7, " ", STR_PAD_LEFT) . " |`\r\n"; - } -}else{ - echo "**Command usage: *!top *\r\nValid types are: Stars, Diamonds, Coins, Usrcoins, Demons, CP, Orbs, Levels, Friends**"; -} -?> \ No newline at end of file diff --git a/tools/bot/levelSearchBot.php b/tools/bot/levelSearchBot.php deleted file mode 100644 index 8c1b5b5f4..000000000 --- a/tools/bot/levelSearchBot.php +++ /dev/null @@ -1,101 +0,0 @@ -prepare("(SELECT * FROM levels WHERE levelID = :str) UNION (SELECT * FROM levels WHERE levelName LIKE CONCAT('%', :str, '%') ORDER BY likes DESC LIMIT 1)"); //getting level info -$query->execute([':str' => $str]); -//checking if exists -if($query->rowCount() == 0){ - exit("The level you are searching for doesn't exist"); -} -$levelInfo = $query->fetchAll()[0]; -//getting creator name -$query = $db->prepare("SELECT userName FROM users WHERE userID = :userID"); -$query->execute([':userID' => $levelInfo["userID"]]); -$creator = $query->fetchColumn(); -//getting song name -if($levelInfo["songID"] != 0){ - $query = $db->prepare("SELECT name, authorName, ID FROM songs WHERE ID = :songID"); - $query->execute([':songID' => $levelInfo["songID"]]); - $songInfo = $query->fetchAll()[0]; - $song = $songInfo["name"] . " by " . $songInfo["authorName"] . " (" . $songInfo["ID"] . ")"; -}else{ - $song = $gs->getAudioTrack($levelInfo["audioTrack"]); -} -//getting difficulty -if($levelInfo["starDemon"] == 1){ - $difficulty .= $gs->getDemonDiff($levelInfo["starDemonDiff"]) . " "; -} -$difficulty .= $gs->getDifficulty($levelInfo["starDifficulty"],$levelInfo["starAuto"],$levelInfo["starDemon"]); -$difficulty .= " " . $levelInfo["starStars"] ."* "; -if($levelInfo["starEpic"] != 0){ - $difficulty .= "Epic "; -}else if($levelInfo["starFeatured"] != 0){ - $difficulty .= "Featured "; -} -//getting length -$length = $gs->getLength($levelInfo["levelLength"]); -//times -$uploadDate = date("d-m-Y G-i", $levelInfo["uploadDate"]); -$updateDate = date("d-m-Y G-i", $levelInfo["updateDate"]); -//getting original level -if($levelInfo["original"] != 0){ - $original .= "\r\n**Original:** " . $levelInfo["original"] .""; -} -if($levelInfo["originalReup"] != 0){ - $original .= "\r\n**Reupload Original:** ".$levelInfo["originalReup"] . ""; -} -//whorated -$query = $db->prepare("SELECT * FROM modactions WHERE value3 = :lvid AND type = '1'"); -$query->execute([':lvid' => $levelInfo["levelID"]]); -$actionlist = $query->fetchAll(); -if($query->rowCount() == 0){ - $whorated = ""; -} -$x = 0; -foreach($actionlist as &$action){ - $query = $db->prepare("SELECT * FROM accounts WHERE accountID = :id"); - $query->execute([':id' => $action["account"]]); - $result2 = $query->fetchAll(); - $result2 = $result2[0]; - if($x != 0){ - $whorated .= " and "; - }else{ - $whorated = "\r\n**Rated by: **"; - } - $whorated .= $result2["userName"].""; - $x++; -} -//coins -$coins = $levelInfo["coins"]; -if($levelInfo["starCoins"] != 0){ - $coins .= " Verified"; -}else{ - $coins .= " Unverified"; -} -//gameVersion -$gameVersion = $gs->getGameVersion($levelInfo["gameVersion"]); -//outputting everything -echo "**NAME:** ".$levelInfo["levelName"]." -**ID:** ".$levelInfo["levelID"]." -**Author:** ".$creator." -**Song:** ".$song." -**Difficulty:** ".$difficulty." -**Coins:** ".$coins." -**Length:** ".$length." -**Upload Time:** ".$uploadDate." -**Update Time:** ".$updateDate." $original $whorated -**Objects:** ".$levelInfo["objects"]." -**Level Version:** ".$levelInfo["levelVersion"]." -**Game Version:** ".$gameVersion." -**Downloads:** ".$levelInfo["downloads"]." -**Likes:** ".$levelInfo["likes"]; -?> \ No newline at end of file diff --git a/tools/bot/modActionsBot.php b/tools/bot/modActionsBot.php deleted file mode 100644 index 3cee3e3de..000000000 --- a/tools/bot/modActionsBot.php +++ /dev/null @@ -1,40 +0,0 @@ -Mod actions: -`| Name | Actions count | Levels count | Last time online | Linked? |` -`|-----------------|---------------|--------------|------------------|---------|` -prepare("SELECT roleID FROM roles WHERE toolModactions = 1 ORDER BY priority DESC"); -$query->execute(); -$result = $query->fetchAll(); -$accountlist = array(); -foreach($result as &$role){ - $query = $db->prepare("SELECT accountID FROM roleassign WHERE roleID = :roleID"); - $query->execute([':roleID' => $role["roleID"]]); - $accounts = $query->fetchAll(); - foreach($accounts as &$user){ - $accountlist[] = $user["accountID"]; - } -} -$accountlist = implode(",", $accountlist); -$query = $db->prepare("SELECT accountID,userName,discordID FROM accounts WHERE accountID IN($accountlist)"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$mod){ - if($mod["discordID"] == 0){ - $link = "No"; - }else{ - $link = "Yes"; - } - $query = $db->prepare("SELECT lastPlayed FROM users WHERE extID = :id"); - $query->execute([':id' => $mod["accountID"]]); - $time = date("d/m/Y H:i", $query->fetchColumn()); - $query = $db->prepare("SELECT count(*) FROM modactions WHERE account = :id"); - $query->execute([':id' => $mod["accountID"]]); - $actionscount = $query->fetchColumn(); - $query = $db->prepare("SELECT count(*) FROM modactions WHERE account = :id AND type = '1'"); - $query->execute([':id' => $mod["accountID"]]); - $lvlcount = $query->fetchColumn(); - echo "`| ".str_pad($mod["userName"], 15, " ", STR_PAD_LEFT)." | ".str_pad($actionscount, 13, " ", STR_PAD_LEFT)." | ".str_pad($lvlcount, 12, " ", STR_PAD_LEFT)." | ".$time." | ". str_pad($link, 7, " ", STR_PAD_LEFT)." |`\r\n"; -} -?> diff --git a/tools/bot/playerStatsBot.php b/tools/bot/playerStatsBot.php deleted file mode 100644 index ea3510e0e..000000000 --- a/tools/bot/playerStatsBot.php +++ /dev/null @@ -1,85 +0,0 @@ -prepare($query); -$query->execute([':userName' => $playername]); -if($query->rowCount() == 0){ - $query = "SELECT * FROM users WHERE userName LIKE CONCAT(:str, '%') ORDER BY stars DESC LIMIT 1"; - $query = $db->prepare($query); - $query->execute([':str' => $playername]); - if($query->rowCount() == 0){ - exit("No account with this or a similiar name has been found"); - } -} - $result = $query->fetchAll(); - $user = $result[0]; - //placeholders - $creatorpoints = $user["creatorPoints"]; - // GET POSITION - $e = "SET @rownum := 0;"; - $query = $db->prepare($e); - $query->execute(); - $extid = $user["extID"]; - $f = "SELECT rank, stars FROM ( - SELECT @rownum := @rownum + 1 AS rank, stars, extID, isBanned - FROM users WHERE isBanned = '0' AND gameVersion > 19 ORDER BY stars DESC - ) as result WHERE extID=:extid"; - $query = $db->prepare($f); - $query->execute([':extid' => $extid]); - $rank = $query->fetchColumn(); - $e = "SET @rownum := 0;"; - $query = $db->prepare($e); - $query->execute(); - $f = "SELECT rank, creatorPoints FROM ( - SELECT @rownum := @rownum + 1 AS rank, creatorPoints, extID, isCreatorBanned - FROM users WHERE isCreatorBanned = '0' ORDER BY creatorPoints DESC - ) as result WHERE extID=:extid"; - $query = $db->prepare($f); - $query->execute([':extid' => $extid]); - $crearank = $query->fetchColumn(); - $query = "SELECT * FROM accounts WHERE accountID = :extID"; - $query = $db->prepare($query); - $query->execute([':extID' => $extid]); - $accinfo = $query->fetch(); - $discordID = 0; - echo "**Name:** ".$user["userName"]. - "\r\n**User ID:** ".$user["userID"]; - if(is_numeric($extid)){ - echo "\r\n**Account ID:** " . $extid; - $query = $db->prepare("SELECT discordID FROM accounts WHERE accountID = :extid"); - $query->execute([':extid' => $extid]); - $discordID = $query->fetchColumn(); - } - echo "\r\n**------------------------------------**". - "\r\n**Stars:** ".$user["stars"]. - "\r\n**Coins:** ".$user["coins"]. - "\r\n**User Coins:** ".$user["userCoins"]. - "\r\n**Diamonds:** ".$user["diamonds"]. - "\r\n**Demons: **".$user["demons"]. - "\r\n**Orbs: **".$user["orbs"]. - "\r\n**Completed Levels: **".$user["completedLvls"]. - "\r\n**Creator points:** ".$creatorpoints. - "\r\n**------------------------------------**". - "\r\n**Leaderboards rank:** ".$rank. - "\r\n**Creator leaderboards rank:** ".$crearank. - "\r\n**------------------------------------**"; - if($accinfo["youtubeurl"] != ""){ - echo "\r\n**YouTube:** http://youtube.com/channel/".$accinfo["youtubeurl"]; - } - if($accinfo["twitter"] != ""){ - echo "\r\n**Twitter:** http://twitter.com/".$accinfo["twitter"]; - } - if($accinfo["twitch"] != ""){ - echo "\r\n**Twitch:** http://twitch.tv/".$accinfo["twitch"].""; - } - if($discordID == 0){ - echo "\r\n**Discord:** None"; - }else{ - echo "\r\n**Discord:** " . $gs->getDiscordAcc($discordID) . " ($discordID)"; - } -?> \ No newline at end of file diff --git a/tools/bot/songAddBot.php b/tools/bot/songAddBot.php deleted file mode 100644 index d46f3d26f..000000000 --- a/tools/bot/songAddBot.php +++ /dev/null @@ -1,33 +0,0 @@ -prepare("SELECT count(*) FROM songs WHERE download = :download"); -$query->execute([':download' => $link]); -$count = $query->fetchColumn(); -if($count != 0){ - echo "This song already exists in our database."; -}else{ - $query = $db->prepare("INSERT INTO songs (name, authorID, authorName, size, download, hash) - VALUES (:name, '9', :author, :size, :download, :hash)"); - $query->execute([':name' => $name, ':download' => $link, ':author' => $author, ':size' => $size, ':hash' => $hash]); - echo $db->lastInsertId(); -} -?> \ No newline at end of file diff --git a/tools/bot/songListBot.php b/tools/bot/songListBot.php deleted file mode 100644 index b21e6dce9..000000000 --- a/tools/bot/songListBot.php +++ /dev/null @@ -1,21 +0,0 @@ - 0){ - $page = $_GET["page"] - 1; -}else{ - $page = 0; -} -$humanpage = $page +1; -$page = $page*20; -echo "***SHOWING PAGE $humanpage***\r\n"; -include "../../incl/lib/connection.php"; -$query = $db->prepare("SELECT ID,name FROM songs WHERE ID >= 5000000 ORDER BY ID DESC LIMIT $page , 20"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$song){ - $name = str_replace("*", "", $song["name"]); - $name = str_replace("_", " ", $song["name"]); - echo "**".$song["ID"]." : **".$name."\r\n"; -} -?> -***USE !songlist TO SEE MORE SONGS*** \ No newline at end of file diff --git a/tools/bot/songSearchBot.php b/tools/bot/songSearchBot.php deleted file mode 100644 index 723e6a6fd..000000000 --- a/tools/bot/songSearchBot.php +++ /dev/null @@ -1,25 +0,0 @@ -prepare("(SELECT ID,name FROM songs WHERE ID = :str) UNION (SELECT ID,name FROM songs WHERE name LIKE CONCAT('%', :str, '%')) ORDER BY ID DESC"); //getting song info -$query->execute([':str' => $str]); -$result = $query->fetchAll(); -foreach($result as &$song){ - $name = str_replace("*", "", $song["name"]); - $name = str_replace("_", " ", $song["name"]); - $string .= "**".$song["ID"]." : **".$name."\r\n"; -} -if(strlen($string) > 1900){ - echo "Too many results, please specify your search query."; -}else{ - if($string == ""){ - echo "Nothing found."; - } - echo $string; -} -?> \ No newline at end of file diff --git a/tools/bot/userLevelSearchBot.php b/tools/bot/userLevelSearchBot.php deleted file mode 100644 index 56a32c1d1..000000000 --- a/tools/bot/userLevelSearchBot.php +++ /dev/null @@ -1,38 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName = :str OR userID = :str"); -$query->execute([':str' => $str]); -//checking if exists -if($query->rowCount() == 0){ - exit("The user you are searching for doesn't exist"); -} -$accountID = $query->fetchColumn(); -$query = $db->prepare("SELECT userID FROM users WHERE extID = :extID"); -$query->execute([':extID' => $accountID]); -$userID = $query->fetchColumn(); -$query = $db->prepare("SELECT levelName,levelID FROM levels WHERE userID = :userID"); //getting level info -$query->execute([':userID' => $userID]); -//checking if exists -if($query->rowCount() == 0){ - exit("This user doesn't have any levels."); -} -echo "```"; -$levels = $query->fetchAll(); -echo str_pad("Level ID", 16, " ", STR_PAD_LEFT) . " | Level Name\r\n--------------------------------------\r\n"; -foreach($levels as $level){ - $levelID = str_pad($level["levelID"], 16, " ", STR_PAD_LEFT); - $levelName = $level["levelName"]; - echo "$levelID | $levelName\r\n"; -} -echo "```"; -?> diff --git a/tools/bot/whoRatedBot.php b/tools/bot/whoRatedBot.php deleted file mode 100644 index 930c61b5e..000000000 --- a/tools/bot/whoRatedBot.php +++ /dev/null @@ -1,19 +0,0 @@ -prepare("SELECT account FROM modactions WHERE value3 = :lvid AND type = '1'"); -$query->execute([':lvid' => $_GET["level"]]); -$result = $query->fetchAll(); -if(!is_numeric($_GET["level"])){ - exit("Please supply a valid level ID."); -} -if($query->rowCount() == 0){ - echo "Nobody did!"; -} -foreach($result as &$action){ - $query = $db->prepare("SELECT userName FROM accounts WHERE accountID = :id"); - $query->execute([':id' => $action["account"]]); - $userName = $query->fetchColumn(); - echo $userName." did!\r\n"; -} -?> diff --git a/tools/cleanup/deleteUnused.php b/tools/cleanup/deleteUnused.php deleted file mode 100644 index ad9fca99e..000000000 --- a/tools/cleanup/deleteUnused.php +++ /dev/null @@ -1,25 +0,0 @@ -
-prepare("SELECT userID, userName, extID, lastPlayed FROM users WHERE NOT extID REGEXP '^[0-9]+$' AND lastPlayed < :time"); -$query->execute([':time' => time() - 604800]); -$users = $query->fetchAll(); -foreach($users as $user){ - $query = $db->prepare("SELECT count(*) FROM levels WHERE userID = :userID"); - $query->execute([':userID' => $user["userID"]]); - $count = $query->fetchColumn(); - $query = $db->prepare("SELECT count(*) FROM comments WHERE userID = :userID"); - $query->execute([':userID' => $user["userID"]]); - $count += $query->fetchColumn(); - if($count == 0){ - $query = $db->prepare("DELETE FROM users WHERE userID = :userID"); - $query->execute([':userID' => $user["userID"]]); - echo "Deleted ".htmlspecialchars($user["userName"],ENT_QUOTES)." - ".$user["userID"]." - ".$user["extID"]." - ".date("d-m-Y G-i", $user["lastPlayed"])."
"; - ob_flush(); - flush(); - $x++; - } -} -echo "
".$x; -?> \ No newline at end of file diff --git a/tools/cron/autoban.php b/tools/cron/autoban.php deleted file mode 100644 index 4693a03ce..000000000 --- a/tools/cron/autoban.php +++ /dev/null @@ -1,68 +0,0 @@ -
-"; -ob_flush(); -flush(); -//note: this needs a better algorithm -$query = $db->prepare("SELECT 10+IFNULL(FLOOR(coins.coins*1.25)+IFNULL(coins1.coins, 0),0) as coins, 3+IFNULL(FLOOR(levels.demons*1.0625)+IFNULL(demons.demons,0),0) as demons, 212+FLOOR((IFNULL(levels.stars,0)+IFNULL(gauntlets.stars,0)+IFNULL(mappacks.stars,0))+IFNULL(stars.stars,0)*1.25) as stars, 25+IFNULL(moons.moons,0) as moons FROM - (SELECT SUM(coins) as coins FROM levels WHERE starCoins <> 0) coins - JOIN - (SELECT SUM(starDemon) as demons, SUM(starStars) as stars FROM levels) levels - JOIN - (SELECT SUM(starStars) as stars FROM dailyfeatures - INNER JOIN levels on levels.levelID = dailyfeatures.levelID) stars - JOIN - (SELECT SUM(starCoins) as coins FROM dailyfeatures - INNER JOIN levels on levels.levelID = dailyfeatures.levelID) coins1 - JOIN - (SELECT SUM(starDemon) as demons FROM dailyfeatures - INNER JOIN levels on levels.levelID = dailyfeatures.levelID) demons - JOIN - ( - SELECT (level1.stars + level2.stars + level3.stars + level4.stars + level5.stars) as stars FROM - (SELECT SUM(starStars) as stars FROM gauntlets - INNER JOIN levels on levels.levelID = gauntlets.level1) level1 - JOIN - (SELECT SUM(starStars) as stars FROM gauntlets - INNER JOIN levels on levels.levelID = gauntlets.level2) level2 - JOIN - (SELECT SUM(starStars) as stars FROM gauntlets - INNER JOIN levels on levels.levelID = gauntlets.level3) level3 - JOIN - (SELECT SUM(starStars) as stars FROM gauntlets - INNER JOIN levels on levels.levelID = gauntlets.level4) level4 - JOIN - (SELECT SUM(starStars) as stars FROM gauntlets - INNER JOIN levels on levels.levelID = gauntlets.level5) level5 - ) gauntlets - JOIN - (SELECT SUM(stars) as stars FROM mappacks) mappacks - JOIN - (SELECT SUM(starStars) as moons FROM levels WHERE levelLength = 5) moons"); -$query->execute(); -$levelstuff = $query->fetch(); -$stars = $levelstuff['stars']; $coins = $levelstuff['coins']; $demons = $levelstuff['demons']; $moons = $levelstuff['moons']; -$query = $db->prepare("UPDATE users SET isBanned = '1' WHERE stars > :stars OR demons > :demons OR userCoins > :coins OR moons > :moons OR stars < 0 OR demons < 0 OR coins < 0 OR userCoins < 0 OR diamonds < 0 OR moons < 0"); -$query->execute([':stars' => $stars, ':demons' => $demons, ':coins' => $coins, ':moons' => $moons]); -$query = $db->prepare("SELECT userID, userName FROM users WHERE stars > :stars OR demons > :demons OR userCoins > :coins OR moons > :moons OR stars < 0 OR demons < 0 OR coins < 0 OR userCoins < 0 OR diamonds < 0 OR moons < 0"); -$query->execute([':stars' => $stars, ':demons' => $demons, ':coins' => $coins, ':moons' => $moons]); -$result = $query->fetchAll(); -foreach($result as $user){ - echo "Banned ".htmlspecialchars($user["userName"],ENT_QUOTES)." - ".$user["userID"]."
"; -} -//banips -$query = $db->prepare("SELECT IP FROM bannedips"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$ip){ - $query = $db->prepare("UPDATE users SET isBanned = '1' WHERE IP LIKE CONCAT(:ip, '%')"); - $query->execute([':ip' => $ip["IP"]]); -} -echo "
Autoban finished"; -ob_flush(); -flush(); -//done -//echo "
Banned everyone with over $stars stars and over $coins user coins and over $demons demons and over $moons moons!
done"; -?> -
diff --git a/tools/cron/cron.php b/tools/cron/cron.php deleted file mode 100644 index 772a924cd..000000000 --- a/tools/cron/cron.php +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/tools/cron/fixcps.php b/tools/cron/fixcps.php deleted file mode 100644 index 2fdecc825..000000000 --- a/tools/cron/fixcps.php +++ /dev/null @@ -1,123 +0,0 @@ -"; -ob_flush(); -flush(); -if(file_exists("../logs/fixcpslog.txt")){ - $cptime = file_get_contents("../logs/fixcpslog.txt"); - $newtime = time() - 30; - if($cptime > $newtime){ - $remaintime = time() - $cptime; - $remaintime = 30 - $remaintime; - $remainmins = floor($remaintime / 60); - $remainsecs = $remainmins * 60; - $remainsecs = $remaintime - $remainsecs; - exit("Please wait $remainmins minutes and $remainsecs seconds before running ". basename($_SERVER['SCRIPT_NAME'])." again"); - } -} -file_put_contents("../logs/fixcpslog.txt",time()); -if(function_exists("set_time_limit")) set_time_limit(0); -$cplog = ""; -$people = array(); -include "../../incl/lib/connection.php"; -//getting users -$query = $db->prepare("UPDATE users - LEFT JOIN - ( - SELECT usersTable.userID, (IFNULL(starredTable.starred, 0) + IFNULL(featuredTable.featured, 0) + (IFNULL(epicTable.epic,0)*2)) as CP FROM ( - SELECT userID FROM users - ) AS usersTable - LEFT JOIN - ( - SELECT count(*) as starred, userID FROM levels WHERE starStars != 0 AND isCPShared = 0 GROUP BY(userID) - ) AS starredTable ON usersTable.userID = starredTable.userID - LEFT JOIN - ( - SELECT count(*) as featured, userID FROM levels WHERE starFeatured != 0 AND isCPShared = 0 GROUP BY(userID) - ) AS featuredTable ON usersTable.userID = featuredTable.userID - LEFT JOIN - ( - SELECT count(*)+(starEpic-1) as epic, userID FROM levels WHERE starEpic != 0 AND isCPShared = 0 GROUP BY(userID) - ) AS epicTable ON usersTable.userID = epicTable.userID - ) calculated - ON users.userID = calculated.userID - SET users.creatorPoints = IFNULL(calculated.CP, 0)"); -$query->execute(); -echo "Calculated base CP
"; -/* - CP SHARING -*/ -$query = $db->prepare("SELECT levelID, userID, starStars, starFeatured, starEpic FROM levels WHERE isCPShared = 1"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as $level){ - $deservedcp = 0; - if($level["starStars"] != 0){ - $deservedcp++; - } - if($level["starFeatured"] != 0){ - $deservedcp++; - } - if($level["starEpic"] != 0){ - $deservedcp += $level["starEpic"]; // Epic - 1, Legendary - 2, Mythic - 3 - } - $query = $db->prepare("SELECT userID FROM cpshares WHERE levelID = :levelID"); - $query->execute([':levelID' => $level["levelID"]]); - $sharecount = $query->rowCount() + 1; - $addcp = $deservedcp / $sharecount; - $shares = $query->fetchAll(); - foreach($shares as &$share){ - $people[$share["userID"]] += $addcp; - } - $people[$level["userID"]] += $addcp; -} -/* - NOW to update GAUNTLETS CP -*/ -$query = $db->prepare("SELECT level1,level2,level3,level4,level5 FROM gauntlets"); -$query->execute(); -$result = $query->fetchAll(); -//getting gauntlets -foreach($result as $gauntlet){ - //getting lvls - for($x = 1; $x < 6; $x++){ - $query = $db->prepare("SELECT userID, levelID FROM levels WHERE levelID = :levelID"); - $query->execute([':levelID' => $gauntlet["level".$x]]); - $result = $query->fetch(); - //getting users - if($result["userID"] != ""){ - $cplog .= $result["userID"] . " - +1\r\n"; - $people[$result["userID"]] += 1; - } - } -} -/* - NOW to update DAILY CP -*/ -$query = $db->prepare("SELECT levelID FROM dailyfeatures WHERE timestamp < :time"); -$query->execute([':time' => time()]); -$result = $query->fetchAll(); -//getting gauntlets -foreach($result as $daily){ - //getting lvls - $query = $db->prepare("SELECT userID, levelID FROM levels WHERE levelID = :levelID"); - $query->execute([':levelID' => $daily["levelID"]]); - $result = $query->fetch(); - //getting users - if($result["userID"] != ""){ - $people[$result["userID"]] += 1; - $cplog .= $result["userID"] . " - +1\r\n"; - } -} -/* - DONE -*/ -foreach($people as $user => $cp){ - echo "$user now has $cp creator points...
"; - $query4 = $db->prepare("UPDATE users SET creatorPoints = (creatorpoints + :creatorpoints) WHERE userID=:userID"); - $query4->execute([':userID' => $user, ':creatorpoints' => $cp]); -} -echo "
done"; -file_put_contents("../logs/cplog.txt",$cplog); -?> diff --git a/tools/cron/fixnames.php b/tools/cron/fixnames.php deleted file mode 100644 index f9b9372d8..000000000 --- a/tools/cron/fixnames.php +++ /dev/null @@ -1,24 +0,0 @@ -"; -ob_flush(); -flush(); -if(function_exists("set_time_limit")) set_time_limit(0); -include "../../incl/lib/connection.php"; -$query = $db->prepare("UPDATE users - INNER JOIN accounts ON accounts.accountID = users.extID - SET users.userName = accounts.userName - WHERE users.extID REGEXP '^-?[0-9]+$' - AND LENGTH(accounts.userName) <= 69"); -$query->execute(); -$query = $db->prepare("UPDATE users - INNER JOIN accounts ON accounts.accountID = users.extID - SET users.userName = 'Invalid Username' - WHERE users.extID REGEXP '^-?[0-9]+$' - AND LENGTH(accounts.userName) > 69"); -$query->execute(); -echo "Done
"; \ No newline at end of file diff --git a/tools/cron/friendsLeaderboard.php b/tools/cron/friendsLeaderboard.php deleted file mode 100644 index bd24d0a06..000000000 --- a/tools/cron/friendsLeaderboard.php +++ /dev/null @@ -1,34 +0,0 @@ - $newtime){ - $remaintime = time() - $cptime; - $remaintime = 30 - $remaintime; - $remainmins = floor($remaintime / 60); - $remainsecs = $remainmins * 60; - $remainsecs = $remaintime - $remainsecs; - exit("Please wait $remainmins minutes and $remainsecs seconds before running ". basename($_SERVER['SCRIPT_NAME'])." again"); - } -} -file_put_contents("../logs/fixfrndlog.txt",time()); -if(function_exists("set_time_limit")) set_time_limit(0); -include "../../incl/lib/connection.php"; -echo "Calculating the amount of friends everyone has"; -$query = $db->prepare("UPDATE accounts - LEFT JOIN - ( - SELECT a.person, (IFNULL(a.friends, 0) + IFNULL(b.friends, 0)) AS friends FROM ( - SELECT count(*) as friends, person1 AS person FROM friendships GROUP BY(person1) - ) AS a - JOIN - ( - SELECT count(*) as friends, person2 AS person FROM friendships GROUP BY(person2) - ) AS b ON a.person = b.person - ) calculated - ON accounts.accountID = calculated.person - SET accounts.friendsCount = IFNULL(calculated.friends, 0)"); -$query->execute(); -echo "
"; -?> diff --git a/tools/cron/removeBlankLevels.php b/tools/cron/removeBlankLevels.php deleted file mode 100644 index 2e65bd036..000000000 --- a/tools/cron/removeBlankLevels.php +++ /dev/null @@ -1,55 +0,0 @@ -prepare("DELETE FROM users WHERE extID = ''"); -$query->execute(); -$query = $db->prepare("DELETE FROM songs WHERE download = ''"); -$query->execute(); -echo "Deleted invalid users and songs.
"; -ob_flush(); -flush(); -$query = $db->prepare("UPDATE levels SET password = 0 WHERE password = 2"); -$query->execute(); -echo "Fixed reuploaded levels with invalid passwords.
"; -ob_flush(); -flush(); -$query = $db->prepare("DELETE FROM songs WHERE download = '10' OR download LIKE 'file:%'"); -$query->execute(); -echo "Removed songs with nonsensical URLs.
"; -/*$query = $db->prepare("SELECT accountID, userName, registerDate FROM accounts"); -$query->execute(); -$result = $query->fetchAll(); -echo "Deleting unused accounts
"; -ob_flush(); -flush(); -foreach($result as &$account){ - $query = $db->prepare("SELECT count(*) FROM users WHERE extID = :accountID"); - $query->execute([':accountID' => $account["accountID"]]); - if($query->fetchColumn() == 0){ - $time = time() - 2592000; - if($account["registerDate"] < $time){ - echo "Deleted " . htmlspecialchars($account["userName"],ENT_QUOTES) . "
"; - $query = $db->prepare("DELETE FROM accounts WHERE accountID = :accountID"); - $query->execute([':accountID' => $account["accountID"]]); - ob_flush(); - flush(); - } - } -}*/ -/*$query = $db->prepare("show tables"); -$query->execute(); -$tables = $query->fetchAll(); -echo "Optimizing tables.
"; -ob_flush(); -flush(); -foreach($tables as &$table){ - $table = $table[0]; - $query = $db->prepare("OPTIMIZE TABLE $table"); - $query->execute(); - echo "Optimized $table
"; - ob_flush(); - flush(); -}*/ -echo "
"; -ob_flush(); -flush(); -?> \ No newline at end of file diff --git a/tools/cron/songsCount.php b/tools/cron/songsCount.php deleted file mode 100644 index 1cfa6e066..000000000 --- a/tools/cron/songsCount.php +++ /dev/null @@ -1,17 +0,0 @@ -prepare("UPDATE songs - LEFT JOIN - ( - SELECT count(*) AS levelsCount, songID FROM levels GROUP BY songID - ) calculated - ON calculated.songID = songs.ID - SET songs.levelsCount = IFNULL(calculated.levelsCount, 0)"); -$query->execute(); -echo "
"; -?> diff --git a/tools/index.php b/tools/index.php deleted file mode 100644 index b793e7a7b..000000000 --- a/tools/index.php +++ /dev/null @@ -1,21 +0,0 @@ -Check out the dashboard beta here -$file"; - } - } - return $dirstring; -} -echo '

Account management tools:

    '; -echo listdir("account"); -echo'

Upload related tools:

    '; -echo listdir("."); -echo "

The cron job (fixing CPs, autoban, etc.)

Stats related tools

    "; -echo listdir("stats"); -?> \ No newline at end of file diff --git a/tools/leaderboardsBan.php b/tools/leaderboardsBan.php deleted file mode 100644 index 775d87236..000000000 --- a/tools/leaderboardsBan.php +++ /dev/null @@ -1,42 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - if($gs->checkPermission($accountID, "toolLeaderboardsban")){ - if(!is_numeric($userID)){ - exit("Invalid userID"); - } - $query = $db->prepare("UPDATE users SET isBanned = 1 WHERE userID = :id"); - $query->execute([':id' => $userID]); - if($query->rowCount() != 0){ - echo "Banned succesfully."; - }else{ - echo "Ban failed."; - } - $query = $db->prepare("INSERT INTO modactions (type, value, value2, timestamp, account) - VALUES ('15',:userID, '1', :timestamp,:account)"); - $query->execute([':userID' => $userID, ':timestamp' => time(), ':account' => $accountID]); - }else{ - exit("You do not have the permission to do this action. Try again"); - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
    Your Username: -
    Your Password: -
    Target UserID: -
    '; -} -?> \ No newline at end of file diff --git a/tools/leaderboardsUnban.php b/tools/leaderboardsUnban.php deleted file mode 100644 index 0d12ef153..000000000 --- a/tools/leaderboardsUnban.php +++ /dev/null @@ -1,42 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - if($gs->checkPermission($accountID, "toolLeaderboardsban")){ - if(!is_numeric($userID)){ - exit("Invalid userID"); - } - $query = $db->prepare("UPDATE users SET isBanned = 0 WHERE userID = :id"); - $query->execute([':id' => $userID]); - if($query->rowCount() != 0){ - echo "Unbanned succesfully."; - }else{ - echo "Unban failed."; - } - $query = $db->prepare("INSERT INTO modactions (type, value, value2, timestamp, account) - VALUES ('15',:userID, '0', :timestamp,:account)"); - $query->execute([':userID' => $userID, ':timestamp' => time(), ':account' => $accountID]); - }else{ - exit("You do not have the permission to do this action. Try again"); - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
    Your Username: -
    Your Password: -
    Target UserID: -
    '; -} -?> \ No newline at end of file diff --git a/tools/levelReupload.php b/tools/levelReupload.php deleted file mode 100644 index da8aa6dfe..000000000 --- a/tools/levelReupload.php +++ /dev/null @@ -1,144 +0,0 @@ - - -LEVEL REUPLOAD - - - '22', 'binaryVersion' => '37', 'gdw' => '0', 'levelID' => $levelID, 'secret' => 'Wmfd2893gb7', 'inc' => '0', 'extras' => '0']; - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post); - curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - $result = curl_exec($ch); - curl_close($ch); - if($result == "" OR $result == "-1" OR $result == "No no no"){ - if($result==""){ - echo "An error has occured while connecting to the server."; - }else if($result=="-1"){ - echo "This level doesn't exist."; - }else{ - echo "RobTop doesn't like you or something..."; - } - echo "
    Error code: $result"; - }else{ - $level = explode('#', $result)[0]; - $resultarray = explode(':', $level); - $levelarray = array(); - $x = 1; - foreach($resultarray as &$value){ - if ($x % 2 == 0) { - $levelarray["a$arname"] = $value; - }else{ - $arname = $value; - } - $x++; - } - //echo $result; - if($_POST["debug"] == 1){ - echo "
    ".$result . "
    "; - var_dump($levelarray); - } - if($levelarray["a4"] == ""){ - echo "An error has occured.
    Error code: ".htmlspecialchars($result,ENT_QUOTES); - } - $uploadDate = time(); - //old levelString - $levelString = chkarray($levelarray["a4"]); - $gameVersion = chkarray($levelarray["a13"]); - if(substr($levelString,0,2) == 'eJ'){ - $levelString = str_replace("_","/",$levelString); - $levelString = str_replace("-","+",$levelString); - $levelString = gzuncompress(base64_decode($levelString)); - if($gameVersion > 18){ - $gameVersion = 18; - } - } - //check if exists - $query = $db->prepare("SELECT count(*) FROM levels WHERE originalReup = :lvl OR original = :lvl"); - $query->execute([':lvl' => $levelarray["a1"]]); - if($query->fetchColumn() == 0){ - $parsedurl = parse_url($url); - if($parsedurl["host"] == $_SERVER['SERVER_NAME']){ - exit("You're attempting to reupload from the target server."); - } - $hostname = $gs->getIP(); - //values - $twoPlayer = chkarray($levelarray["a31"]); - $songID = chkarray($levelarray["a35"]); - $coins = chkarray($levelarray["a37"]); - $reqstar = chkarray($levelarray["a39"]); - $extraString = chkarray($levelarray["a36"], ""); - $starStars = chkarray($levelarray["a18"]); - $isLDM = chkarray($levelarray["a40"]); - $password = chkarray($levelarray["a27"]); - if($password != "0"){ - $password = XORCipher::cipher(base64_decode($password),26364); - } - $starCoins = 0; - $starDiff = 0; - $starDemon = 0; - $starAuto = 0; - if($parsedurl["host"] == "www.boomlings.com"){ - if($starStars != 0){ - $starCoins = chkarray($levelarray["a38"]); - $starDiff = chkarray($levelarray["a9"]); - $starDemon = chkarray($levelarray["a17"]); - $starAuto = chkarray($levelarray["a25"]); - } - }else{ - $starStars = 0; - } - $targetUserID = chkarray($levelarray["a6"]); - //linkacc - $query = $db->prepare("SELECT accountID, userID FROM links WHERE targetUserID=:target AND server=:url"); - $query->execute([':target' => $targetUserID, ':url' => $parsedurl["host"]]); - if($query->rowCount() == 0){ - $userID = $reupUID; - $extID = $reupAID; - }else{ - $userInfo = $query->fetchAll()[0]; - $userID = $userInfo["userID"]; - $extID = $userInfo["accountID"]; - } - //query - $query = $db->prepare("INSERT INTO levels (levelName, gameVersion, binaryVersion, userName, levelDesc, levelVersion, levelLength, audioTrack, auto, password, original, twoPlayer, songID, objects, coins, requestedStars, extraString, levelString, levelInfo, secret, uploadDate, updateDate, originalReup, userID, extID, unlisted, hostname, starStars, starCoins, starDifficulty, starDemon, starAuto, isLDM, songIDs, sfxIDs, ts) - VALUES (:name ,:gameVersion, '27', 'Reupload', :desc, :version, :length, :audiotrack, '0', :password, :originalReup, :twoPlayer, :songID, '0', :coins, :reqstar, :extraString, :levelString, '', '', '$uploadDate', '$uploadDate', :originalReup, :userID, :extID, '0', :hostname, :starStars, :starCoins, :starDifficulty, :starDemon, :starAuto, :isLDM, :songIDs, :sfxIDs, :ts)"); - $query->execute([':password' => $password, ':starDemon' => $starDemon, ':starAuto' => $starAuto, ':gameVersion' => $gameVersion, ':name' => $levelarray["a2"], ':desc' => $levelarray["a3"], ':version' => $levelarray["a5"], ':length' => $levelarray["a15"], ':audiotrack' => $levelarray["a12"], ':twoPlayer' => $twoPlayer, ':songID' => $songID, ':coins' => $coins, ':reqstar' => $reqstar, ':extraString' => $extraString, ':levelString' => "", ':originalReup' => $levelarray["a1"], ':hostname' => $hostname, ':starStars' => $starStars, ':starCoins' => $starCoins, ':starDifficulty' => $starDiff, ':userID' => $userID, ':extID' => $extID, ':isLDM' => $isLDM, ':songIDs' => $levelarray["a52"], ':sfxIDs' => $levelarray["a53"], ':ts' => $levelarray["a57"]]); - $levelID = $db->lastInsertId(); - file_put_contents("../data/levels/$levelID",$levelString); - echo "Level reuploaded, ID: $levelID


    "; - }else{ - echo "This level has been already reuploaded"; - } - } -}else{ - echo '

    LINKING YOUR ACCOUNT USING linkAcc.php RECOMMENDED

    -
    ID:
    -
    - Advanced options - URL:
    - Debug Mode (0=off, 1=on):
    -
    -
    '; -} -?> - - diff --git a/tools/levelToGD.php b/tools/levelToGD.php deleted file mode 100644 index deb3bb526..000000000 --- a/tools/levelToGD.php +++ /dev/null @@ -1,132 +0,0 @@ - - -LEVEL REUPLOAD TO NORMAL GD - - -prepare("SELECT * FROM levels WHERE levelID = :level"); - $query->execute([':level' => $levelID]); - $levelInfo = $query->fetch(); - $userID = $levelInfo["userID"]; - $query = $db->prepare("SELECT accountID FROM accounts WHERE userName = :user"); - $query->execute([':user' => $userhere]); - $accountID = $query->fetchColumn(); - $query = $db->prepare("SELECT userID FROM users WHERE extID = :ext"); - $query->execute([':ext' => $accountID]); - if($query->fetchColumn() != $userID){ //verifying if lvl owned - exit("This level doesn't belong to the account you're trying to reupload from"); - } - $udid = "S" . mt_rand(111111111,999999999) . mt_rand(111111111,999999999) . mt_rand(111111111,999999999) . mt_rand(111111111,999999999) . mt_rand(1,9); //getting accountid - $sid = mt_rand(111111111,999999999) . mt_rand(11111111,99999999); - //echo $udid; - $post = ['userName' => $usertarg, 'udid' => $udid, 'password' => $passtarg, 'sID' => $sid, 'secret' => 'Wmfv3899gc9']; - $ch = curl_init($server . "/accounts/loginGJAccount.php"); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post); - curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - $result = curl_exec($ch); - curl_close($ch); - if($result == "" OR $result == "-1" OR $result == "No no no"){ - if($result==""){ - echo "An error has occured while connecting to the login server."; - }else if($result=="-1"){ - echo "Login to the target server failed."; - }else{ - echo "RobTop doesn't like you or something..."; - } - exit("
    Error code: $result"); - } - if(!is_numeric($levelID)){ //checking if the level id is numeric due to possible exploits - exit("Invalid levelID"); - } - //TODO: move all file_get_contents calls like this to a separate function - $levelString = file_get_contents("../data/levels/$levelID"); - $seed2 = base64_encode(XORCipher::cipher(GenerateHash::genSeed2noXor($levelString),41274)); - $accountID = explode(",",$result)[0]; - $gjp = base64_encode(XORCipher::cipher($passtarg,37526)); - $post = ['gameVersion' => $levelInfo["gameVersion"], - 'binaryVersion' => $levelInfo["binaryVersion"], - 'gdw' => "0", - 'accountID' => $accountID, - 'gjp' => $gjp, - 'userName' => $usertarg, - 'levelID' => "0", - 'levelName' => $levelInfo["levelName"], - 'levelDesc' => $levelInfo["levelDesc"], - 'levelVersion' => $levelInfo["levelVersion"], - 'levelLength' => $levelInfo["levelLength"], - 'audioTrack' => $levelInfo["audioTrack"], - 'auto' => $levelInfo["auto"], - 'password' => $levelInfo["password"], - 'original' => "0", - 'twoPlayer' => $levelInfo["twoPlayer"], - 'songID' => $levelInfo["songID"], - 'objects' => $levelInfo["objects"], - 'coins' => $levelInfo["coins"], - 'requestedStars' => $levelInfo["requestedStars"], - 'unlisted' => "0", - 'wt' => "0", - 'wt2' => "3", - 'extraString' => $levelInfo["extraString"], - 'seed' => "v2R5VPi53f", - 'seed2' => $seed2, - 'levelString' => $levelString, - 'levelInfo' => $levelInfo["levelInfo"], - 'secret' => "Wmfd2893gb7"]; - if($_POST["debug"] == 1){ - var_dump($post); - } - $ch = curl_init($server . "/uploadGJLevel21.php"); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post); - curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - $result = curl_exec($ch); - curl_close($ch); - if($result == "" OR $result == "-1" OR $result == "No no no"){ - if($result==""){ - echo "An error has occured while connecting to the upload server."; - }else if($result=="-1"){ - echo "Reuploading level failed."; - }else{ - echo "RobTop doesn't like you or something... (upload)"; - } - exit("
    Error code: $result"); - } - echo "Level reuploaded - $result"; -}else{ - echo '
    Your password for the target server is NOT saved, it\'s used for one-time verification purposes only. -

    This server

    Username:
    - Password:
    - Level ID:
    -

    Target server

    Username:
    - Password:
    -
    - Advanced options - URL:
    - Debug Mode (0=off, 1=on):
    -
    -
    '; -} -?> - - \ No newline at end of file diff --git a/tools/linkAcc.php b/tools/linkAcc.php deleted file mode 100644 index 7c477e216..000000000 --- a/tools/linkAcc.php +++ /dev/null @@ -1,92 +0,0 @@ - - -ACCOUNT LINKING - - - $usertarg, 'udid' => $udid, 'password' => $passtarg, 'sID' => $sid, 'secret' => 'Wmfv3899gc9']; - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post); - curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - $result = curl_exec($ch); - curl_close($ch); - if($result == "" OR $result == "-1" OR $result == "No no no"){ - if($result==""){ - echo "An error has occured while connecting to the server."; - }else if($result=="-1"){ - echo "Login to the target server failed."; - }else{ - echo "RobTop doesn't like you or something..."; - } - echo "
    Error code: $result"; - }else{ - if($_POST["debug"] == 1){ - echo "
    $result
    "; - } - $parsedurl = parse_url($url); - if($parsedurl["host"] == $_SERVER['SERVER_NAME']){ - exit("You can't link 2 accounts on the same server."); - } - //getting stuff - $query = $db->prepare("SELECT accountID FROM accounts WHERE userName = :userName LIMIT 1"); - $query->execute([':userName' => $userhere]); - $accountID = $query->fetchColumn(); - $query = $db->prepare("SELECT userID FROM users WHERE extID = :extID LIMIT 1"); - $query->execute([':extID' => $accountID]); - $userID = $query->fetchColumn(); - $targetAccountID = explode(",",$result)[0]; - $targetUserID = explode(",",$result)[1]; - $query = $db->prepare("SELECT count(*) FROM links WHERE targetAccountID = :targetAccountID LIMIT 1"); - $query->execute([':targetAccountID' => $targetAccountID]); - if($query->fetchColumn() != 0){ - exit("The target account is linked to an account already."); - } - //echo $accountID; - if(!is_numeric($targetAccountID) OR !is_numeric($accountID)){ - exit("Invalid AccountID found"); - } - $server = $parsedurl["host"]; - //query - $query = $db->prepare("INSERT INTO links (accountID, targetAccountID, server, timestamp, userID, targetUserID) - VALUES (:accountID,:targetAccountID,:server,:timestamp,:userID,:targetUserID)"); - $query->execute([':accountID' => $accountID, ':targetAccountID' => $targetAccountID, ':server' => $server, ':timestamp' => time(), 'userID' => $userID, 'targetUserID' => $targetUserID]); - echo "Account linked succesfully."; - } - }else{ - echo "Invalid local username/password combination."; - } -}else{ - echo '
    Your password for the target server is NOT saved, it\'s used for one-time verification purposes only. -

    This server

    - Username:
    - Password:
    -

    Target server

    - Username:
    - Password:
    -
    - Advanced options - URL:
    - Debug Mode (0=off, 1=on):
    -
    -
    '; -} -?> - - \ No newline at end of file diff --git a/tools/logs/cronlastrun.txt b/tools/logs/cronlastrun.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/logs/fixcpslog.txt b/tools/logs/fixcpslog.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/logs/fixfrndlog.txt b/tools/logs/fixfrndlog.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/logs/fixnameslog.txt b/tools/logs/fixnameslog.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/logs/frndlog.txt b/tools/logs/frndlog.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/logs/snglog.txt b/tools/logs/snglog.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tools/packCreate.php b/tools/packCreate.php deleted file mode 100644 index 77a779a26..000000000 --- a/tools/packCreate.php +++ /dev/null @@ -1,108 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - if($gs->checkPermission($accountID, "toolPackcreate") == false){ - echo "This account doesn't have the permissions to access this tool. Try again"; - }else{ - if(!is_numeric($stars) OR !is_numeric($coins) OR $stars > 10 OR $coins > 2){ - exit("Invalid stars/coins value"); - } - if(strlen($color) != 6){ - exit("Unknown color value"); - } - $rgb = hexdec(substr($color,0,2)). - ",".hexdec(substr($color,2,2)). - ",".hexdec(substr($color,4,2)); - $lvlsarray = explode(",", $levels); - foreach($lvlsarray AS &$level){ - if(!is_numeric($level)){ - exit("$level isn't a number"); - } - $query = $db->prepare("SELECT levelName FROM levels WHERE levelID=:levelID"); - $query->execute([':levelID' => $level]); - if($query->rowCount() == 0){ - exit("Level #$level doesn't exist."); - } - $levelName = $query->fetchColumn(); - $levelstring .= $levelName . ", "; - } - $levelstring = substr($levelstring,0,-2); - $diff = 0; - $diffname = "Auto"; - switch($stars){ - case 1: - $diffname = "Auto"; - $diff = 0; - break; - case 2: - $diffname = "Easy"; - $diff = 1; - break; - case 3: - $diffname = "Normal"; - $diff = 2; - break; - case 4: - case 5: - $diffname = "Hard"; - $diff = 3; - break; - case 6: - case 7: - $diffname = "Harder"; - $diff = 4; - break; - case 8: - case 9: - $diffname = "Insane"; - $diff = 5; - break; - case 10: - $diffname = "Demon"; - $diff = 6; - break; - } - echo "AccountID: $accountID
    - Pack Name: $packName
    - Levels: $levelstring ($levels)
    - Difficulty: $diffname ($diff)
    - Stars: $stars
    - Coins: $coins
    - RGB Color: $rgb"; - $query = $db->prepare("INSERT INTO mappacks (name, levels, stars, coins, difficulty, rgbcolors) - VALUES (:name,:levels,:stars,:coins,:difficulty,:rgbcolors)"); - $query->execute([':name' => $packName, ':levels' => $levels, ':stars' => $stars, ':coins' => $coins, ':difficulty' => $diff, ':rgbcolors' => $rgb]); - $query = $db->prepare("INSERT INTO modactions (type, value, timestamp, account, value2, value3, value4, value7) - VALUES ('11',:value,:timestamp,:account,:levels, :stars, :coins, :rgb)"); - $query->execute([':value' => $packName, ':timestamp' => time(), ':account' => $accountID, ':levels' => $levels, ':stars' => $stars, ':coins' => $coins, ':rgb' => $rgb]); - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
    Username: -
    Password: -
    Pack Name: -
    Level IDs: (separate by commas) -
    Stars: (max 10) -
    Coins: (max 2) -
    Color: -
    '; -} -?> diff --git a/tools/revertLikes.php b/tools/revertLikes.php deleted file mode 100644 index b467cfff6..000000000 --- a/tools/revertLikes.php +++ /dev/null @@ -1,54 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - if($gs->checkPermission($accountID, "toolLeaderboardsban")){ //TODO: create a permission for this - if(!is_numeric($levelID)) - exit("Invalid level ID"); - - $query = $db->prepare("SELECT count(*) FROM actions WHERE value = :levelID AND type = 3 AND timestamp >= :timestamp"); - $query->execute([':levelID' => $levelID, ':timestamp' => $timestamp]); - $count = $query->fetchColumn(); - - $query = $db->prepare("UPDATE levels SET likes = likes + :count WHERE levelID = :levelID"); - $query->execute([':levelID' => $levelID, ':count' => $count]); - - if($query->rowCount() != 0){ - echo "Banned succesfully."; - }else{ - echo "Ban failed."; - } - - $query = $db->prepare("INSERT INTO modactions (type, value, value2, value3, timestamp, account) - VALUES ('17',:levelID, '1', :now,:account)"); - $query->execute([':levelID' => $levelID,':timestamp' => $timestamp, ':now' => time(), ':account' => $accountID]); - - }else{ - exit("You do not have the permission to do this action. Try again"); - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
    Your Username: -
    Your Password: -
    Level ID: -
    Timestamp since: -
    '; -} -?> \ No newline at end of file diff --git a/tools/songAdd.php b/tools/songAdd.php deleted file mode 100644 index a544cddf6..000000000 --- a/tools/songAdd.php +++ /dev/null @@ -1,30 +0,0 @@ -songReupload($_POST['songlink']); - if($result == "-4"){ - echo "This URL doesn't point to a valid audio file."; - }elseif($result == "-3") - echo "This song already exists in our database."; - elseif($result == "-2") - echo "The download link isn't a valid URL"; - else - echo "Song reuploaded: ${result}
    "; - -}else{ - echo 'Direct links or Dropbox links only accepted, NO YOUTUBE LINKS
    -
    - Link:
    '; - Captcha::displayCaptcha(); - echo '
    '; -} -?> \ No newline at end of file diff --git a/tools/stats/dailyTable.php b/tools/stats/dailyTable.php deleted file mode 100644 index a1e0249c1..000000000 --- a/tools/stats/dailyTable.php +++ /dev/null @@ -1,35 +0,0 @@ -

    Daily Levels

    - -prepare("SELECT dailyfeatures.feaID, dailyfeatures.levelID, dailyfeatures.timestamp, levels.levelName, users.userName FROM dailyfeatures INNER JOIN levels ON dailyfeatures.levelID = levels.levelID INNER JOIN users ON levels.userID = users.userID WHERE timestamp < :time ORDER BY feaID DESC"); -$query->execute([':time' => time()]); -$result = $query->fetchAll(); -foreach($result as &$daily){ - //basic daily info - $feaID = $daily["feaID"]; - $levelID = $daily["levelID"]; - $time = $daily["timestamp"]; - $levelName = $daily["levelName"]; - $creator = $daily["userName"]; - echo ""; - //level name - /*$query = $db->prepare("SELECT levelName, userID FROM levels WHERE levelID = :level"); - $query->execute([':level' => $levelID]); - $level = $query->fetch(); - $levelName = $level["levelName"]; - $userID = $level["userID"];*/ - echo ""; - //creator name - /*$query = $db->prepare("SELECT userName FROM users WHERE userID = :userID"); - $query->execute([':userID' => $userID]); - $creator = $query->fetchColumn();*/ - echo ""; - //timestamp - $time = date("d/m/Y H:i", $time); - echo ""; -} -?> -
    #IDNameCreatorTime
    $feaID$levelID$levelName$creator$time
    \ No newline at end of file diff --git a/tools/stats/modActions.php b/tools/stats/modActions.php deleted file mode 100644 index 5b8ca69dc..000000000 --- a/tools/stats/modActions.php +++ /dev/null @@ -1,117 +0,0 @@ -

    Actions Count

    - - -getAccountsWithPermission("toolModactions")); -if($accounts == ""){ - exit("Error: No accounts with the 'toolModactions' permission have been found"); -} -$query = $db->prepare("SELECT accounts.accountID, accounts.userName, users.lastPlayed FROM accounts INNER JOIN users ON users.extID = accounts.accountID WHERE accountID IN ($accounts) ORDER BY userName ASC"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$mod){ - $time = date("d/m/Y G:i:s", $mod['lastPlayed']); - //TODO: optimize the count queries - $query = $db->prepare("SELECT count(*) FROM modactions WHERE account = :id"); - $query->execute([':id' => $mod["accountID"]]); - $actionscount = $query->fetchColumn(); - $query = $db->prepare("SELECT count(*) FROM modactions WHERE account = :id AND type = '1'"); - $query->execute([':id' => $mod["accountID"]]); - $lvlcount = $query->fetchColumn(); - echo ""; -} -?> -
    ModeratorCountLevels ratedLast time online
    ${mod["userName"]}${actionscount}${lvlcount}${time}
    -

    Actions Log

    - -prepare("SELECT modactions.*, accounts.userName FROM modactions INNER JOIN accounts ON modactions.account = accounts.accountID ORDER BY ID DESC"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$action){ - //detecting mod - /*$account = $action["account"]; - $query = $db->prepare("SELECT userName FROM accounts WHERE accountID = :id"); - $query->execute([':id'=>$account]); - $account = $query->fetchColumn();*/ - //detecting action - $value = $action["value"]; - $value2 = $action["value2"]; - $account = $action["userName"]; - switch($action["type"]){ - case 1: - $actionname = "Rated a level"; - break; - case 2: - $actionname = "Featured change"; - break; - case 3: - $actionname = "Coins verification state"; - break; - case 4: - $actionname = "Epic change"; - break; - case 5: - $actionname = "Set as daily feature"; - if(is_numeric($value2)){ - $value2 = date("d/m/Y G:i:s", $value2); - } - break; - case 6: - $actionname = "Deleted a level"; - break; - case 7: - $actionname = "Creator change"; - break; - case 8: - $actionname = "Renamed a level"; - break; - case 9: - $actionname = "Changed level password"; - break; - case 10: - $actionname = "Changed demon difficulty"; - break; - case 11: - $actionname = "Shared CP"; - break; - case 12: - $actionname = "Changed level publicity"; - break; - case 13: - $actionname = "Changed level description"; - break; - case 15: - $actionname = "Un/banned a user"; - break; - case 16: - $actionname = "Song ID change"; - break; - default: - $actionname = $action["type"]; - break; - } - if($action["type"] == 2 OR $action["type"] == 3 OR $action["type"] == 4 OR $action["type"] == 15){ - if($action["value"] == 1){ - $value = "True"; - }else{ - $value = "False"; - } - } - if($action["type"] == 5 OR $action["type"] == 6){ - $value = ""; - } - $time = date("d/m/Y G:i:s", $action["timestamp"]); - if($action["type"] == 5 AND $action["value2"] > time()){ - echo ""; - }else{ - echo ""; - } - -} -?> -
    ModeratorActionValueValue2LevelIDTime
    ".$account."".$actionname."".$value."".$value2."future".$time."
    ".$account."".$actionname."".$value."".$value2."".$action["value3"]."".$time."
    \ No newline at end of file diff --git a/tools/stats/noLogIn.php b/tools/stats/noLogIn.php deleted file mode 100644 index a7a0dcfa3..000000000 --- a/tools/stats/noLogIn.php +++ /dev/null @@ -1,30 +0,0 @@ -

    Unused Accounts

    - -prepare("SELECT accountID, userName, registerDate FROM accounts"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$account){ - $query = $db->prepare("SELECT count(*) FROM users WHERE extID = :accountID"); - $query->execute([':accountID' => $account["accountID"]]); - if($query->fetchColumn() == 0){ - $register = date("d/m/Y G:i:s", $account["registerDate"]); - echo ""; - ob_flush(); - flush(); - $time = time() - 2592000; - if($account["registerDate"] < $time){ - echo ""; - } - echo ""; - $x++; - } -} -?> -
    #IDNameRegistration date
    $x".$account["accountID"] . "" . $account["userName"] . "$register1
    \ No newline at end of file diff --git a/tools/stats/packTable.php b/tools/stats/packTable.php deleted file mode 100644 index 01399e100..000000000 --- a/tools/stats/packTable.php +++ /dev/null @@ -1,102 +0,0 @@ -

    MAP PACKS

    - -prepare("SELECT * FROM mappacks ORDER BY ID ASC"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$pack){ - $lvlarray = explode(",", $pack["levels"]); - echo ""; -} -/* - GAUNTLETS -*/ -?> -
    #IDMap PackStarsCoinsLevels
    $x".$pack["ID"]."".htmlspecialchars($pack["name"],ENT_QUOTES)."".$pack["stars"]."".$pack["coins"].""; - $x++; - foreach($lvlarray as &$lvl){ - echo $lvl . " - "; - $query = $db->prepare("SELECT levelName FROM levels WHERE levelID = :levelID"); - $query->execute([':levelID' => $lvl]); - $levelName = $query->fetchColumn(); - echo $levelName . ", "; - } - echo "
    -

    GAUNTLETS

    - -prepare("SELECT * FROM gauntlets ORDER BY ID ASC"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$gauntlet){ - $gauntletname = "Unknown"; - switch($gauntlet["ID"]){ - case 1: - $gauntletname = "Fire"; - break; - case 2: - $gauntletname = "Ice"; - break; - case 3: - $gauntletname = "Poison"; - break; - case 4: - $gauntletname = "Shadow"; - break; - case 5: - $gauntletname = "Lava"; - break; - case 6: - $gauntletname = "Bonus"; - break; - case 7: - $gauntletname = "Chaos"; - break; - case 8: - $gauntletname = "Demon"; - break; - case 9: - $gauntletname = "Time"; - break; - case 10: - $gauntletname = "Crystal"; - break; - case 11: - $gauntletname = "Magic"; - break; - case 12: - $gauntletname = "Spike"; - break; - case 13: - $gauntletname = "Monster"; - break; - case 14: - $gauntletname = "Doom"; - break; - case 15: - $gauntletname = "Death"; - break; - - } - echo ""; - for ($x = 1; $x < 6; $x++) { - echo ""; - } - echo ""; -} -/* - GAUNTLETS -*/ -?> -
    #NameLevel 1Level 2Level 3Level 4Level 5
    ".$gauntlet["ID"]."".$gauntletname.""; - $lvl = $gauntlet["level".$x]; - echo $lvl . " - "; - $query = $db->prepare("SELECT levelName FROM levels WHERE levelID = :levelID"); - $query->execute([':levelID' => $lvl]); - $levelName = $query->fetchColumn(); - echo "$levelName
    \ No newline at end of file diff --git a/tools/stats/reportList.php b/tools/stats/reportList.php deleted file mode 100644 index c38651cdf..000000000 --- a/tools/stats/reportList.php +++ /dev/null @@ -1,14 +0,0 @@ - -prepare("SELECT levels.levelID, levels.levelName, count(*) AS reportsCount FROM reports INNER JOIN levels ON reports.levelID = levels.levelID GROUP BY levels.levelID ORDER BY reportsCount DESC"); -$query->execute(); -$result = $query->fetchAll(); -foreach($result as &$report){ - $levelName = htmlspecialchars($report['levelName'], ENT_QUOTES); - echo ""; -} -?> -
    LevelIDLevel NameReported
    ${report['levelID']}$levelName${report['reportsCount']} times
    \ No newline at end of file diff --git a/tools/stats/songList.php b/tools/stats/songList.php deleted file mode 100644 index 82e916561..000000000 --- a/tools/stats/songList.php +++ /dev/null @@ -1,48 +0,0 @@ -
    - Search: -
    Search Type: - -
    - - - - - - - - - prepare("SELECT ID,name,authorName,size FROM songs WHERE " . $searchType . " LIKE CONCAT('%', :name, '%') ORDER BY ID DESC LIMIT 5000"); - $query->execute([':name' => $name]); - $result = $query->fetchAll(); - foreach ($result as &$song) { - echo ""; - } - ?> -
    IDSong NameSong AuthorSize
    " . $song["ID"] . "" . htmlspecialchars($song["name"], ENT_QUOTES) . "" . $song['authorName'] . "" . $song['size'] . "mb
    diff --git a/tools/stats/stats.php b/tools/stats/stats.php deleted file mode 100644 index 3291f5a9f..000000000 --- a/tools/stats/stats.php +++ /dev/null @@ -1,111 +0,0 @@ -

    Levels

    - - -prepare("SELECT count(*) FROM levels ".$params4." ".$params2); - $query->execute(); - $row = ""; - $query = $db->prepare("SELECT count(*) FROM levels WHERE starStars = 0 ".$params." ".$params2); - $query->execute(); - $row .= ""; - $query = $db->prepare("SELECT count(*) FROM levels WHERE starStars <> 0 ".$params." ".$params2); - $query->execute(); - $row .= ""; - $query = $db->prepare("SELECT count(*) FROM levels WHERE starFeatured <> 0 ".$params." ".$params2); - $query->execute(); - $row .= ""; - $query = $db->prepare("SELECT count(*) FROM levels WHERE starEpic <> 0 ".$params." ".$params2); - $query->execute(); - $row .= ""; - return $row; -} - -function generateQuery($groupBy, $requirements){ - $queryString = " - SELECT total.${groupBy}, total.amount AS total, unrated.amount AS unrated, rated.amount AS rated, featured.amount AS featured, epic.amount AS epic - FROM( - (SELECT ${groupBy}, count(*) AS amount FROM levels WHERE ${requirements} GROUP BY(${groupBy})) total - JOIN - (SELECT ${groupBy}, count(*) AS amount FROM levels WHERE ${requirements} AND starStars = 0 GROUP BY(${groupBy})) unrated - ON total.${groupBy} = unrated.${groupBy} - JOIN - (SELECT ${groupBy}, count(*) AS amount FROM levels WHERE ${requirements} AND starStars <> 0 GROUP BY(${groupBy})) rated - ON total.${groupBy} = rated.${groupBy} - JOIN - (SELECT ${groupBy}, count(*) AS amount FROM levels WHERE ${requirements} AND starFeatured <> 0 GROUP BY(${groupBy})) featured - ON total.${groupBy} = featured.${groupBy} - JOIN - (SELECT ${groupBy}, count(*) AS amount FROM levels WHERE ${requirements} AND starEpic <> 0 GROUP BY(${groupBy})) epic - ON total.${groupBy} = epic.${groupBy} - ) GROUP BY(total.${groupBy}) - "; - return $queryString; -} - -function fetchQuery($db, $groupBy, $requirements){ - $query = $db->prepare(generateQuery($groupBy, $requirements)); - $query->execute(); - return $query->fetchAll(); -} - - -echo genLvlRow("","","Total", ""); -foreach(fetchQuery($db, 'starAuto', 'starAuto = 1') as &$row){ - $diffName = $gs->getDifficulty(50, 1, 0); - echo ""; -} - -foreach(fetchQuery($db, 'starDifficulty', 'starAuto = 0 AND starDemon = 0') as &$row){ - $diffName = $gs->getDifficulty($row['starDifficulty'], 0, 0); - echo ""; -} - -foreach(fetchQuery($db, 'starDemon', 'starDemon = 1') as &$row){ - $diffName = $gs->getDifficulty(50, 0, 1); - echo ""; -} -?> -
    DifficultyTotalUnratedRatedFeaturedEpic
    $params3".$query->fetchColumn()."".$query->fetchColumn()."".$query->fetchColumn()."".$query->fetchColumn()."".$query->fetchColumn()."
    ${diffName}${row['total']}${row['unrated']}${row['rated']}${row['featured']}${row['epic']}
    ${diffName}${row['total']}${row['unrated']}${row['rated']}${row['featured']}${row['epic']}
    ${diffName}${row['total']}${row['unrated']}${row['rated']}${row['featured']}${row['epic']}
    -

    Demons

    - - -prepare(generateQuery('starDemonDiff', 'starDemon = 1')); -$query->execute(); -foreach($query->fetchAll() as &$row){ - $diffName = $gs->getDemonDiff($row['starDemonDiff']); - echo ""; -} -?> -
    DifficultyTotalUnratedRatedFeaturedEpic
    ${diffName}${row['total']}${row['unrated']}${row['rated']}${row['featured']}${row['epic']}
    - -

    Accounts

    - - -prepare("SELECT count(*) FROM users"); -$query->execute(); -$thing = $query->fetchColumn(); -echo ""; -$query = $db->prepare("SELECT count(*) FROM accounts"); -$query->execute(); -$thing = $query->fetchColumn(); -echo ""; -$sevendaysago = time() - 604800; -$query = $db->prepare("SELECT count(*) FROM users WHERE lastPlayed > :lastPlayed"); -$query->execute([':lastPlayed' => $sevendaysago]); -$thing = $query->fetchColumn(); -echo ""; -?> -
    TypeCount
    Total$thing
    Registered$thing
    Active$thing
    -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - $accountID = $query->fetchColumn(); - if($query->rowCount()==0){ - echo "Invalid account/password. Try again."; - }else if($gs->checkPermission($accountID, "toolSuggestlist")){ - $accountID = $query->fetchColumn(); - $query = $db->prepare("SELECT suggestBy,suggestLevelId,suggestDifficulty,suggestStars,suggestFeatured,suggestAuto,suggestDemon,timestamp FROM suggest ORDER BY timestamp DESC"); - $query->execute(); - $result = $query->fetchAll(); - echo ''; - foreach($result as &$sugg){ - echo ""; - } - echo "
    TimeSuggested byLevel IDDifficultyStarsFeatured
    ".date("d/m/Y G:i", $sugg["timestamp"])."".$gs->getAccountName($sugg["suggestBy"])."(".$sugg["suggestBy"].")".htmlspecialchars($sugg["suggestLevelId"],ENT_QUOTES)."".htmlspecialchars($gs->getDifficulty($sugg["suggestDifficulty"],$sugg["suggestAuto"],$sugg["suggestDemon"]), ENT_QUOTES)."".htmlspecialchars($sugg["suggestStars"],ENT_QUOTES)."".htmlspecialchars($sugg["suggestFeatured"],ENT_QUOTES)."
    "; - }else{ - echo "You don't have permissions to view content on this page. Try again.\n"; - } - }else{ - echo "Invalid account/password. Try again."; - } -}else{ - echo '
    Username: -
    Password:
    '; -} -?> diff --git a/tools/stats/top24h.php b/tools/stats/top24h.php deleted file mode 100644 index 21bb089f2..000000000 --- a/tools/stats/top24h.php +++ /dev/null @@ -1,17 +0,0 @@ -

    TOP LEADERBOARD PROGRESS

    - -prepare("SELECT users.userID, SUM(actions.value) AS stars, users.userName FROM actions INNER JOIN users ON actions.account = users.userID WHERE type = '9' AND timestamp > :time AND users.isBanned = 0 GROUP BY(users.userID)"); -$query->execute([':time' => $time]); -$result = $query->fetchAll(); -foreach($result as &$gain){ - $x++; - echo ""; -} -?> -
    #UserIDUserNameStars
    $x${gain['userID']}${gain['userName']}${gain['stars']}
    \ No newline at end of file diff --git a/tools/stats/unlisted.php b/tools/stats/unlisted.php deleted file mode 100644 index 0dec75add..000000000 --- a/tools/stats/unlisted.php +++ /dev/null @@ -1,32 +0,0 @@ -prepare("SELECT accountID FROM accounts WHERE userName=:userName"); - $query->execute([':userName' => $userName]); - if($query->rowCount()==0){ - echo "Invalid password or nonexistant account. Try again"; - }else{ - $accountID = $query->fetchColumn(); - $query = $db->prepare("SELECT levelID, levelName FROM levels WHERE extID=:extID AND unlisted=1"); - $query->execute([':extID' => $accountID]); - $result = $query->fetchAll(); - echo ''; - foreach($result as &$level){ - echo ""; - } - echo "
    IDName
    ".$level["levelID"]."".$level["levelName"]."
    "; - } - }else{ - echo "Invalid password or nonexistant account. Try again"; - } -}else{ - echo '
    Username: -
    Password:
    '; -} -?> \ No newline at end of file diff --git a/tools/stats/vipList.php b/tools/stats/vipList.php deleted file mode 100644 index 753cce349..000000000 --- a/tools/stats/vipList.php +++ /dev/null @@ -1,20 +0,0 @@ -

    VIP List

    -prepare("SELECT roleID, roleName FROM roles WHERE priority > 0 ORDER BY priority DESC"); -$query->execute(); -$result = $query->fetchAll(); -foreach ($result as $role) { - echo "

    " . $role['roleName'] . "

    "; - $query2 = $db->prepare("SELECT users.userName, users.lastPlayed FROM roleassign INNER JOIN users ON roleassign.accountID = users.extID WHERE roleassign.roleID = :roleID"); - $query2->execute([':roleID' => $role["roleID"]]); - $account = $query2->fetchAll(); - echo ''; - foreach ($account as $user) { - $time = date("d/m/Y G:i:s", $user["lastPlayed"]); - $username = htmlspecialchars($user["userName"], ENT_QUOTES); - echo ""; - } - echo "
    UserLast Online
    " . $username . "$time
    "; -} diff --git a/unblockGJUser20.php b/unblockGJUser20.php index 200715562..f0b68851e 100644 --- a/unblockGJUser20.php +++ b/unblockGJUser20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJAccSettings20.php b/updateGJAccSettings20.php index aa08f6223..e47c38191 100644 --- a/updateGJAccSettings20.php +++ b/updateGJAccSettings20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJDesc20.php b/updateGJDesc20.php index b326e40dc..0106b6cfc 100644 --- a/updateGJDesc20.php +++ b/updateGJDesc20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJUserScore.php b/updateGJUserScore.php index 096741e24..8c01ed04f 100644 --- a/updateGJUserScore.php +++ b/updateGJUserScore.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJUserScore19.php b/updateGJUserScore19.php index 096741e24..8c01ed04f 100644 --- a/updateGJUserScore19.php +++ b/updateGJUserScore19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJUserScore20.php b/updateGJUserScore20.php index 096741e24..8c01ed04f 100644 --- a/updateGJUserScore20.php +++ b/updateGJUserScore20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJUserScore21.php b/updateGJUserScore21.php index 096741e24..8c01ed04f 100644 --- a/updateGJUserScore21.php +++ b/updateGJUserScore21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/updateGJUserScore22.php b/updateGJUserScore22.php index 096741e24..8c01ed04f 100644 --- a/updateGJUserScore22.php +++ b/updateGJUserScore22.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadFriendRequest20.php b/uploadFriendRequest20.php index ffc0de83f..a7ad8abfe 100644 --- a/uploadFriendRequest20.php +++ b/uploadFriendRequest20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJAccComment20.php b/uploadGJAccComment20.php index 1229a9523..2a6db2cf4 100644 --- a/uploadGJAccComment20.php +++ b/uploadGJAccComment20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJComment.php b/uploadGJComment.php index 2f97f7225..8c0946e67 100644 --- a/uploadGJComment.php +++ b/uploadGJComment.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJComment19.php b/uploadGJComment19.php index 2f97f7225..8c0946e67 100644 --- a/uploadGJComment19.php +++ b/uploadGJComment19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJComment20.php b/uploadGJComment20.php index 2f97f7225..8c0946e67 100644 --- a/uploadGJComment20.php +++ b/uploadGJComment20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJComment21.php b/uploadGJComment21.php index 2f97f7225..8c0946e67 100644 --- a/uploadGJComment21.php +++ b/uploadGJComment21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJLevel.php b/uploadGJLevel.php index 50e5a3e86..fc4dfeae4 100644 --- a/uploadGJLevel.php +++ b/uploadGJLevel.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJLevel19.php b/uploadGJLevel19.php index 50e5a3e86..fc4dfeae4 100644 --- a/uploadGJLevel19.php +++ b/uploadGJLevel19.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJLevel20.php b/uploadGJLevel20.php index 50e5a3e86..fc4dfeae4 100644 --- a/uploadGJLevel20.php +++ b/uploadGJLevel20.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJLevel21.php b/uploadGJLevel21.php index 50e5a3e86..fc4dfeae4 100644 --- a/uploadGJLevel21.php +++ b/uploadGJLevel21.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/uploadGJLevelList.php b/uploadGJLevelList.php index b9e3653a3..14752bb06 100644 --- a/uploadGJLevelList.php +++ b/uploadGJLevelList.php @@ -1,3 +1,3 @@ - \ No newline at end of file diff --git a/uploadGJMessage20.php b/uploadGJMessage20.php index 75b976f92..bf805e317 100644 --- a/uploadGJMessage20.php +++ b/uploadGJMessage20.php @@ -1,3 +1,3 @@ \ No newline at end of file