MD_STD/MD_STD.php

305 lines
10 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.
*/
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;
}
/**
* 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}");
}
return \array_values(\array_diff($output, ['.', '..', '.git']));
}
/**
* 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;
}
/**
* 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 = 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_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_USERAGENT, 'md-bot/1.0');
$result = curl_exec($curl);
curl_close($curl);
if (is_bool($result)) return "";
return $result;
}
/**
* 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)) {
// 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;
}
/**
* 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];
}
}