596 lines
18 KiB
PHP
596 lines
18 KiB
PHP
<?PHP
|
|
/**
|
|
* Provides type-safe overrides of default PHP functions.
|
|
*/
|
|
declare(strict_types = 1);
|
|
|
|
/**
|
|
* Standard class providing overrides of default PHP functions as static
|
|
* functions.
|
|
*/
|
|
final class MD_STD {
|
|
/**
|
|
* Wrapper around file_get_contents, that provides catches errors on it and returns
|
|
* with type safety.
|
|
*
|
|
* @param string $filename Filepath of the file to read.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function file_get_contents(string $filename):string {
|
|
|
|
if (\substr($filename, 0, 4) !== 'http' && !\file_exists($filename)) {
|
|
throw new MDFileDoesNotExist("There is no file {$filename}");
|
|
}
|
|
|
|
$contents = \file_get_contents($filename);
|
|
|
|
if (\is_bool($contents)) {
|
|
throw new MDFileIsNotReadable("File {$filename} is not readable");
|
|
}
|
|
|
|
return $contents;
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the real path of a relative file path. Throws an error rather than
|
|
* returning the default false.
|
|
*
|
|
* @param string $path File path to convert.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function realpath(string $path):string {
|
|
|
|
$output = \realpath($path);
|
|
if (!\is_string($output)) throw new MDFileDoesNotExist("The file {$path} does not exist or is not readable.");
|
|
return $output;
|
|
|
|
}
|
|
|
|
/**
|
|
* Wrapper around mkdir, that throws an exception, if the folder cannot be
|
|
* created.
|
|
*
|
|
* @see https://www.php.net/manual/en/function.mkdir.php
|
|
*
|
|
* @param string $pathname The directory path.
|
|
* @param integer $mode Permissions.
|
|
* @param boolean $recursive Allows the creation of nested directories
|
|
* specified in the pathname.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function mkdir(string $pathname, int $mode = 0775, bool $recursive = false):void {
|
|
|
|
// One reason, to throw an error, is that the folder already exists.
|
|
if (\is_dir($pathname)) {
|
|
return;
|
|
}
|
|
if (\mkdir($pathname, $mode, $recursive) === false) {
|
|
throw new Exception("Failed to create directory: $pathname");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Wrapper around unlink, that throws an exception if the file failed to be
|
|
* removed.
|
|
*
|
|
* @see https://www.php.net/manual/en/function.unlink.php
|
|
*
|
|
* @param string $filename File path.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function unlink(string $filename):void {
|
|
|
|
if (\unlink($filename) === false) {
|
|
throw new Exception("Failed to delete: $filename");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Gets contents of a folder.
|
|
*
|
|
* @param string $filepath Directory path.
|
|
*
|
|
* @return array<string>
|
|
*/
|
|
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<mixed> $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<string> $urls URL to query.
|
|
* @param integer $timeout Timeout in milliseconds.
|
|
*
|
|
* @return array<string>
|
|
*/
|
|
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<string> $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;
|
|
|
|
}
|
|
}
|