*/ class Utils { /** * Get the emoticons. * * @return array Array containing emoticons. */ public static function getEmotes() { return Database::fetch('emoticons'); } /** * Parse the emoticons. * * @param string $text String to parse emoticons from. * * @return string Parsed text. */ public static function parseEmotes($text) { // Get emoticons from the database $emotes = self::getEmotes(); // Do the replacements foreach ($emotes as $emote) { $text = str_replace( $emote['emote_string'], '' . $emote['emote_string'] . '', $text ); } // Return the parsed text return $text; } /** * Verify a ReCaptcha * * @param string $response The user response. * * @return array The response from the ReCaptcha API. */ public static function verifyCaptcha($response) { // Attempt to get the response $resp = @file_get_contents( 'https://www.google.com/recaptcha/api/siteverify?secret=' . Config::get('recaptcha_private') . '&response=' . $response ); // In the highly unlikely case that it failed to get anything forge a false if (!$resp) { return false; } // Decode the response JSON from the servers $resp = json_decode($resp, true); // Return shit return $resp; } /** * The error handler. * * @param int $errno The error ID. * @param string $errstr Quick description of the event. * @param string $errfile File the error occurred in. * @param int $errline Line the error occurred on. */ public static function errorHandler($errno, $errstr, $errfile, $errline) { // Remove ROOT path from the error string and file location $errstr = str_replace(ROOT, '', $errstr); $errfile = str_replace(ROOT, '', $errfile); // Attempt to log the error to the database if (Database::$database !== null) { // Encode backtrace data $backtrace = base64_encode(json_encode(debug_backtrace())); // Check if this error has already been logged in the past if ($past = Database::fetch( 'error_log', false, [ 'error_backtrace' => [$backtrace, '=', true], 'error_string' => [$errstr, '='], 'error_line' => [$errline, '='], ] )) { // If so assign the errid $errid = $past['error_id']; } else { // Create an error ID $errid = substr(md5(microtime()), rand(0, 22), 10); // Log the error Database::insert('error_log', [ 'error_id' => $errid, 'error_timestamp' => date("r"), 'error_revision' => SAKURA_VERSION, 'error_type' => $errno, 'error_line' => $errline, 'error_string' => $errstr, 'error_file' => $errfile, 'error_backtrace' => $backtrace, ]); } } switch ($errno) { case E_ERROR: case E_USER_ERROR: $error = 'FATAL ERROR: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; break; case E_WARNING: case E_USER_WARNING: $error = 'WARNING: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; break; case E_NOTICE: case E_USER_NOTICE: $error = 'NOTICE: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; break; default: $error = 'Unknown error type [' . $errno . ']: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile; } // Truncate all previous outputs ob_clean(); ob_end_clean(); // Check if this request was made through the ajax thing if (isset($_REQUEST['ajax'])) { die('An error occurred while executing the script.|1|javascript:alert("' . (isset($errid) ? 'Error Log ID: '. $errid : 'Failed to log.') . '");'); } // Check for dev mode $detailed = Config::local('dev', 'show_errors'); // Build page $errorPage = ' Sakura Internal Error

An error occurred while executing the script.

To prevent potential security risks or data loss Sakura has stopped execution of the script.

'; if (isset($errid)) { $errorPage .= '

The error and surrounding data has been logged.

' . (!$detailed ? 'Report the following text to a staff member' : 'Logged as') . '

' . $errid . '
'; } else { $errorPage .= '

Sakura was not able to log this error which could mean that there was an error with the database connection. If you\'re the system administrator check the database credentials and make sure the server is running and if you\'re not please let the system administrator know about this error if it occurs again.

'; } if ($detailed) { $errorPage .= '

Summary

' . $error . '

Backtraces

'; foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $num => $trace) { $errorPage .= '

#' . $num . '

';

                foreach ($trace as $key => $val) {
                    $errorPage .=
                    str_pad(
                        '[' . $key . ']',
                        12
                    ) . '=> ' . (
                        is_array($val) || is_object($val) ?
                        json_encode($val) :
                        $val
                    ) . "\r\n";
                }

                $errorPage .= '
'; } } $errorPage .= '
'; // Die and display error message die($errorPage); } /** * Send an e-mail. * * @param string $to Destination e-mail. * @param string $subject E-mail subject. * @param string $body Contents of the message. * @return bool|string Return whatever PHPMailer returns. */ public static function sendMail($to, $subject, $body) { // Initialise PHPMailer $mail = new PHPMailer(); // Set to SMTP $mail->isSMTP(); // Set the SMTP server host $mail->Host = Config::get('smtp_server'); // Do we require authentication? $mail->SMTPAuth = Config::get('smtp_auth'); // Do we encrypt as well? $mail->SMTPSecure = Config::get('smtp_secure'); // Set the port to the SMTP server $mail->Port = Config::get('smtp_port'); // If authentication is required log in as well if (Config::get('smtp_auth')) { $mail->Username = Config::get('smtp_username'); $mail->Password = base64_decode(Config::get('smtp_password')); } // Add a reply-to header $mail->addReplyTo(Config::get('smtp_replyto_mail'), Config::get('smtp_replyto_name')); // Set a from address as well $mail->setFrom(Config::get('smtp_from_email'), Config::get('smtp_from_name')); // Set the addressee foreach ($to as $email => $name) { $mail->addBCC($email, $name); } // Subject line $mail->Subject = $subject; // Set body $mail->Body = $body; // Send the message $send = $mail->send(); // Clear the addressee list $mail->clearAddresses(); // If we got an error return the error if (!$send) { return $mail->ErrorInfo; } // Else just return whatever return $send; } /** * Clean a string * * @param string $string Dirty string. * @param bool $lower Make the string lowercase. * @param bool $noSpecial String all special characters. * @param bool $replaceSpecial Thing to replace special characters with. * * @return string Clean string. */ public static function cleanString($string, $lower = false, $noSpecial = false, $replaceSpecial = '') { // Run common sanitisation function over string $string = htmlentities($string, ENT_NOQUOTES | ENT_HTML401, Config::get('charset')); $string = stripslashes($string); $string = strip_tags($string); // If set also make the string lowercase if ($lower) { $string = strtolower($string); } // If set remove all characters that aren't a-z or 0-9 if ($noSpecial) { $string = preg_replace('/[^a-z0-9]/', $replaceSpecial, $string); } // Return clean string return $string; } /** * Load contents of an infopage. * * @param string $id ID of the info page. * * @return array|bool Contents of the info page. */ public static function loadInfoPage($id) { // Get contents from the database $infopage = Database::fetch('infopages', false, ['page_shorthand' => [$id, '=']]); // Return the data if there is any else just return false return count($infopage) ? $infopage : false; } /** * Validate MX records. * * @param string $email E-mail address. * * @return bool Success. */ public static function checkMXRecord($email) { // Get the domain from the e-mail address $domain = substr(strstr($email, '@'), 1); // Check the MX record $record = checkdnsrr($domain, 'MX'); // Return the record data return $record; } /** * Detect the version of an IP. * * @param string $ip The IP. * * @return int Either 0, 4 or 6. */ public static function ipVersion($ip) { // Check if var is IP if (filter_var($ip, FILTER_VALIDATE_IP)) { // IPv4 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return 4; } // IPv6 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return 6; } } // Not an IP or unknown type return 0; } /** * Unpack an IPv6 * * @param mixed $inet IP address. * * @return null|string The unpacked IP. */ public static function inetToBits($inet) { // Unpack string $unpacked = unpack('A16', $inet); // Split the string $unpacked = str_split($unpacked[1]); // Define variable $binaryIP = null; // "Build" binary IP foreach ($unpacked as $char) { $binaryIP .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT); } // Return IP return $binaryIP; } /** * Match a subnet. * * @param string $ip the IP. * @param string $range The range. * * @return bool|int Success. */ public static function matchSubnet($ip, $range) { // Use the proper IP type switch (self::ipVersion($ip)) { case 4: // Break the range up in parts list($subnet, $bits) = explode('/', $range); // Convert IP and Subnet to long $ip = ip2long($ip); $subnet = ip2long($subnet); $mask = -1 << (32 - $bits); // In case the supplied subnet wasn't correctly aligned $subnet &= $mask; // Return true if IP is in subnet return ($ip & $mask) == $subnet; case 6: // Break the range up in parts list($subnet, $bits) = explode('/', $range); // Convert subnet to packed address and convert it to binary $subnet = inet_pton($subnet); $binarySubnet = self::inetToBits($subnet); // Convert IPv6 to packed address and convert it to binary as well $ip = inet_pton($ip); $binaryIP = self::inetToBits($ip); // Return bits of the strings according to the bits $ipBits = substr($binaryIP, 0, $bits); $subnetBits = substr($binarySubnet, 0, $bits); return ($ipBits === $subnetBits); default: return 0; } } /** * Check if an IP originates from CloudFlare. * * @param string $ip The IP. * * @return bool Success. */ public static function checkCFIP($ip) { // Get CloudFlare Subnet list $cfhosts = file_get_contents( ROOT . Config::local('data', 'cfipv' . (self::ipVersion($ip))) ); // Replace \r\n with \n $cfhosts = str_replace("\r\n", "\n", $cfhosts); // Explode the file into an array $cfhosts = explode("\n", $cfhosts); // Check if IP is in a CloudFlare subnet foreach ($cfhosts as $subnet) { // Check if the subnet isn't empty (git newline prevention) if (strlen($subnet) < 1) { continue; } // Return true if found if (self::matchSubnet($ip, $subnet)) { return true; } } // Return false if fails return false; } /** * Get the IP of a visitor. * * @return string The IP. */ public static function getRemoteIP() { // Assign REMOTE_ADDR to a variables $ip = $_SERVER['REMOTE_ADDR']; // Check if the IP is a CloudFlare IP if (self::checkCFIP($ip)) { // If it is check if the CloudFlare IP header is set and if it is assign it to the ip variable if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { $ip = $_SERVER['HTTP_CF_CONNECTING_IP']; } } // Return the correct IP return $ip; } /** * Get the country code of a visitor. * * @return string 2 character country code. */ public static function getCountryCode() { // Attempt to get country code using PHP's built in geo thing if (function_exists("geoip_country_code_by_name")) { $code = geoip_country_code_by_name(self::getRemoteIP()); // Check if $code is anything if ($code) { return $code; } } // Check if the required header is set and return it if (isset($_SERVER['HTTP_CF_IPCOUNTRY'])) { return $_SERVER['HTTP_CF_IPCOUNTRY']; } // Return XX as a fallback return 'XX'; } /** * Check the entropy of a password. * * @param string $pw Password. * * @return double|int Entropy. */ public static function pwdEntropy($pw) { // Decode utf-8 chars $pw = utf8_decode($pw); // Count the amount of unique characters in the password string and calculate the entropy return count(count_chars($pw, 1)) * log(256, 2); } /** * Get the country name from a 2 character code. * * @param string $code The country code. * * @return string The country name. */ public static function getCountryName($code) { // Parse JSON file $iso3166 = json_decode( utf8_encode( file_get_contents( ROOT . Config::local('data', 'iso3166') ) ), true ); // Check if key exists if (array_key_exists($code, $iso3166)) { return $iso3166[$code]; // If entry found return the full name } // Else return unknown return 'Unknown'; } /** * Get the FAQ table data (why is this a function). * * @return array FAQ data. */ public static function getFaqData() { // Do database call $faq = Database::fetch('faq', true, null, ['faq_id']); // Return FAQ data return $faq; } /** * Get the type of log in text (unused and will probably be removwed). * @param mixed $type * @return mixed */ public static function getLogStringFromType($type) { // Query the database $return = Database::fetch('logtypes', false, ['id' => [$type, '=']]); // Check if type exists and else return a unformattable string if (count($return) < 2) { return 'Unknown action.'; } // Return the string return $return['string']; } /** * Get a user's logs (unused, probably removed in future). * @param mixed $uid * @return array */ public static function getUserLogs($uid = 0) { // Check if a user is specified $conditions = ($uid ? ['uid' => [$uid, '=']] : null); // Get data from database $logsDB = Database::fetch('logs', true, $conditions, ['id', true]); // Storage array $logs = []; // Iterate over entries foreach ($logsDB as $log) { // Store usable data $logs[$log['id']] = [ 'user' => $_USER = Users::getUser($log['uid']), 'rank' => Users::getRank($_USER['rank_main']), 'string' => vsprintf(self::getLogStringFromType($log['action']), json_decode($log['attribs'], true)), ]; } // Return new logs return $logs; } /** * Calculate the time that has elapsed since a certain data (doesn't take leap years in account). * * @param int $timestamp The timestamp. * @param string $append Append to * @param string $none Text if 0. * * @return string Returns the time elapsed in a readable format. */ public static function timeElapsed($timestamp, $append = ' ago', $none = 'Just now') { // Subtract the entered timestamp from the current timestamp $time = time() - $timestamp; // If the new timestamp is below 1 return a standard string if ($time < 1) { return $none; } // Array containing time "types" $times = [ 365 * 24 * 60 * 60 => 'year', 30 * 24 * 60 * 60 => 'month', 24 * 60 * 60 => 'day', 60 * 60 => 'hour', 60 => 'minute', 1 => 'second', ]; foreach ($times as $secs => $str) { // Do a devision to check if the given timestamp fits in the current "type" $calc = $time / $secs; if ($calc >= 1) { // Round the number $round = floor($calc); // Return the string return $round . ' ' . $times[$secs] . ($round == 1 ? '' : 's') . $append; } } } /** * Get the byte symbol for a unit from bytes. * * @param int $bytes The amount of bytes. * * @return string The converted amount with the symbol. */ public static function getByteSymbol($bytes) { // Return nothing if the input was 0 if (!$bytes) { return; } // Array with byte symbols $symbols = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; // Calculate byte entity $exp = floor(log($bytes) / log(1024)); // Format the things $bytes = sprintf("%.2f " . $symbols[$exp], ($bytes / pow(1024, floor($exp)))); // Return the formatted string return $bytes; } /** * Get the premium tracker data. * * @return array The premium tracker data. */ public static function getPremiumTrackerData() { // Create data array $data = []; // Get database stuff $table = Database::fetch('premium_log', true, null, ['transaction_id', true]); // Add raw table data to data array $data['table'] = $table; // Create balance entry $data['balance'] = 0.0; // Create users entry $data['users'] = []; // Calculate the thing foreach ($table as $row) { // Calculate balance $data['balance'] = $data['balance'] + $row['transaction_amount']; // Add userdata to table if (!array_key_exists($row['user_id'], $data['users'])) { $data['users'][$row['user_id']] = User::construct($row['user_id']); } } // Return the data return $data; } /** * Add a new entry to the tracker. * * @param int $id The user ID. * @param float $amount The amount of money. * @param string $comment A little information. */ public static function updatePremiumTracker($id, $amount, $comment) { Database::insert('premium_log', [ 'user_id' => $id, 'transaction_amount' => $amount, 'transaction_date' => time(), 'transaction_comment' => $comment, ]); } /** * Clean up the contents of tags. * * @param string $text Dirty * * @return string Clean */ public static function fixCodeTags($text) { $parts = explode('', $text); $newStr = ''; if (count($parts) > 1) { foreach ($parts as $p) { $parts2 = explode('', $p); if (count($parts2) > 1) { $code = str_replace('
', '', $parts2[0]); $code = str_replace('
', '', $code); $code = str_replace('
', '', $code); $code = str_replace('<', '<', $code); $newStr .= ''.$code.''; $newStr .= $parts2[1]; } else { $newStr .= $p; } } } else { $newStr = $text; } return $newStr; } }