", '€','‚','ƒ','„','…','†','‡','ˆ', '‰','Š','‹','Œ','Ž','‘','’','“', '”','•','–','—','˜','™','š','›', 'œ','ž','Ÿ' ]; $With = [ ''','"','<','>', '€','‚','ƒ','„','…','†','‡','ˆ', '‰','Š','‹','Œ','Ž','‘','’','“', '”','•','–','—','˜','™','š','›', 'œ','ž','Ÿ' ]; $Str = str_replace($Replace, $With, $Str); } return $Str; } /** * Un-HTML-escape a string for output. * * It's like the above function, but in reverse. * * @param string $Str * @return string unescaped string */ function reverse_display_str($Str) { if ($Str === null || $Str === false || is_array($Str)) { return ''; } if ($Str != '' && !is_number($Str)) { $Replace = [ ''','"','<','>', '€','‚','ƒ','„','…','†','‡','ˆ', '‰','Š','‹','Œ','Ž','‘','’','“', '”','•','–','—','˜','™','š','›', 'œ','ž','Ÿ' ]; $With = [ "'",'"',"<",">", '€','‚','ƒ','„','…','†','‡','ˆ', '‰','Š','‹','Œ','Ž','‘','’','“', '”','•','–','—','˜','™','š','›', 'œ','ž','Ÿ' ]; $Str = str_replace($Replace, $With, $Str); $Str = str_replace("&", "&", $Str); $Str = mb_convert_encoding($Str, 'UTF-8', 'HTML-ENTITIES'); } return $Str; } /** * Send a message to an IRC bot listening on SOCKET_LISTEN_PORT * * @param string $Raw An IRC protocol snippet to send. */ function send_irc($Raw) { Irc::sendRaw($Raw); } /** * Determine the redirect header to use based on the client HTTP_REFERER or fallback * * @param string fallback URL if HTTP_REFERER is empty * @return string redirect URL */ function redirectUrl(string $fallback): string { return empty($_SERVER['HTTP_REFERER']) ? $fallback : $_SERVER['HTTP_REFERER']; } /** * Display a critical error and kills the page. * * @param string $Error Error type. Automatically supported: * 403, 404, 0 (invalid input), -1 (invalid request) * If you use your own string for Error, it becomes the error description. * @param boolean $NoHTML If true, the header/footer won't be shown, just the description. * @param string $Log If true, the user is given a link to search $Log in the site log. */ function error($Error, $NoHTML = false, $Log = false) { global $Debug; require_once(__DIR__ . '/../sections/error/index.php'); $Debug->profile(); die(); } /** * Convenience function for check_perms within Permissions class. * * @see Permissions::check_perms() * * @param string $PermissionName * @param int $MinClass * @return bool */ function check_perms($PermissionName, $MinClass = 0) { return Permissions::check_perms($PermissionName, $MinClass); } /** * Print JSON status result with an optional message and die. */ function json_die($Status, $Message="bad parameters") { json_print($Status, $Message); die(); } /** * Print JSON status result with an optional message. */ function json_print($Status, $Message) { if (check_perms('site_debug')) { global $Debug; $Message['queries'] = $Debug->get_queries(); $Message['searches'] = $Debug->get_sphinxql_queries(); } if ($Status == 'success' && $Message) { $response = ['status' => $Status, 'response' => $Message]; } elseif ($Message) { $response = ['status' => $Status, 'error' => $Message]; } else { $response = ['status' => $Status, 'response' => []]; } print(json_encode(add_json_info($response))); } function json_error($Code) { echo json_encode(add_json_info(['status' => 'failure', 'error' => $Code, 'response' => []])); die(); } function json_or_error($JsonError, $Error = null, $NoHTML = false) { if (defined('AJAX')) { json_error($JsonError); } else { error($Error ?? $JsonError, $NoHTML); } } function add_json_info($Json) { if (!isset($Json['info'])) { $Json = array_merge($Json, [ 'info' => [ 'source' => SITE_NAME, 'version' => 1, ], ]); } return $Json; } /** * Print the site's URL including the appropriate URI scheme, including the trailing slash * * @param bool $SSL - whether the URL should be crafted for HTTPS or regular HTTP * @return url for site */ function site_url($SSL = true) { return $SSL ? 'https://' . SSL_SITE_URL . '/' : 'http://' . NONSSL_SITE_URL . '/'; } /** * Hydrate an array from a query string (everything that follow '?') * This reimplements parse_str() and side-steps the issue of max_input_vars limits. * * Example: * in: li[]=14&li[]=31&li[]=58&li[]=68&li[]=69&li[]=54&li[]=5, param=li[] * parsed: ['li[]' => ['14', '31, '58', '68', '69', '5']] * out: ['14', '31, '58', '68', '69', '5'] * * @param string query string from url * @param string url param to extract * @return array hydrated equivalent */ function parseUrlArgs(string $urlArgs, string $param): array { $list = []; $pairs = explode('&', $urlArgs); foreach ($pairs as $p) { [$name, $value] = explode('=', $p, 2); if (!isset($list[$name])) { $list[$name] = $value; } else { if (!is_array($list[$name])) { $list[$name] = [$list[$name]]; } $list[$name][] = $value; } } return array_key_exists($param, $list) ? $list[$param] : []; } /** * The text of the pop-up confirmation when burning an FL token. * * @param integer $seeders - number of seeders for the torrent * @return string Warns if there are no seeders on the torrent */ function FL_confirmation_msg($seeders, $size) { $TokensToUse = ceil($size / BYTES_PER_FREELEECH_TOKEN); /* Coder Beware: this text is emitted as part of a Javascript single quoted string. * Any apostrophes should be avoided or escaped appropriately (with \\'). */ return ($seeders == 0) ? 'Warning! This torrent is not seeded at the moment, are you sure you want to use '.$TokensToUse.' Freeleech token(s) here?' : 'Are you sure you want to use '.$TokensToUse.' Freeleech token(s) here?'; } /** * Utility function that unserializes an array, and then if the unserialization fails, * it'll then return an empty array instead of a null or false which will break downstream * things that require an incoming array * * @param string $array * @return array */ function unserialize_array($array) { $array = empty($array) ? [] : unserialize($array); return (empty($array)) ? [] : $array; } /** * Utility function for determining if checkbox should be checked if some $value is set or not * @param $value * @return string */ function isset_array_checked($array, $value) { return (isset($array[$value])) ? "checked" : ""; } /** * Helper function to return an string of N elements from an array. * * (e.g. [2, 4, 6] into a list of query placeholders (e.g. '?,?,?') * By default '?' is used, but a custom placeholder may be specified, * such as '(?)' or '(?, now(), 100)', for use in a bulk insert. * * @param array $list The list of elements * @param string $placeholder ('?' by default). * @return string The resulting placeholder string. */ function placeholders(array $list, $placeholder = '?') { return implode(',', array_fill(0, count($list), $placeholder)); } /** * Magical function. * * @param string $Str function to detect encoding on. * @return true if the string is in UTF-8. */ function is_utf8($Str) { return preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] // ASCII | [\xC2-\xDF][\x80-\xBF] // non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] // excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} // straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] // excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} // planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} // planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} // plane 16 )*$%xs', $Str ); } /** * Detect the encoding of a string and transform it to UTF-8. * * @param string $Str * @return UTF-8 encoded version of $Str */ function make_utf8($Str) { if ($Str != '') { if (is_utf8($Str)) { $Encoding = 'UTF-8'; } if (empty($Encoding)) { $Encoding = mb_detect_encoding($Str, 'UTF-8, ISO-8859-1'); } if (empty($Encoding)) { $Encoding = 'ISO-8859-1'; } if ($Encoding == 'UTF-8') { return $Str; } else { return @mb_convert_encoding($Str, 'UTF-8', $Encoding); } } } /** * Generate a random string drawn from alphanumeric characters * but omitting lowercase l, uppercase I and O (to avoid confusion). * * @param int $len * @return string random alphanumeric string */ function randomString($len = 32) { $alphabet = str_split('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'); $max = count($alphabet); $mask = (int)pow(2, ceil(log($len, 2))) - 1; $out = ''; while (strlen($out) < $len) { $n = ord(openssl_random_pseudo_bytes(1)) & $mask; if ($n < $max) { $out .= $alphabet[$n]; } } return $out; } // TODO: reconcile this with log_attempt in login/index.php function log_token_attempt(DB_MYSQL $db, int $userId = 0): void { $watch = new Gazelle\LoginWatch; $ipStr = $_SERVER['REMOTE_ADDR']; [$attemptId, $attempts, $bans] = $db->row(' SELECT ID, Attempts, Bans FROM login_attempts WHERE IP = ? ', $_SERVER['REMOTE_ADDR'] ); if (!$attemptId) { $watch->create($ipStr, null, $userId); return; } $attempts++; $watch->setWatch($attemptId); if ($attempts < 6) { $watch->increment($userId, $ipStr, null); return; } $watch->ban($attempts, null, $userId); if ($bans > 9) { (new IPv4())->createBan(0, $ipStr, $ipStr, 'Automated ban per failed token usage'); } } /** * Shorten a string * * @param string $text string to cut * @param int $maxLength cut at length * @param bool $force force cut at length instead of at closest word * @param bool $ellipsis Show dots at the end * @return string formatted string */ function shortenString(string $text, int $maxLength, bool $force = false, bool $ellipsis = true): string { if (mb_strlen($text, 'UTF-8') <= $maxLength) { return $text; } if ($force) { $short = mb_substr($text, 0, $maxLength, 'UTF-8'); } else { $short = mb_substr($text, 0, $maxLength, 'UTF-8'); $words = explode(' ', $short); if (count($words) > 1) { array_pop($words); $short = implode(' ', $words); } } if ($ellipsis) { $short .= "\xE2\x80\xA6"; // U+2026 HORIZONTAL ELLIPSIS } return $short; } function display_array($Array, $Escape = []) { foreach ($Array as $Key => $Val) { if ((!is_array($Escape) && $Escape == true) || !in_array($Key, $Escape)) { $Array[$Key] = display_str($Val); } } return $Array; }