=1099511627776) return number_format($size/1099511627776,$digit)."To"; elseif ($size>=1073741824) return number_format($size/1073741824,$digit)."Go"; else if ($size>=1048576) return number_format($size/1048576,$digit)."Mo"; else if ($size>=1024) return number_format($size/1024,$digit)."Ko"; else return $size."o"; } /* * Generic Data/value helpers */ function vardump($data) { ob_start(); var_dump($data); $data = ob_get_contents(); ob_end_clean(); return $data; } /** * Format a callable object for logging * @param string|array $callable The callable object * @return string The callable object string representation */ function format_callable($callable) { if (is_string($callable)) return $callable."()"; if (is_array($callable)) if (is_string($callable[0])) return $callable[0]."::".$callable[1]."()"; elseif (is_object($callable[0])) return get_class($callable[0])."->".$callable[1]."()"; else return "Unkown->".$callable[1]."()"; // @phpstan-ignore-next-line return vardump($callable); } /** * Check if given value is empty * @param mixed $value * @return bool */ function check_is_empty($value) { switch(gettype($value)) { case "boolean": case "integer": case "double": case "object": case "resource": return False; case "array": case "string": if ($value == "0") return false; return empty($value); case "NULL": return True; } return empty($value); } /** * Ensure the given value is an array and return an array with this value if not * @param mixed $value * @return array */ function ensure_is_array($value) { if (is_array($value)) return $value; if (check_is_empty($value)) return array(); return array($value); } /** * Get a specific configuration variable value * * @param mixed $value The value to cast * @param string $type The type of expected value. The configuration variable * value will be cast as this type. Could be : bool, int, * float or string. * @param bool $split If true, $type=='array' and $value is a string, split * the value by comma (optional, default: false) * @return mixed The cast value **/ function cast($value, $type, $split=false) { if (strpos($type, 'array_of_') === 0) { $type = substr($type, 9); $values = array(); foreach(ensure_is_array($value) as $key => $value) $values[$key] = cast($value, $type); return $values; } switch($type) { case 'bool': case 'boolean': return boolval($value); case 'int': case 'integer': return intval($value); case 'float': return floatval($value); case 'str': case 'string': return strval($value); case 'array': if ($split && is_string($value)) $value = preg_split('/ *, */', $value); return ensure_is_array($value); } return $value; } /* * Generic file/directory helpers */ /** * Dump file content to propose its download * @param string $file_path The file path * @param string|null $mime_type The file MIME type (optional, default: auto-detected) * @param int|null $max_age Max age in second of this file in browser cache (optional, * default: null=1h, set to False to disable Cache-Control header) * @return never */ function dump_file($file_path, $mime_type=null, $max_age=null) { if (is_file($file_path)) { if (is_null($mime_type)) { $detector = new ExtensionMimeTypeDetector(); $mime_type = $detector->detectMimeTypeFromFile($file_path); Log::trace('MIME type detected for "%s" is "%s"', $file_path, $mime_type); } header("Content-Type: $mime_type"); $last_modified_time = filemtime($file_path); $etag = md5_file($file_path); $max_age = is_null($max_age)?3600:$max_age; if ($max_age) header("Cache-Control: max-age=$max_age, must-revalidate"); header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT"); header("Etag: $etag"); if ( ( isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time ) || ( isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag ) ) { header("HTTP/1.1 304 Not Modified"); exit(); } header('Pragma: public'); header('Content-Length: ' . filesize($file_path)); readfile($file_path); exit(); } Url :: trigger_error_404(); } function delete_directory($dir, $recursive=true) { $files = array_diff(scandir($dir), array('.','..')); if ($recursive) { foreach ($files as $file) { if (is_dir("$dir/$file")) { if (!delete_directory("$dir/$file", true)) { Log :: error("delete_directory($dir) : Fail to delete sub-directory '$dir/$file'."); return false; } } else if (!unlink("$dir/$file")) { Log :: error("delete_directory($dir) : Fail to delete '$dir/$file'."); return false; } } } else if (!empty($files)) { Log :: error("delete_directory($dir) : Directory is not empty."); return false; } return rmdir($dir); } /* * Run external command helper */ /** * Run external command * * @param $command string|array The command. It's could be an array of the command with its * arguments. * @param $data_stdin string|null The command arguments (optional, default: null) * @param $escape_command_args boolean If true, the command will be escaped * (optional, default: true) * @param string|null $cwd The initial working dir for the command * (optional, default: null = use current PHP process working * directory) * * @return false|array An array of return code, stdout and stderr result or False in case of fatal * error **/ function run_external_command($command, $data_stdin=null, $escape_command_args=true, $cwd=null) { if (is_array($command)) $command = implode(' ', $command); if ($escape_command_args) $command = escapeshellcmd($command); Log :: debug("Run external command: '$command'"); $descriptorspec = array( 0 => array("pipe", "r"), // stdin 1 => array("pipe", "w"), // stdout 2 => array("pipe", "w"), // stderr ); $process = proc_open($command, $descriptorspec, $pipes, $cwd); if (!is_resource($process)) { Log :: error("Fail to run external command: '$command'"); return false; } if (!is_null($data_stdin)) { fwrite($pipes[0], $data_stdin); } fclose($pipes[0]); $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); $return_value = proc_close($process); $error = (!empty($stderr) || $return_value != 0); Log :: log( ($error?'ERROR':'DEBUG'), "External command ".($error?"error":"result").":\n". "\tCommand : $command\n". "\tReturn code: $return_value\n". "\tOutput:\n". "\t\t- Stdout :\n$stdout\n\n". "\t\t- Stderr :\n$stderr" ); return array($return_value, $stdout, $stderr); } /** * Check an AJAX request and trigger a fatal error on fail * * Check if session key is present and valid and set AJAX * mode. * * @param string|null $session_key string The current session key (optional) * * @return void **/ function check_ajax_request($session_key=null) { Url :: api_mode(true); if (Session :: check_key($session_key)) Tpl :: fatal_error('Invalid request'); if (Tpl :: debug_ajax()) Log :: debug("Ajax Request : ".vardump($_REQUEST)); } /** * Just mark message for translation * @param string $msg The message to translate * @return string The message without transformation */ function ___($msg) { return $msg; } /** * Format duration * @param int|float $value The duration to format * @param string|null $unit The unit of provide value (optional, default: 's') * @param string|null $precision Minimal precision to displayed (optional, default: 's') * @param string|null $separator The separator between time block (optional, default: null = one space) * @return string|false The formatted duration as string, or false in case of error */ function format_duration($value, $unit=null, $precision=null, $separator=null) { $units = array( ___('d') => 86400000000000, ___('h') => 3600000000000, ___('m') => 60000000000, ___('s') => 1000000000, ___('ms') => 1000000, ___('ns') => 1, ); if (!$unit) $unit = 's'; if (!array_key_exists($unit, $units)) return false; if (!$precision) $precision = 's'; if (!array_key_exists($precision, $units)) return false; if ($value == 0) return "0".I18n :: _($precision); $value = $value * $units[$unit]; $result = array(); foreach ($units as $unit => $factor) { if ($value >= $factor) { $vunit = floor($value/$factor); $value = $value - ($vunit * $factor); $result[] = $vunit._($unit); } if ($unit == $precision) break; } // 0 ? if (empty($result)) return I18n :: _('Less than 1%s', I18n :: _($precision)); return implode(is_null($separator)?' ':strval($separator), $result); } /** * Implode array's keys & values (ex: 'k1=v1, k2=v2, ...') * @param array $values Array to implode * @param boolean $quoted Set to false to disable values quotting (optional, default: true) * @param string $separator Values separator (opional, default: ", ") * @param string $kv_separator Key/value separator (optional, default: "=") * @return string Imploded array string */ function implode_with_keys($values, $quoted=true, $separator=', ', $kv_separator='=') { $result = []; $quoted = $quoted?'"':''; foreach ($values as $key => $value) $result[] = "$key$kv_separator$quoted$value$quoted"; return implode($separator, $result); } /** * Generate UUID * @return string UUID */ function generate_uuid() { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // 32 bits for "time_low" mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), // 16 bits for "time_mid" mt_rand( 0, 0xffff ), // 16 bits for "time_hi_and_version", // four most significant bits holds version number 4 mt_rand( 0, 0x0fff ) | 0x4000, // 16 bits, 8 bits for "clk_seq_hi_res", // 8 bits for "clk_seq_low", // two most significant bits holds zero and one for variant DCE1.1 mt_rand( 0, 0x3fff ) | 0x8000, // 48 bits for "node" mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) ); } /** * Get system timezone * @param bool $as_string Set to true to retrieve timezone name instead of a DateTimeZone object * @return ($as_string is true ? string : \DateTimeZone) System timezone */ function get_system_timezone($as_string=false) { $timezone = trim( $_SERVER['TZ'] ?? (file_get_contents('/etc/timezone') ?: file_get_contents('/etc/localtime')) ); return $as_string?$timezone:new \DateTimeZone($timezone); } # vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab