*/ public static function scandir(string $filepath):array { if (!\is_dir($filepath) || ($output = \scandir($filepath)) === false) { throw new MDFileDoesNotExist("There is no file {$filepath}"); } // Remove unwanted files from list $output = \array_diff($output, ['.', '..', '.git']); // Return array values, to make it a list rather than an associative array. // This should be done in a separate line, as it observably leads to a // significant reduction in the used RAM. return \array_values($output); } /** * Type safe wrapper around ob_get_clean(): Gets the current buffer * contents and delete current output buffer. * * @return string */ public static function ob_get_clean():string { $output = \ob_get_clean(); if ($output === false) throw new MDOutputBufferNotStarted("Output buffer was not started"); return $output; } /** * Function checking if a string starts with another. * * @param string $haystack String to check. * @param string $needle Potential start of $haystack. * * @return boolean */ public static function startsWith(string $haystack, string $needle):bool { if (substr($haystack, 0, \strlen($needle)) == $needle) return true; else return false; } /** * Function checking if a string starts with any input from the input array. * * @param string $haystack String to check. * @param string[] $needles Array containing potential start values of $haystack. * * @return boolean */ public static function startsWithAny(string $haystack, array $needles):bool { $output = false; foreach ($needles as $needle) { $output = self::startsWith($haystack, $needle); if ($output == true) return $output; } return $output; } /** * Type-safe(r) wrapper around preg_replace. * * @param string $pattern The pattern to search for. It can be either a string or an array with strings. * @param string $replacement To replace with. * @param string $subject The string or an array with strings to search and replace. * * @return string */ public static function preg_replace_str(string $pattern, string $replacement, string $subject):string { $output = \preg_replace($pattern, $replacement, $subject); if ($output === null) { throw new Exception("Error replacing in $subject: Replacing $pattern with $replacement"); } return $output; } /** * Type-safe wrapper around json_encode. * * @see https://www.php.net/manual/en/function.json-encode.php * * @param array $value The value being encoded. Can be any type except a resource. * @param integer $options Bitmask consisting of JSON_FORCE_OBJECT, JSON_HEX_QUOT ... * @param integer $depth Depth of coding. * * @return string */ public static function json_encode(array $value, int $options = 0, int $depth = 512):string { $output = \json_encode($value, $options, $depth); if ($output === false) throw new Exception("JSON output could not be generated"); return $output; } /** * Type-safe wrapper around strtotime(). * * @param string $datetime String to convert. * * @return integer */ public static function strtotime(string $datetime):int { $output = \strtotime($datetime); if ($output === false) throw new MDInvalidInputDate("Invalid input date {$datetime}."); return $output; } /** * Initializes a curl request with the given presets. * * @param string $url URL to query. * @param integer $timeout Timeout in milliseconds. * * @return resource */ public static function curl_init(string $url, int $timeout) { $curl = \curl_init(); \curl_setopt($curl, CURLOPT_URL, $url); \curl_setopt($curl, CURLOPT_HEADER, false); \curl_setopt($curl, CURLOPT_CONNECTTIMEOUT_MS, $timeout); //timeout in seconds \curl_setopt($curl, CURLOPT_TIMEOUT_MS, $timeout); //timeout in seconds \curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // \curl_setopt($curl, CURLOPT_COOKIESESSION, true); \curl_setopt($curl, CURLOPT_AUTOREFERER, true); \curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.2) Gecko/20100101 Firefox/10.0.2'); /* if (!file_exists(__DIR__ . '/../../curled.txt')) { touch (__DIR__ . '/../../curled.txt'); } file_put_contents(__DIR__ . '/../../curled.txt', $url . PHP_EOL, FILE_APPEND); */ return $curl; } /** * Wrapper for curling contents from the web. * * @param string $url URL to query. * @param integer $timeout Timeout in milliseconds. * * @return string */ public static function runCurl(string $url, int $timeout = 1200):string { $curl = self::curl_init($url, $timeout); \curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $result = \curl_exec($curl); // if ($err = curl_errno($curl)) echo $err; // if ($errmsg = curl_error($curl)) echo $errmsg; \curl_close($curl); if (\is_bool($result)) return ""; return $result; } /** * Wrapper for curling multiple pages from the web at ones and returning their contents. * Adapted from hushuilong's comment at https://www.php.net/manual/de/function.curl-multi-init.php#105252. * * @param array $urls URL to query. * @param integer $timeout Timeout in milliseconds. * * @return array */ public static function runCurlMulti(array $urls, int $timeout = 1200):array { if (!($mh = curl_multi_init())) { throw new exception("Failed to set up multi handle"); } $curl_array = []; foreach($urls as $i => $url) { $curl_array[$i] = self::curl_init($url, $timeout); curl_setopt($curl_array[$i], CURLOPT_RETURNTRANSFER, true); curl_multi_add_handle($mh, $curl_array[$i]); } $running = NULL; do { usleep(10000); curl_multi_exec($mh, $running); } while($running > 0); $res = []; foreach($urls as $i => $url) { $res[$i] = curl_multi_getcontent($curl_array[$i]); } foreach($urls as $i => $url){ curl_multi_remove_handle($mh, $curl_array[$i]); } curl_multi_close($mh); return $res; } /** * Function lang_getfrombrowser gets the browser language based on HTTP headers. * * @param array $allowed_languages Array containing all the languages for which * there are translations. * @param string $default_language Default language of the instance of MD. * @param string $lang_variable Currently set language variable. Optional. * @param boolean $strict_mode Whether to demand "de-de" (true) or "de" (false) Optional. * * @return string */ public static function lang_getfrombrowser(array $allowed_languages, string $default_language, string $lang_variable = "", bool $strict_mode = true):string { // $_SERVER['HTTP_ACCEPT_LANGUAGE'] verwenden, wenn keine Sprachvariable mitgegeben wurde if ($lang_variable === "") { if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) $lang_variable = $_SERVER['HTTP_ACCEPT_LANGUAGE']; } // wurde irgendwelche Information mitgeschickt? if (empty($lang_variable)) { // Nein? => Standardsprache zurückgeben return $default_language; } // Den Header auftrennen $accepted_languages = preg_split('/,\s*/', $lang_variable); if (!is_array($accepted_languages)) return $default_language; // Die Standardwerte einstellen $current_lang = $default_language; $current_q = 0; // Nun alle mitgegebenen Sprachen abarbeiten foreach ($accepted_languages as $accepted_language) { // Alle Infos über diese Sprache rausholen // phpcs:disable Generic.Strings.UnnecessaryStringConcat $res = \preg_match('/^([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', $accepted_language, $matches); // phpcs:enable // war die Syntax gültig? if (!$res) { // Nein? Dann ignorieren continue; } // Sprachcode holen und dann sofort in die Einzelteile trennen $lang_code = \explode('-', $matches[1]); // Wurde eine Qualität mitgegeben? if (isset($matches[2])) { // die Qualität benutzen $lang_quality = (float)$matches[2]; } else { // Kompabilitätsmodus: Qualität 1 annehmen $lang_quality = 1.0; } // Bis der Sprachcode leer ist... // phpcs:disable Squiz.PHP.DisallowSizeFunctionsInLoops while (!empty($lang_code)) { // phpcs:enable // mal sehen, ob der Sprachcode angeboten wird if (\in_array(\strtolower(\join('-', $lang_code)), $allowed_languages, true)) { // Qualität anschauen if ($lang_quality > $current_q) { // diese Sprache verwenden $current_lang = \strtolower(join('-', $lang_code)); $current_q = $lang_quality; // Hier die innere while-Schleife verlassen break; } } // Wenn wir im strengen Modus sind, die Sprache nicht versuchen zu minimalisieren if ($strict_mode) { // innere While-Schleife aufbrechen break; } // den rechtesten Teil des Sprachcodes abschneiden \array_pop($lang_code); } } // die gefundene Sprache zurückgeben return $current_lang; } /** * Type-safe wrapper around filesize, if output is false, throws an error. * * @param string $filename File name. * * @return integer */ public static function filesize(string $filename):int { $output = \filesize($filename); if ($output === false) { throw new Exception("Cannot get filesize of file {$filename}"); } return $output; } /** * Function human_filesize translates byte-level filesizes to human readable ones. * Thanks to Jeffrey Sambells http://jeffreysambells.com/2012/10/25/human-readable-filesize-php * * @param integer $bytes A file size, e.g. returned from filesize(). * @param integer $decimals Number of decimal digits to allow. * * @return string */ public static function human_filesize(int $bytes, int $decimals = 2):string { $size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB']; $factor = \floor((\strlen((string)$bytes) - 1) / 3); return \sprintf("%.{$decimals}f", $bytes / \pow(1024, $factor)) . $size[$factor]; } /** * Type-safe wrapper around openssl_random_pseudo_bytes. * * @param integer $length Length. * * @return string */ public static function openssl_random_pseudo_bytes(int $length):string { $output = \openssl_random_pseudo_bytes($length); if ($output === false) throw new Exception("Failed generating random pseudo bytes using openssl_random_pseudo_bytes"); return $output; } /** * Function for minimizing HTML, trimming each line. * * @param string $input Input. * * @return string */ public static function minimizeHTMLString(string $input):string { $input = \explode(PHP_EOL, $input); $output = ""; foreach ($input as $line) $output .= \trim($line) . PHP_EOL; return $output; } /** * This function cuts down a string and adds a period in case it's longer * than length to create a snippet. * * @param string $string Input text to cut down. * @param integer $length Length of the snippet to create. * * @return string */ public static function createTextSnippet(string $string, int $length = 180):string { if (\mb_strlen($string) > $length) { $string = \mb_substr($string, 0, $length); if (($lastWhitespace = \mb_strrpos($string, ' ')) !== false) { $string = \mb_substr($string, 0, $lastWhitespace); } $string .= '...'; } return $string; } /** * Wrapper around the finfo functions to get the mime content type of a file. * * @param string $filepath Expected file path. * * @return string */ public static function mime_content_type(string $filepath):string { if (!($finfo = \finfo_open(FILEINFO_MIME_TYPE))) { throw new Exception("Cannot open finfo context"); } if (!($mime_type = finfo_file($finfo, $filepath))) { throw new MDWrongFileType("Cannot get mime type of file: " . basename($filepath)); } \finfo_close($finfo); return $mime_type; } /** * Checks if a file exists, with one of the expected mime types. * * @param string $filepath File path of the file that needs to exist. * @param string[] $accepted_mimetype Mime type the file should have. * * @return void */ public static function ensure_file(string $filepath, array $accepted_mimetype = []):void { if (!\file_exists($filepath)) { throw new MDFileDoesNotExist("File " . basename($filepath) . " does not exist"); } // Check for mime type follows. If no check is to be done, ignore this. if (empty($accepted_mimetype)) { return; } $mime_type = self::mime_content_type($filepath); if (!\in_array($mime_type, $accepted_mimetype, true)) { throw new MDWrongFileType("Incorrect mime type of file " . \basename($filepath) . ". Mime type is " . \mime_content_type($filepath) . ", accepted any of ['" . \implode("', '", $accepted_mimetype) . "']"); } } /** * Wrapper around exec, to be used with editing functions. * Pipes STDERR to STDOUT and throws an Exception on any error. * * @param string $cmds Commands to run. * * @return void */ public static function exec_edit(string $cmds):void { $error = \shell_exec($cmds . ' 2>&1 1>/dev/null'); if (!empty($error)) { throw new \Exception('Shell error: ' . $error . PHP_EOL . PHP_EOL . 'Command was: ' . $cmds); } } /** * Wrapper around levenshtein(), that only compares the first 250 * characters (levenshtein can't handle more than 255). * * @param string $str1 First string. * @param string $str2 Second string. * * @return integer */ public static function levenshtein(string $str1, string $str2):int { if (\strlen($str1) > 250) $str1 = \substr($str1, 0, 250); if (\strlen($str2) > 250) $str2 = \substr($str2, 0, 250); return \levenshtein($str1, $str2); } /** * Converts a string to color codes. * * @param string $str Input string. * * @return string */ public static function string_to_color_code(string $str):string { $code = substr(dechex(crc32($str)), 0, 6); return $code; } }