[ 'min_range' => 1, // Minimum number of an ID generated. 'max_range' => 4294967295 // Max value for MySQL's int data type ], ] ); if (!$input) { throw new MDpageParameterNotNumericException("Value is not numeric."); } return $input; } /** * Sanitizes and validates input integers to be either valid IDs or 0. * * @param mixed $input Input string. * * @return integer */ public static function sanitize_id_or_zero(mixed $input):int { if ($input === "") { return 0; } $input = \filter_var($input, \FILTER_VALIDATE_INT, [ 'options' => [ 'min_range' => 0, // Minimum number of an ID generated. 'max_range' => 4294967295 // Max value for MySQL's int data type ], ] ); if ($input === false) { throw new MDpageParameterNotNumericException("Value is not numeric."); } return $input; } /** * General string sanitization for all purposes. For use of inputs with MySQL's * MATCH AGAINST, use the dedicated sanitization function. * * @param mixed $input Input string. * * @return string */ public static function sanitize_text(mixed $input):string { $output = \filter_var($input, FILTER_UNSAFE_RAW); if ($output === false) { return ""; } $output = strip_tags($output); while (strpos($output, " ") !== false) { $output = str_replace(" ", " ", $output); } return trim($output); } /** * String sanitization for 3 o4 6 characters RGB color codes (sans the leading #). * * @param mixed $input Input string. * * @return string */ public static function sanitize_rgb_color(mixed $input):string { $output = \filter_var($input, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH); if ($output === false || (preg_match('/^[a-zA-Z0-9]{3}$/', $output) === false && preg_match('/^[a-zA-Z0-9]{6}$/', $output) === false) ) { throw new MDInvalidColorCode("Invalid color code provided: " . $output); } return $output; } /** * Valiates a list of entries, ensuring all returned values are valid IDs. * * @param array $inputs Input array. * * @return list */ public static function sanitize_id_array(array $inputs):array { $output = []; foreach ($inputs as $input) { $output[] = self::sanitize_id($input); } return $output; } /** * Retrieves HTTP input texts from GET or POST variables, whatever is provided. * If neither is given, returns a provided default. * * @param non-empty-string $var_name Variable name. * @param string $default Default value for the output. * @param array $allowed List of allowed values. Defaults to empty (all values allowed). * * @return string */ public static function get_http_input_text(string $var_name, string $default = "", array $allowed = []):string { if (isset($_GET[$var_name])) { $output = self::sanitize_text($_GET[$var_name]); } else if (isset($_POST[$var_name])) { $output = self::sanitize_text($_POST[$var_name]); } else { $output = self::sanitize_text($default); } if (!empty($allowed) and !\in_array($output, $allowed, true)) { throw new MDpageParameterNotFromListException("Parameter `{$var_name}` must be any of the allowed values: '" . implode('\', \'', $allowed) . "'"); } return $output; } /** * Retrieves HTTP input texts from POST variables. * If none is given, returns a provided default. * * @param non-empty-string $var_name Variable name. * @param string $default Default value for the output. * @param array $allowed List of allowed values. Defaults to empty (all values allowed). * * @return string */ public static function get_http_post_text(string $var_name, string $default = "", array $allowed = []):string { if (isset($_POST[$var_name])) { $output = self::sanitize_text($_POST[$var_name]); } else { $output = self::sanitize_text($default); } if (!empty($allowed) and !\in_array($output, $allowed, true)) { throw new MDpageParameterNotFromListException("Parameter `{$var_name}` must be any of the allowed values: '" . implode('\', \'', $allowed) . "'"); } return $output; } /** * Sanitizes and validates a URL. An empty string passes. * * @param mixed $input Input string. * * @return string */ public static function sanitize_url(mixed $input):string { if ($input === "") { return ""; } $output = \filter_var($input, FILTER_SANITIZE_URL); if (($output = \filter_var($output, FILTER_VALIDATE_URL)) === false) { throw new MDInvalidUrl("Invalid input URL"); } // Check for valid schemes if (MD_STD::startsWithAny($input, ['https://', 'http://', 'ftp://']) === false) { throw new MDInvalidUrl("Invalid input URL"); } return $output; } /** * Sanitizes and validates an e-mail address. An empty string passes. * * @param mixed $input Input string. * * @return string */ public static function sanitize_email(mixed $input):string { if ($input === "") { return ""; } $output = \filter_var($input, FILTER_SANITIZE_EMAIL); if (($output = \filter_var($output, FILTER_VALIDATE_EMAIL)) === false) { throw new MDInvalidEmail("Invalid input email address"); } return $output; } /** * Validates a password (minimum requirements: 8 characters, including * one number and one special char) and returns a list of errors, * if there are any. * * @param string $input Input string. * * @return array */ public static function validate_password(string $input):array { $errors = []; if (mb_strlen($input) < 8) { $errors[] = 'password_too_short'; } if (!(\preg_match('@[0-9]@', $input)) && !(\preg_match('@[^\w]@', $input))) { $errors[] = 'password_has_no_number_no_special_char'; } return $errors; } /** * Sanitizes and validates a phone number. An empty string passes. * * @param mixed $input Input string. * * @return string */ public static function validate_phone_number(mixed $input):string { if ($input === "") { return ""; } if (!preg_match("#^[()0-9/ +-]+$#", $input)) { throw new MDgenericInvalidInputsException("Invalid phone number entered."); } return $input; } /** * Sanitizes a string to a float. * * @param string $input Input string. * * @return float */ public static function sanitize_float(string $input):float { $output = \str_replace(",", ".", $input); if (($output = \filter_var($output, FILTER_VALIDATE_FLOAT)) === false) { throw new MDgenericInvalidInputsException("Input is readable as a floating point value"); } return $output; } /** * Validates a coordinate. * * @param string|integer $input Input string. * * @return float */ public static function validate_longitude(string|int $input):float { if (is_string($input)) $output = self::sanitize_float($input); else $output = $input; if ($output < -180 || $output > 180) { throw new MDCoordinateOutOfRange("Longitude out of range"); } return $output; } /** * Validates a coordinate. * * @param string|integer $input Input string. * * @return float */ public static function validate_latitude(string|int $input):float { if (is_string($input)) $output = self::sanitize_float($input); else $output = $input; if ($output < -90 || $output > 90) { throw new MDCoordinateOutOfRange("Latitude out of range"); } return $output; } /** * Validates ISBNs. Empty strings are accepted as well. * * @param string $input Input string. * * @return string */ public static function validate_isbn(string $input):string { if ($input === "") { return ""; } // Remove hyphens $input = trim(strtr($input, ["-" => "", "–" => ""])); // ISBN 10 if (\mb_strlen($input) === 10) { if (\preg_match('/\d{9}[0-9xX]/i', $input)) { return $input; } } // ISBN 13 if (\mb_strlen($input) === 13) { if (\preg_match('/\d{13}/i', $input)) { return $input; } } throw new MDgenericInvalidInputsException("ISBNs must be either 10 or 13 characters long."); } /** * Returns an UTF8 version of a string. * * @param string $input Input string. * * @return string */ public static function ensureStringIsUtf8(string $input):string { // If the input is valid UTF8 from the start, it is simply returned in its // original form. if (\mb_check_encoding($input, 'UTF-8')) { return $input; } // To detect and convert the encoding for non-UTF8 strings, the list of // encodings known to PHP's mbstring functions is checked against the input string. // If any encoding matches the string, it will be converted to UTF8 accordingly. $suitableEncodings = []; $encodings = \mb_list_encodings(); foreach ($encodings as $encoding) { if (\mb_detect_encoding($input, $encoding, true) !== false) { $suitableEncodings[] = $encoding; } } // If ISO-8859-1 is in the list of suitable encodings, try to convert with that. if (\in_array('ISO-8859-1', $suitableEncodings, true)) { if (($converted = \iconv('ISO-8859-1', "UTF-8//TRANSLIT", $input)) !== false) { return $converted; } } // If a conversion from ISO-8859-1 doesn't work, just take any of the other ones. $suitableEncodings = \array_reverse($suitableEncodings); foreach ($suitableEncodings as $encoding) { if (($converted = \iconv($encoding, "UTF-8//TRANSLIT", $input)) !== false) { return $converted; } } /* if (count($suitableEncodings) === 1) { return mb_convert_encoding($input, 'UTF-8', ); } */ return $input; } /** * Wrapper around move_uploaded_file that throws errors in case the upload failed * for an identifiable reason. * * @param non-empty-string $filename Name of the file to upload. * @param non-empty-string $destination Destination to move the file to. * @param array $mime_types Optional array of acceptable mime types. If this is * not empty, the file will be checked for having one * of the given mime types. If it does not, an error * will be thrown. * * @return boolean */ public static function move_uploaded_file(string $filename, string $destination, array $mime_types = []):bool { MD_STD::ensure_file($filename, $mime_types); if (empty($destDir = dirname($destination))) { return false; } MD_STD::check_is_writable($destDir); if (!(\move_uploaded_file($filename, $destination))) { return false; } return true; } }