4992 lines
244 KiB
PHP
4992 lines
244 KiB
PHP
<?php
|
||
/**
|
||
* Observium
|
||
*
|
||
* This file is part of Observium.
|
||
*
|
||
* @package observium
|
||
* @subpackage functions
|
||
* @copyright (C) Adam Armstrong
|
||
*
|
||
*/
|
||
|
||
// Common Functions
|
||
/// FIXME. There should be functions that use only standard php (and self) functions.
|
||
|
||
// FIXME. Function temporary placed here, since cache_* functions currently included in WUI only.
|
||
// MOVEME includes/cache.inc.php
|
||
|
||
/**
|
||
* Add clear cache attrib, this will request for clearing cache in next request.
|
||
*
|
||
* @param string $target Clear cache target: wui or cli (default if wui)
|
||
*/
|
||
function set_cache_clear($target = 'wui')
|
||
{
|
||
if (OBS_DEBUG || (defined('OBS_CACHE_DEBUG') && OBS_CACHE_DEBUG)) {
|
||
print_error('<span class="text-warning">CACHE CLEAR SET.</span> Cache clear set.');
|
||
}
|
||
if (!$GLOBALS['config']['cache']['enable']) {
|
||
// Cache not enabled
|
||
return;
|
||
}
|
||
|
||
switch (strtolower($target)) {
|
||
case 'cli':
|
||
// Add clear CLI cache attrib. Currently not used
|
||
set_obs_attrib('cache_cli_clear', get_request_id());
|
||
break;
|
||
default:
|
||
// Add clear WUI cache attrib
|
||
set_obs_attrib('cache_wui_clear', get_request_id());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns an array of predefined timestamp formats along with their names and example values.
|
||
* The array also includes a 'Current' timestamp format based on the global `$config['timestamp_format']`.
|
||
*
|
||
* @return array An associative array containing timestamp formats as keys and their properties (name and subtext) as values.
|
||
* @global array $config The global configuration array containing the 'timestamp_format' key.
|
||
*
|
||
*/
|
||
function get_params_timestamp()
|
||
{
|
||
global $config;
|
||
|
||
$params = [
|
||
'Y-m-d H:i:s' => ['name' => 'Default'],
|
||
'Y-m-d H:i:s T' => ['name' => 'Default with TZ'],
|
||
'd/m/Y h:i:s A' => ['name' => 'GB'],
|
||
'd/m/Y h:i:s A T' => ['name' => 'GB with TZ'],
|
||
'n/j/Y g:i:s A' => ['name' => 'US'],
|
||
'j F Y, g:i:s A' => ['name' => 'US Full'],
|
||
'n/j/Y g:i:s A T' => ['name' => 'US with TZ'],
|
||
'd.m.Y H:i:s' => ['name' => 'EU'],
|
||
'd.m.Y H:i:s T' => ['name' => 'EU with TZ'],
|
||
];
|
||
foreach ($params as $key => $param) {
|
||
$params[$key]['subtext'] = date($key);
|
||
}
|
||
|
||
if (!isset($params[$config['timestamp_format']])) {
|
||
$params[$config['timestamp_format']] = ['name' => 'Current (' . date($config['timestamp_format']) . ')'];
|
||
}
|
||
|
||
return $params;
|
||
|
||
}
|
||
|
||
/**
|
||
* Generate and store Unique ID for current system. Store in DB at first run.
|
||
* IDs is RFC 4122 version 4 (without dashes, varchar(32)), i.e. c39b2386c4e8487fad4a87cd367b279d
|
||
*
|
||
* @return string Unique system ID
|
||
* @throws Exception
|
||
*/
|
||
function get_unique_id()
|
||
{
|
||
if (!defined('OBS_UNIQUE_ID')) {
|
||
$unique_id = get_obs_attrib('unique_id');
|
||
|
||
if (safe_empty($unique_id)) {
|
||
// Generate a version 4 (random) UUID object
|
||
$uuid4 = Ramsey\Uuid\Uuid::uuid4();
|
||
//$unique_id = $uuid4->toString(); // i.e. c39b2386-c4e8-487f-ad4a-87cd367b279d
|
||
$unique_id = $uuid4->getHex(); // i.e. c39b2386c4e8487fad4a87cd367b279d
|
||
if (PHP_VERSION_ID >= 80000) {
|
||
$unique_id = $unique_id->toString();
|
||
}
|
||
if (!OBS_DB_SKIP) {
|
||
dbInsert([ 'attrib_type' => 'unique_id', 'attrib_value' => $unique_id ], 'observium_attribs');
|
||
}
|
||
}
|
||
define('OBS_UNIQUE_ID', $unique_id);
|
||
}
|
||
|
||
return OBS_UNIQUE_ID;
|
||
}
|
||
|
||
/**
|
||
* Generate and store Unique Request ID for current script/page.
|
||
* ID unique between 2 different requests or page loads
|
||
* IDs is RFC 4122 version 4, i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a
|
||
*
|
||
* @return string Unique Request ID
|
||
* @throws Exception
|
||
*/
|
||
function get_request_id()
|
||
{
|
||
if (!defined('OBS_REQUEST_ID')) {
|
||
// Generate a version 4 (random) UUID object
|
||
$uuid4 = Ramsey\Uuid\Uuid::uuid4();
|
||
$request_id = $uuid4->toString(); // i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a
|
||
define('OBS_REQUEST_ID', $request_id);
|
||
}
|
||
|
||
return OBS_REQUEST_ID;
|
||
}
|
||
|
||
/**
|
||
* Set new DB Schema version
|
||
*
|
||
* @param integer $db_rev New DB schema revision
|
||
* @param boolean $schema_insert Update (by default) or insert by first install db schema
|
||
*
|
||
* @return boolean Status of DB schema update
|
||
*/
|
||
function set_db_version($db_rev, $schema_insert = FALSE)
|
||
{
|
||
if ($db_rev >= 211) { // Do not remove this check, since before this revision observium_attribs table not exist!
|
||
$status = set_obs_attrib('dbSchema', $db_rev);
|
||
} else {
|
||
if ($schema_insert) {
|
||
$status = dbInsert(['version' => $db_rev], 'dbSchema');
|
||
if ($status !== FALSE) {
|
||
$status = TRUE;
|
||
} // Note dbInsert return IDs if exist or 0 for not indexed tables
|
||
} else {
|
||
$status = dbUpdate(['version' => $db_rev], 'dbSchema');
|
||
}
|
||
}
|
||
|
||
if ($status) {
|
||
$GLOBALS['cache']['db_version'] = $db_rev; // Cache new db version
|
||
}
|
||
|
||
return $status;
|
||
}
|
||
|
||
/**
|
||
* Get current DB Schema version
|
||
*
|
||
* @return string DB schema version
|
||
*/
|
||
// TESTME needs unit testing
|
||
function get_db_version($refresh = FALSE)
|
||
{
|
||
if (!isset($GLOBALS['cache']['db_version']) || $refresh) {
|
||
if ($refresh) {
|
||
reset_attribs_cache();
|
||
}
|
||
$db_rev = @get_obs_attrib('dbSchema');
|
||
if (!$db_rev) {
|
||
$db_rev = 0;
|
||
}
|
||
$db_rev = (int)$db_rev;
|
||
if ($db_rev > 0) {
|
||
$GLOBALS['cache']['db_version'] = $db_rev;
|
||
} else {
|
||
// Do not cache zero value
|
||
return $db_rev;
|
||
}
|
||
}
|
||
return $GLOBALS['cache']['db_version'];
|
||
}
|
||
|
||
/**
|
||
* Get current DB Size
|
||
*
|
||
* @return string DB size in bytes
|
||
*/
|
||
// TESTME needs unit testing
|
||
function get_db_size()
|
||
{
|
||
return dbFetchCell('SELECT SUM(`data_length` + `index_length`) AS `size` FROM `information_schema`.`tables` WHERE `table_schema` = ?;', [$GLOBALS['config']['db_name']]);
|
||
}
|
||
|
||
/**
|
||
* Get unique local id.
|
||
* Need to identify poller system.
|
||
*
|
||
* @return string
|
||
*/
|
||
function get_local_id() {
|
||
// http://0pointer.de/blog/projects/ids.html
|
||
switch (PHP_OS) {
|
||
case 'Linux':
|
||
// Note. system-uuid is good, but available only for root
|
||
if (is_file('/etc/machine-id')) {
|
||
// 1d56dd4b3c334a20bff1fc4878b9e1ee
|
||
return trim(file_get_contents('/etc/machine-id'));
|
||
}
|
||
if (is_file('/var/lib/dbus/machine-id')) {
|
||
return trim(file_get_contents('/var/lib/dbus/machine-id'));
|
||
}
|
||
print_debug("DEBUG: Machine-ID not found on Linux host.");
|
||
break;
|
||
case 'FreeBSD':
|
||
// kern.hostuuid: fe38be37-5d64-11eb-b896-6470021048e6
|
||
if ($id = explode(': ', external_exec('sysctl kern.hostuuid'))[1]) {
|
||
return str_replace('-', '', trim($id));
|
||
}
|
||
break;
|
||
case 'Darwin':
|
||
// $ system_profiler SPHardwareDataType
|
||
// Hardware:
|
||
//
|
||
// Hardware Overview:
|
||
//
|
||
// Model Name: iMac
|
||
// Model Identifier: iMac21,1
|
||
// Chip: Apple M1
|
||
// Total Number of Cores: 8 (4 performance and 4 efficiency)
|
||
// Memory: 16 GB
|
||
// System Firmware Version: 7429.41.5
|
||
// OS Loader Version: 6723.140.2
|
||
// Serial Number (system): XXXX402RXXXX
|
||
// Hardware UUID: 360CXXXX-XXXX-XXXX-8C34-D2EA2266XXXX
|
||
// Provisioning UDID: 0000XXXX-0009193C36FB001E
|
||
// Activation Lock Status: Disabled
|
||
|
||
foreach (explode("\n", external_exec('system_profiler SPHardwareDataType')) as $line) {
|
||
if (str_contains($line, 'UUID:') &&
|
||
$id = explode(': ', $line)[1]) {
|
||
return str_replace('-', '', strtolower(trim($id)));
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
// Derp way, need to store lock file, available only for current host (not in db!)..
|
||
$id_file = $GLOBALS['config']['log_dir'] . '/.machine-id';
|
||
if (is_file($id_file)) {
|
||
return file_get_contents($id_file);
|
||
}
|
||
|
||
try {
|
||
$uuid4 = Ramsey\Uuid\Uuid::uuid4();
|
||
} catch (Exception $e) {
|
||
return '';
|
||
}
|
||
|
||
$unique_id = $uuid4->getHex(); // i.e. c39b2386c4e8487fad4a87cd367b279d
|
||
if (file_put_contents($id_file, $unique_id)) {
|
||
// return generated id, only when lock file is writable, for prevent logs spamming
|
||
return $unique_id;
|
||
}
|
||
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* Get local hostname
|
||
*
|
||
* @return string FQDN local hostname
|
||
*/
|
||
function get_localhost() {
|
||
global $cache;
|
||
|
||
if (!isset($cache['localhost'])) {
|
||
$cache['localhost'] = php_uname('n');
|
||
if (!str_contains($cache['localhost'], '.')) {
|
||
// try use hostname -f for get FQDN hostname
|
||
$localhost_t = external_exec('/bin/hostname -f');
|
||
if (str_contains($localhost_t, '.')) {
|
||
$cache['localhost'] = $localhost_t;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $cache['localhost'];
|
||
}
|
||
|
||
/**
|
||
* Get owner of current process
|
||
*
|
||
* @return string Username
|
||
*/
|
||
function get_localuser()
|
||
{
|
||
if ($_SERVER['USER']) {
|
||
return $_SERVER['USER'];
|
||
}
|
||
if (function_exists('posix_geteuid')) {
|
||
return posix_getpwuid(posix_geteuid())['name'];
|
||
}
|
||
|
||
return external_exec('whoami');
|
||
}
|
||
|
||
/**
|
||
* Calculates the total size of a directory.
|
||
*
|
||
* @param string $dir The path to the directory.
|
||
*
|
||
* @return int|null The total size of the directory in bytes or null if the directory does not exist or is not readable.
|
||
*/
|
||
function get_dir_size($dir)
|
||
{
|
||
// Check if the directory exists and is readable
|
||
if (!is_dir($dir) || !is_readable($dir)) {
|
||
return NULL;
|
||
}
|
||
|
||
$size = 0;
|
||
|
||
foreach (get_recursive_directory_iterator($dir) as $path => $file) {
|
||
// Check if the file is not a link to avoid potential infinite loop
|
||
if (!$file -> isLink()) {
|
||
$size += $file -> getSize();
|
||
}
|
||
}
|
||
|
||
return $size;
|
||
}
|
||
|
||
/**
|
||
* Recursively delete dir.
|
||
*
|
||
* @param string $dir
|
||
*
|
||
* @return bool
|
||
*/
|
||
function delete_dir($dir)
|
||
{
|
||
if (!file_exists($dir)) {
|
||
print_debug("Dir '$dir' not exist.");
|
||
return TRUE;
|
||
}
|
||
|
||
$dirs = [];
|
||
$files = [];
|
||
// Delete files inside dir
|
||
foreach (get_recursive_directory_iterator($dir) as $path => $file) {
|
||
$files[] = $path;
|
||
if ($dir !== $file -> getPath()) {
|
||
$dirs[] = $file -> getPath();
|
||
}
|
||
|
||
if (!unlink($path)) {
|
||
// File not deleted
|
||
return FALSE;
|
||
}
|
||
/*
|
||
print_vars($file->getFilename());
|
||
echo PHP_EOL;
|
||
print_vars($file->getExtension());
|
||
echo PHP_EOL;
|
||
print_vars($file->getPath());
|
||
echo PHP_EOL;
|
||
*/
|
||
}
|
||
if (count($files)) {
|
||
print_debug("Deleted files:");
|
||
print_debug_vars($files);
|
||
}
|
||
|
||
// Now delete sub-dirs
|
||
foreach ($dirs as $d) {
|
||
if (!rmdir($d)) {
|
||
// Sub dir not deleted
|
||
return FALSE;
|
||
}
|
||
}
|
||
$dirs[] = $dir;
|
||
print_debug("Deleted dirs:");
|
||
print_debug_vars($dirs);
|
||
|
||
return rmdir($dir);
|
||
}
|
||
|
||
function percent($value, $max, $precision = 0)
|
||
{
|
||
|
||
$percent = float_div($value, $max) * 100;
|
||
|
||
if (is_numeric($precision)) {
|
||
return round($percent, $precision);
|
||
}
|
||
return $percent;
|
||
}
|
||
|
||
/**
|
||
* Percent Class
|
||
*
|
||
* Given a percentage value return a class name (for CSS).
|
||
*
|
||
* @param int|string $percent
|
||
*
|
||
* @return string
|
||
*/
|
||
function percent_class($percent)
|
||
{
|
||
if ($percent < "25") {
|
||
$class = "info";
|
||
} elseif ($percent < "50") {
|
||
$class = "";
|
||
} elseif ($percent < "75") {
|
||
$class = "success";
|
||
} elseif ($percent < "90") {
|
||
$class = "warning";
|
||
} else {
|
||
$class = "danger";
|
||
}
|
||
|
||
return $class;
|
||
}
|
||
|
||
/**
|
||
* Percent Colour
|
||
*
|
||
* This function returns a colour based on a 0-100 value
|
||
* It scales from green to red from 0-100 as default.
|
||
*
|
||
* @param integer $value
|
||
* @param integer $brightness
|
||
* @param integer $max
|
||
* @param integer $min
|
||
* @param string $thirdColourHex
|
||
*
|
||
* @return string
|
||
*/
|
||
function percent_colour($value, $brightness = 128, $max = 100, $min = 0, $thirdColourHex = '00')
|
||
{
|
||
if ($value > $max) {
|
||
$value = $max;
|
||
}
|
||
if ($value < $min) {
|
||
$value = $min;
|
||
}
|
||
|
||
// Calculate first and second colour (Inverse relationship)
|
||
$div = float_div($value, $max);
|
||
$first = (1 - $div) * $brightness;
|
||
$second = $div * $brightness;
|
||
|
||
// Find the influence of the middle Colour (yellow if 1st and 2nd are red and green)
|
||
$diff = abs($first - $second);
|
||
$influence = ($brightness - $diff) / 2;
|
||
$first = (int)($first + $influence);
|
||
$second = (int)($second + $influence);
|
||
|
||
// Convert to HEX, format and return
|
||
$firstHex = str_pad(dechex($first), 2, 0, STR_PAD_LEFT);
|
||
$secondHex = str_pad(dechex($second), 2, 0, STR_PAD_LEFT);
|
||
|
||
return '#' . $secondHex . $firstHex . $thirdColourHex;
|
||
|
||
// alternatives:
|
||
// return $thirdColourHex . $firstHex . $secondHex;
|
||
// return $firstHex . $thirdColourHex . $secondHex;
|
||
}
|
||
|
||
/**
|
||
* Convert sequence of numbers in an array to range of numbers.
|
||
* Example:
|
||
* array(1,2,3,4,5,6,7,8,9,10) -> '1-10'
|
||
* array(1,2,3,5,7,9,10,11,12,14) -> '1-3,5,7,9-12,14'
|
||
*
|
||
* @param array $arr Array with sequence of numbers
|
||
* @param string $separator Use this separator for list
|
||
* @param bool $sort Sort input array or not
|
||
*
|
||
* @return string
|
||
*/
|
||
function range_to_list($arr, $separator = ',', $sort = TRUE)
|
||
{
|
||
if (!is_array($arr)) {
|
||
return '';
|
||
}
|
||
|
||
if ($sort) {
|
||
sort($arr, SORT_NUMERIC);
|
||
}
|
||
|
||
$ranges = [];
|
||
$count = count($arr);
|
||
for ($i = 0; $i < $count; $i++) {
|
||
$rstart = $arr[$i];
|
||
$rend = $rstart;
|
||
while (isset($arr[$i + 1]) && ((int)$arr[$i + 1] - (int)$arr[$i]) === 1) {
|
||
$rend = $arr[$i + 1];
|
||
$i++;
|
||
}
|
||
if (is_numeric($rstart) && is_numeric($rend)) {
|
||
$ranges[] = ($rstart == $rend) ? $rstart : $rstart . '-' . $rend;
|
||
} else {
|
||
return ''; // Not numeric value(s)
|
||
}
|
||
}
|
||
|
||
return implode($separator, $ranges);
|
||
}
|
||
|
||
// '1-3,5,7,9-12,14' -> array(1,2,3,5,7,9,10,11,12,14)
|
||
function list_to_range($str, $separator = ',', $sort = TRUE)
|
||
{
|
||
if (!is_string($str)) {
|
||
return $str;
|
||
}
|
||
|
||
// Clean spaces while separator not with spaces
|
||
if (!str_contains($separator, ' ')) {
|
||
$str = str_replace(' ', '', $str);
|
||
}
|
||
|
||
$arr = [];
|
||
foreach (explode($separator, trim($str)) as $list) {
|
||
$negative = FALSE;
|
||
if ($list[0] === '-') {
|
||
$negative = TRUE;
|
||
$list = substr($list, 1);
|
||
}
|
||
if (str_contains($list, '-')) {
|
||
[$min, $max] = explode('-', $list, 2);
|
||
if (!is_numeric($min) || !is_numeric($max)) {
|
||
continue;
|
||
}
|
||
if ($negative) {
|
||
$min = '-' . $min;
|
||
}
|
||
if ($min > $max) {
|
||
// ie 10-3
|
||
[$min, $max] = [$max, $min];
|
||
} elseif ($min == $max) {
|
||
// ie 1-1
|
||
$arr[] = (int)$min;
|
||
continue;
|
||
}
|
||
for ($i = $min; $i <= $max; $i++) {
|
||
$arr[] = (int)$i;
|
||
}
|
||
} elseif (is_numeric($list)) {
|
||
$arr[] = $negative ? (int)('-' . $list) : (int)$list;
|
||
}
|
||
}
|
||
|
||
if ($sort) {
|
||
sort($arr, SORT_NUMERIC);
|
||
}
|
||
|
||
return $arr;
|
||
}
|
||
|
||
/**
|
||
* Write a line to the specified logfile (or default log if not specified).
|
||
* We open & close for every line, somewhat lower performance but this means multiple concurrent processes could write to the file.
|
||
* Now marking process and pid, if things are running simultaneously you can still see what's coming from where.
|
||
*
|
||
* @param string $filename
|
||
* @param string $string
|
||
*
|
||
* @return false|void
|
||
*/
|
||
function logfile($filename, $string = NULL) {
|
||
global $config;
|
||
|
||
if (defined('__PHPUNIT_PHAR__')) {
|
||
print_debug("Skip logging to '$filename' when run phpunit tests.");
|
||
return FALSE;
|
||
}
|
||
|
||
// Use default logfile if none specified
|
||
if (safe_empty($string)) {
|
||
$string = $filename;
|
||
$filename = $config['log_file'];
|
||
}
|
||
|
||
// Place logfile in log directory if no path specified
|
||
if (basename($filename) === $filename) {
|
||
$filename = $config['log_dir'] . '/' . $filename;
|
||
}
|
||
// Create logfile if not exist
|
||
if (is_file($filename)) {
|
||
if (!is_writable($filename)) {
|
||
print_debug("Log file '$filename' is not writeable, check file permissions.");
|
||
return FALSE;
|
||
}
|
||
$fd = fopen($filename, 'ab');
|
||
} else {
|
||
$fd = fopen($filename, 'wb');
|
||
// Check writable file (only after creation for speedup)
|
||
if (!is_writable($filename)) {
|
||
print_debug("Log file '$filename' is not writeable or not created.");
|
||
if ($fd !== FALSE) { fclose($fd); }
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
$string = '[' . date('Y/m/d H:i:s O') . '] ' . OBS_SCRIPT_NAME . '(' . getmypid() . '): ' . trim($string) . PHP_EOL;
|
||
fwrite($fd, $string);
|
||
fclose($fd);
|
||
}
|
||
|
||
/**
|
||
* Get used system versions
|
||
*
|
||
* @param string|null $program
|
||
*
|
||
* @return array|string
|
||
*/
|
||
function get_versions($program = NULL)
|
||
{
|
||
$return_version = !empty($program); // return only version string for program
|
||
|
||
if (isset($GLOBALS['cache']['versions'])) {
|
||
// Already cached
|
||
if ($return_version) {
|
||
$key = strtolower($program) . '_version';
|
||
if (isset($GLOBALS['cache']['versions'][$key])) {
|
||
return $GLOBALS['cache']['versions'][$key];
|
||
}
|
||
// else directly request version
|
||
} else {
|
||
return $GLOBALS['cache']['versions'];
|
||
}
|
||
}
|
||
$versions = []; // Init
|
||
if ($return_version) {
|
||
// Return only one not cached version
|
||
$programs = (array)$program;
|
||
} else {
|
||
// return array with all versions
|
||
$programs = ['os', 'php', 'python', 'mysql', 'snmp', 'rrdtool', 'fping', 'http', 'curl'];
|
||
}
|
||
|
||
foreach ($programs as $entry) {
|
||
switch ($entry) {
|
||
case 'os':
|
||
// Local system OS version
|
||
if (is_executable($GLOBALS['config']['install_dir'] . '/scripts/distro')) {
|
||
$os = explode('|', external_exec($GLOBALS['config']['install_dir'] . '/scripts/distro'), 6);
|
||
$versions['os_system'] = $os[0];
|
||
$versions['os_version'] = $os[1];
|
||
$versions['os_arch'] = $os[2];
|
||
$versions['os_distro'] = $os[3];
|
||
$versions['os_distro_version'] = $os[4];
|
||
$versions['os_virt'] = $os[5];
|
||
$versions['os_text'] = $os[0] . ' ' . $os[1] . ' [' . $os[2] . '] (' . $os[3] . ' ' . $os[4] . ')';
|
||
}
|
||
if ($return_version) {
|
||
return (string)$versions['os_version'];
|
||
}
|
||
break;
|
||
|
||
case 'php':
|
||
// PHP
|
||
$php_version = PHP_VERSION;
|
||
$versions['php_full'] = $php_version;
|
||
$versions['php_version'] = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
|
||
if ($return_version) {
|
||
return $versions['php_version'];
|
||
}
|
||
|
||
$versions['php_old'] = version_compare($versions['php_version'], OBS_MIN_PHP_VERSION, '<');
|
||
// PHP OPcache
|
||
$versions['php_opcache'] = FALSE;
|
||
if (extension_loaded('Zend OPcache')) {
|
||
$opcache = ini_get('opcache.enable');
|
||
$php_version .= ' (OPcache: ';
|
||
if ($opcache && is_cli() && ini_get('opcache.enable_cli')) { // CLI
|
||
$php_version .= 'ENABLED)';
|
||
$versions['php_opcache'] = 'ENABLED';
|
||
} elseif ($opcache && !is_cli()) { // WUI
|
||
$php_version .= 'ENABLED)';
|
||
$versions['php_opcache'] = 'ENABLED';
|
||
} else {
|
||
$php_version .= 'DISABLED)';
|
||
$versions['php_opcache'] = 'DISABLED';
|
||
}
|
||
}
|
||
$versions['php_text'] = $php_version;
|
||
|
||
// PHP memory_limit
|
||
$php_memory_limit = unit_string_to_numeric(ini_get('memory_limit'));
|
||
$versions['php_memory_limit'] = $php_memory_limit;
|
||
if ($php_memory_limit < 0) {
|
||
$versions['php_memory_limit_text'] = 'Unlimited';
|
||
} else {
|
||
$versions['php_memory_limit_text'] = format_bytes($php_memory_limit);
|
||
}
|
||
break;
|
||
|
||
case 'python':
|
||
/** Python
|
||
* I.e.:
|
||
* python_version = 2.7.5
|
||
* python_text = 2.7.5
|
||
*/
|
||
$python_version = str_replace('Python ', '', external_exec('/usr/bin/env python --version 2>&1'));
|
||
$python3_default = TRUE;
|
||
if (str_contains($python_version, 'No such file or directory')) {
|
||
// /usr/bin/env: 'python': No such file or directory
|
||
$python_version = str_replace('Python ', '', external_exec('/usr/bin/env python3 --version 2>&1'));
|
||
if (str_contains($python_version, 'No such file or directory')) {
|
||
// /usr/bin/env: 'python': No such file or directory
|
||
$python_version = 'Not found';
|
||
} else {
|
||
// Append info about no default python executable
|
||
$python3_default = FALSE;
|
||
}
|
||
} elseif (str_starts_with($python_version, '2.')) {
|
||
// python3 not symlynk to python
|
||
$python3 = str_replace('Python ', '', external_exec('/usr/bin/env python3 --version 2>&1'));
|
||
if (!str_contains($python3, 'No such file or directory')) {
|
||
// /usr/bin/env: 'python': No such file or directory
|
||
$python_version = $python3;
|
||
// Append info about no default python executable
|
||
$python3_default = FALSE;
|
||
}
|
||
}
|
||
$versions['python_version'] = $python_version;
|
||
if ($return_version) {
|
||
return $versions['python_version'];
|
||
}
|
||
if (str_starts_with($python_version, '2.')) {
|
||
$versions['python_old'] = version_compare($versions['python_version'], OBS_MIN_PYTHON2_VERSION, '<');
|
||
} else {
|
||
$versions['python_old'] = version_compare($versions['python_version'], OBS_MIN_PYTHON3_VERSION, '<');
|
||
}
|
||
$versions['python_text'] = $python_version;
|
||
if (!$python3_default) {
|
||
$versions['python_text'] .= ' (python3 not used as default python)';
|
||
}
|
||
break;
|
||
|
||
case 'mysql':
|
||
case 'mariadb':
|
||
if (defined('OBS_DB_SKIP') && OBS_DB_SKIP) {
|
||
break;
|
||
}
|
||
/** MySQL
|
||
* I.e.:
|
||
* mysql_client = 5.0.12-dev
|
||
* mysql_full = 10.3.23-MariaDB-log
|
||
* mysql_name = MariaDB
|
||
* mysql_version = 10.3.23
|
||
* mysql_text = 10.3.23-MariaDB-log (extension: mysqli 5.0.12-dev)
|
||
*/
|
||
$mysql_client = dbClientInfo();
|
||
if (preg_match('/(\d+\.[\w\.\-]+)/', $mysql_client, $matches)) {
|
||
$mysql_client = $matches[1];
|
||
}
|
||
$versions['mysql_client'] = $mysql_client;
|
||
$mysql_version = dbFetchCell("SELECT version();");
|
||
$versions['mysql_full'] = $mysql_version;
|
||
$versions['mysql_version'] = explode('-', $mysql_version)[0];
|
||
|
||
// Define DB NAME for later use
|
||
$versions['mysql_name'] = str_contains(strtolower($mysql_version), 'maria') ? 'MariaDB' : 'MySQL';
|
||
if (!defined('OBS_DB_NAME')) {
|
||
define('OBS_DB_NAME', $versions['mysql_name']);
|
||
}
|
||
|
||
if ($return_version) {
|
||
return $versions['mysql_version'];
|
||
}
|
||
|
||
if ($versions['mysql_name'] === 'MariaDB') {
|
||
$versions['mysql_old'] = version_compare($versions['mysql_version'], OBS_MIN_MARIADB_VERSION, '<');
|
||
} else {
|
||
$versions['mysql_old'] = version_compare($versions['mysql_version'], OBS_MIN_MYSQL_VERSION, '<');
|
||
}
|
||
$mysql_version .= ' (extension: ' . OBS_DB_EXTENSION . ' ' . $mysql_client . ')';
|
||
$versions['mysql_text'] = $mysql_version;
|
||
|
||
break;
|
||
|
||
case 'snmp':
|
||
/** SNMP
|
||
* I.e.:
|
||
* snmp_version = 5.7.2
|
||
* snmp_text = NET-SNMP 5.7.2
|
||
*/
|
||
$snmp_cmd = is_executable($GLOBALS['config']['snmpget']) ? $GLOBALS['config']['snmpget'] : '/usr/bin/env snmpget';
|
||
$snmp_version = str_replace(' version:', '', external_exec($snmp_cmd . " --version 2>&1"));
|
||
$versions['snmp_version'] = str_replace('NET-SNMP ', '', $snmp_version);
|
||
if ($return_version) {
|
||
return $versions['snmp_version'];
|
||
}
|
||
if (empty($versions['snmp_version'])) {
|
||
$versions['snmp_version'] = 'not found';
|
||
}
|
||
$versions['snmp_text'] = $snmp_version;
|
||
break;
|
||
|
||
case 'rrdtool':
|
||
/** RRDtool
|
||
* I.e.:
|
||
* rrdtool_version = 1.5.5
|
||
* rrdcached_version = 1.5.5
|
||
* rrdtool_text = 1.5.5 (rrdcached 1.5.5: unix:/var/run/rrdcached.sock)
|
||
*/
|
||
$rrdtool_cmd = is_executable($GLOBALS['config']['rrdtool']) ? $GLOBALS['config']['rrdtool'] : '/usr/bin/env rrdtool';
|
||
[, $rrdtool_version] = explode(' ', external_exec($rrdtool_cmd . ' --version | head -n1'));
|
||
$versions['rrdtool_version'] = $rrdtool_version;
|
||
if ($return_version) {
|
||
return $versions['rrdtool_version'];
|
||
}
|
||
$versions['rrdtool_old'] = version_compare($versions['rrdtool_version'], OBS_MIN_RRD_VERSION, '<');
|
||
|
||
if (!safe_empty($GLOBALS['config']['rrdcached'])) {
|
||
if (OBS_RRD_NOLOCAL) {
|
||
// Remote rrdcached daemon (unknown version)
|
||
$rrdtool_version .= ' (rrdcached remote: ' . $GLOBALS['config']['rrdcached'] . ')';
|
||
// Remote RRDcached require version 1.5.5
|
||
$versions['rrdtool_old'] = version_compare($versions['rrdtool_version'], '1.5.5', '<');
|
||
} else {
|
||
$rrdcached_exec = str_replace('rrdtool', 'rrdcached', $GLOBALS['config']['rrdtool']);
|
||
if (!is_executable($rrdcached_exec)) {
|
||
$rrdcached_exec = '/usr/bin/env rrdcached -h';
|
||
}
|
||
[, $versions['rrdcached_version']] = explode(' ', external_exec($rrdcached_exec . ' -h | head -n1'));
|
||
$rrdtool_version .= ' (rrdcached ' . $versions['rrdcached_version'] . ': ' . $GLOBALS['config']['rrdcached'] . ')';
|
||
}
|
||
}
|
||
|
||
if (empty($rrdtool_version)) {
|
||
$rrdtool_version = 'not found';
|
||
$versions['rrdtool_version'] = $rrdtool_version;
|
||
$versions['rrdtool_old'] = TRUE;
|
||
}
|
||
$versions['rrdtool_text'] = $rrdtool_version;
|
||
break;
|
||
|
||
case 'fping':
|
||
/** Fping
|
||
* I.e.:
|
||
* fping_version = 3.13
|
||
* fping_text = 3.13 (IPv4 and IPv6)
|
||
*/
|
||
$fping_version = 'not found';
|
||
$fping_exec = is_executable($GLOBALS['config']['fping']) ? $GLOBALS['config']['fping'] : '/usr/bin/env fping';
|
||
$fping = external_exec($fping_exec . " -v 2>&1");
|
||
if (preg_match('/Version\s+(\d\S+)/', $fping, $matches)) {
|
||
$fping_version = $matches[1];
|
||
$fping_text = $fping_version;
|
||
|
||
if (version_compare($fping_version, '4.0', '>=')) {
|
||
$fping_text .= ' (IPv4 and IPv6)';
|
||
} elseif (is_executable($GLOBALS['config']['fping6'])) {
|
||
$fping_text .= ' (IPv4 and IPv6)';
|
||
} else {
|
||
$fping_text .= ' (IPv4 only)';
|
||
}
|
||
}
|
||
$versions['fping_version'] = $fping_version;
|
||
$versions['fping_text'] = $fping_text;
|
||
if ($return_version) {
|
||
return $versions['fping_version'];
|
||
}
|
||
break;
|
||
|
||
case 'http':
|
||
// Apache (or any http used?)
|
||
if (is_cli()) {
|
||
foreach (['apache2', 'httpd'] as $http_cmd) {
|
||
if (is_executable('/usr/sbin/' . $http_cmd)) {
|
||
$http_cmd = '/usr/sbin/' . $http_cmd;
|
||
} else {
|
||
$http_cmd = '/usr/bin/env ' . $http_cmd;
|
||
}
|
||
$http_version = external_exec($http_cmd . ' -v | awk \'/Server version:/ {print $3}\'');
|
||
|
||
if ($http_version) {
|
||
break;
|
||
}
|
||
}
|
||
if (empty($http_version)) {
|
||
$http_version = 'not found';
|
||
}
|
||
$versions['http_full'] = $http_version;
|
||
} else {
|
||
$versions['http_full'] = $_SERVER['SERVER_SOFTWARE'];
|
||
}
|
||
$versions['http_version'] = str_replace('Apache/', '', $versions['http_full']);
|
||
$versions['http_text'] = $versions['http_version'];
|
||
if ($return_version) {
|
||
return $versions['http_version'];
|
||
}
|
||
break;
|
||
|
||
case 'curl':
|
||
//case 'fetch':
|
||
// cURL or fetch library
|
||
if (function_exists('curl_version')) {
|
||
$curl_version = curl_version();
|
||
//print_vars($curl_version);
|
||
|
||
$versions['curl_version'] = $curl_version['version'];
|
||
// Do not use curl when https not supported
|
||
$curl_https = in_array('https', $curl_version['protocols']);
|
||
$versions['curl_old'] = version_compare($versions['curl_version'], '7.16.2', '<') || !$curl_https;
|
||
$versions['curl_text'] = 'cURL ' . $curl_version['version'];
|
||
$curl_extra = [];
|
||
if (!$curl_https) {
|
||
$curl_extra[] = 'NO https';
|
||
}
|
||
if ($curl_version['ssl_version']) {
|
||
// OpenSSL/1.1.1f
|
||
$curl_extra[] = $curl_version['ssl_version'];
|
||
}
|
||
if ($curl_version['libz_version']) {
|
||
// 1.2.11
|
||
$curl_extra[] = 'LibZ ' . $curl_version['libz_version'];
|
||
}
|
||
if ($curl_version['libidn']) {
|
||
// 2.3.0
|
||
$curl_extra[] = 'LibIDN ' . $curl_version['libidn'];
|
||
}
|
||
// if ($curl_version['brotli_version']) {
|
||
// // 1.0.9
|
||
// $curl_extra[] = 'Brotli ' . $curl_version['brotli_version'];
|
||
// }
|
||
// if ($curl_version['libssh_version']) {
|
||
// // libssh/0.9.3/openssl/zlib
|
||
// $curl_extra[] = $curl_version['libssh_version'];
|
||
// }
|
||
if ($curl_extra) {
|
||
$versions['curl_text'] .= ' (' . implode(', ', $curl_extra) . ')';
|
||
}
|
||
} else {
|
||
$versions['curl_version'] = '-1';
|
||
$versions['curl_old'] = TRUE;
|
||
$versions['curl_text'] = 'PHP fetch';
|
||
}
|
||
if ($return_version) {
|
||
return $versions['curl_version'];
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Cache for current execution
|
||
$GLOBALS['cache']['versions'] = $versions;
|
||
//print_vars($GLOBALS['cache']['versions']);
|
||
|
||
return $versions;
|
||
}
|
||
|
||
/**
|
||
* Print version information about used Observium and additional software.
|
||
*
|
||
* @return NULL
|
||
*/
|
||
function print_versions() {
|
||
get_versions();
|
||
|
||
$observium_date = format_unixtime(strtotime(OBSERVIUM_DATE), 'jS F Y');
|
||
|
||
$os_version = $GLOBALS['cache']['versions']['os_text'];
|
||
$php_version = $GLOBALS['cache']['versions']['php_text'];
|
||
$python_version = $GLOBALS['cache']['versions']['python_text'];
|
||
$mysql_version = $GLOBALS['cache']['versions']['mysql_text'];
|
||
$mysql_name = $GLOBALS['cache']['versions']['mysql_name'];
|
||
$snmp_version = $GLOBALS['cache']['versions']['snmp_text'];
|
||
$rrdtool_version = $GLOBALS['cache']['versions']['rrdtool_text'];
|
||
$fping_version = $GLOBALS['cache']['versions']['fping_text'];
|
||
$http_version = $GLOBALS['cache']['versions']['http_text'];
|
||
$curl_version = $GLOBALS['cache']['versions']['curl_text'];
|
||
|
||
// PHP memory_limit
|
||
$php_memory_limit = $GLOBALS['cache']['versions']['php_memory_limit'];
|
||
$php_memory_limit_text = $GLOBALS['cache']['versions']['php_memory_limit_text'];
|
||
|
||
if (is_cli()) {
|
||
$timezone = get_timezone();
|
||
//print_vars($timezone);
|
||
|
||
$mysql_mode = dbFetchCell("SELECT @@SESSION.sql_mode;");
|
||
$mysql_binlog = dbShowVariables("LIKE 'log_bin'");
|
||
$mysql_charset = dbShowVariables("LIKE 'character_set_connection'");
|
||
|
||
if ($GLOBALS['cache']['versions']['php_old']) {
|
||
$php_version = '%r' . $php_version;
|
||
}
|
||
if ($GLOBALS['cache']['versions']['python_old']) {
|
||
$python_version = '%r' . $python_version;
|
||
}
|
||
if ($GLOBALS['cache']['versions']['mysql_old']) {
|
||
$mysql_version = '%r' . $mysql_version;
|
||
}
|
||
if ($GLOBALS['cache']['versions']['rrdtool_old']) {
|
||
$rrdtool_version = '%r' . $rrdtool_version;
|
||
}
|
||
if ($GLOBALS['cache']['versions']['curl_old']) {
|
||
$curl_version = '%r' . $curl_version;
|
||
}
|
||
|
||
echo PHP_EOL;
|
||
print_cli_heading("Observium");
|
||
print_cli_data("Version", OBSERVIUM_VERSION);
|
||
if (OBSERVIUM_EDITION !== 'community') {
|
||
print_cli_data("Train", OBSERVIUM_TRAIN);
|
||
}
|
||
print_cli_data("Released", $observium_date);
|
||
if (OBSERVIUM_EDITION !== 'community' && OBSERVIUM_USER) {
|
||
print_cli_data("Subscription User", OBSERVIUM_USER);
|
||
// FIXME. Need way for get subscription level and date end of subscription.
|
||
}
|
||
|
||
echo PHP_EOL;
|
||
print_cli_heading("Software versions");
|
||
print_cli_data("OS", $os_version);
|
||
print_cli_data("Apache", $http_version);
|
||
print_cli_data("PHP", $php_version);
|
||
print_cli_data("Python", $python_version);
|
||
print_cli_data($mysql_name, $mysql_version);
|
||
print_cli_data("SNMP", $snmp_version);
|
||
print_cli_data("RRDtool", $rrdtool_version);
|
||
print_cli_data("Fping", $fping_version);
|
||
print_cli_data("Fetch", $curl_version);
|
||
|
||
// Additionally, in CLI always display Memory Limit, MySQL Mode and Charset info
|
||
|
||
echo PHP_EOL;
|
||
print_cli_heading("Memory Limit", 3);
|
||
print_cli_data("PHP", ($php_memory_limit >= 0 && $php_memory_limit < 268435456 ? '%r' : '') . $php_memory_limit_text, 3);
|
||
|
||
echo PHP_EOL;
|
||
print_cli_heading("DB info", 3);
|
||
print_cli_data("DB schema", get_db_version(), 3);
|
||
print_cli_data("$mysql_name binlog", ($mysql_binlog['log_bin'] === 'ON' ? '%r' : '') . $mysql_binlog['log_bin'], 3);
|
||
print_cli_data("$mysql_name mode", $mysql_mode, 3);
|
||
|
||
echo PHP_EOL;
|
||
print_cli_heading("Charset info", 3);
|
||
print_cli_data("PHP", ini_get("default_charset"), 3);
|
||
print_cli_data($mysql_name, $mysql_charset['character_set_connection'], 3);
|
||
|
||
echo PHP_EOL;
|
||
print_cli_heading("Timezones info", 3);
|
||
print_cli_data("Date", date("l, d-M-y H:i:s T"), 3);
|
||
print_cli_data("PHP", $timezone['php'], 3);
|
||
print_cli_data($mysql_name, ($timezone['diff'] !== 0 ? '%r' : '') . $timezone['mysql'], 3);
|
||
|
||
if (OBS_DISTRIBUTED) {
|
||
$poller_id = $GLOBALS['config']['poller_id'];
|
||
if ($poller_id !== 0 && empty($GLOBALS['config']['poller_name'])) {
|
||
// poller name not set by config
|
||
$poller = get_poller($poller_id);
|
||
$poller_name = $poller['poller_name'];
|
||
} else {
|
||
$poller_name = $poller_id !== 0 ? $GLOBALS['config']['poller_name'] : 'Main';
|
||
}
|
||
echo PHP_EOL;
|
||
print_cli_heading("Poller info", 3);
|
||
print_cli_data("ID", $poller_id, 3);
|
||
print_cli_data("Name", $poller_name, 3);
|
||
}
|
||
echo PHP_EOL;
|
||
|
||
} else {
|
||
|
||
if ($php_memory_limit >= 0 && $php_memory_limit < 268435456) {
|
||
$php_memory_limit_text = '<span class="text-danger">' . $php_memory_limit_text . '</span>';
|
||
}
|
||
|
||
// Check minimum versions
|
||
if ($GLOBALS['cache']['versions']['php_old']) {
|
||
$php_class = 'error';
|
||
$php_version = generate_tooltip_link(NULL, $php_version, 'Minimum supported: ' . OBS_MIN_PHP_VERSION);
|
||
} else {
|
||
$php_class = '';
|
||
$php_version = escape_html($php_version);
|
||
}
|
||
if ($GLOBALS['cache']['versions']['python_old']) {
|
||
$python_class = 'warning';
|
||
if (str_starts_with($python_version, '2.')) {
|
||
$python_version = generate_tooltip_link(NULL, $python_version, 'Recommended version is greater than or equal to: ' . OBS_MIN_PYTHON2_VERSION . ' or ' . OBS_MIN_PYTHON3_VERSION);
|
||
} else {
|
||
$python_version = generate_tooltip_link(NULL, $python_version, 'Recommended version is greater than or equal to: ' . OBS_MIN_PYTHON3_VERSION);
|
||
}
|
||
} else {
|
||
$python_class = '';
|
||
$python_version = escape_html($python_version);
|
||
}
|
||
if ($GLOBALS['cache']['versions']['mysql_old']) {
|
||
$mysql_class = 'warning';
|
||
if ($mysql_name === 'MariaDB') {
|
||
$mysql_version = generate_tooltip_link(NULL, $mysql_version, 'Recommended version is greater than or equal to: ' . OBS_MIN_MARIADB_VERSION);
|
||
} else {
|
||
$mysql_version = generate_tooltip_link(NULL, $mysql_version, 'Recommended version is greater than or equal to: ' . OBS_MIN_MYSQL_VERSION);
|
||
}
|
||
} else {
|
||
$mysql_class = '';
|
||
$mysql_version = escape_html($mysql_version);
|
||
}
|
||
if ($GLOBALS['cache']['versions']['rrdtool_old']) {
|
||
$rrdtool_class = 'error';
|
||
$rrdtool_version = generate_tooltip_link(NULL, $rrdtool_version, 'Minimum supported: ' . OBS_MIN_RRD_VERSION);
|
||
} else {
|
||
$rrdtool_class = '';
|
||
$rrdtool_version = escape_html($rrdtool_version);
|
||
}
|
||
if ($GLOBALS['cache']['versions']['curl_old']) {
|
||
$curl_class = 'error';
|
||
if ($GLOBALS['cache']['versions']['curl_version'] == '-1') {
|
||
$curl_tooltip = 'cURL module not installed';
|
||
} else {
|
||
$curl_tooltip = 'cURL module too old. Minimum supported: 7.16.2';
|
||
}
|
||
$curl_version = generate_tooltip_link(NULL, $curl_version, $curl_tooltip);
|
||
} else {
|
||
$curl_class = '';
|
||
$curl_version = escape_html($curl_version);
|
||
}
|
||
|
||
echo generate_box_open(['title' => 'Version Information']);
|
||
echo '
|
||
<table class="table table-striped table-condensed-more">
|
||
<tbody>
|
||
<tr><td><b>' . escape_html(OBSERVIUM_PRODUCT) . '</b></td><td>' . escape_html(OBSERVIUM_VERSION_LONG) . ' (' . escape_html($observium_date) . ')</td></tr>
|
||
<tr><td><b>OS</b></td><td>' . escape_html($os_version) . '</td></tr>
|
||
<tr><td><b>Apache</b></td><td>' . escape_html($http_version) . '</td></tr>
|
||
<tr class="' . $php_class . '"><td><b>PHP</b></td><td>' . $php_version . ' (Memory: ' . $php_memory_limit_text . ')</td></tr>
|
||
<tr class="' . $python_class . '"><td><b>Python</b></td><td>' . $python_version . '</td></tr>
|
||
<tr class="' . $mysql_class . '"><td><b>' . $mysql_name . '</b></td><td>' . $mysql_version . '</td></tr>
|
||
<tr><td><b>SNMP</b></td><td>' . escape_html($snmp_version) . '</td></tr>
|
||
<tr class="' . $rrdtool_class . '"><td><b>RRDtool</b></td><td>' . $rrdtool_version . '</td></tr>
|
||
<tr><td><b>Fping</b></td><td>' . escape_html($fping_version) . '</td></tr>
|
||
<tr class="' . $curl_class . '"><td><b>Fetch</b></td><td>' . $curl_version . '</td></tr>
|
||
</tbody>
|
||
</table>' . PHP_EOL;
|
||
echo generate_box_close();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Convert SNMP timeticks string into seconds
|
||
*
|
||
* SNMP timeticks can be in two different normal formats:
|
||
* - "(2105)" == 21.05 sec
|
||
* - "0:0:00:21.05" == 21.05 sec
|
||
* Sometime devices return wrong type or numeric instead timetick:
|
||
* - "Wrong Type (should be Timeticks): 1632295600" == 16322956 sec
|
||
* - "1546241903" == 15462419.03 sec
|
||
* Parse the timeticks string and convert it to seconds.
|
||
*
|
||
* @param string $timetick
|
||
* @param bool $float - Return a float with microseconds
|
||
*
|
||
* @return int|float
|
||
*/
|
||
function timeticks_to_sec($timetick, $float = FALSE)
|
||
{
|
||
if (str_contains($timetick, 'Wrong Type')) {
|
||
// Wrong Type (should be Timeticks): 1632295600
|
||
[, $timetick] = explode(': ', $timetick, 2);
|
||
}
|
||
|
||
$timetick = trim($timetick, " \t\n\r\0\x0B\"()"); // Clean string
|
||
if (is_numeric($timetick)) {
|
||
// When "Wrong Type" or timetick as an integer, than time with count of ten millisecond ticks
|
||
$time = $timetick / 100;
|
||
return ($float ? (float)$time : (int)$time);
|
||
}
|
||
if (!preg_match('/^[\d\.: ]+$/', $timetick)) {
|
||
return FALSE;
|
||
}
|
||
|
||
$timetick_array = explode(':', $timetick);
|
||
if (count($timetick_array) == 1 && is_numeric($timetick)) {
|
||
$secs = $timetick;
|
||
$microsecs = 0;
|
||
} else {
|
||
//list($days, $hours, $mins, $secs) = $timetick_array;
|
||
$secs = array_pop($timetick_array);
|
||
$mins = array_pop($timetick_array);
|
||
$hours = array_pop($timetick_array);
|
||
$days = array_pop($timetick_array);
|
||
[$secs, $microsecs] = explode('.', $secs);
|
||
|
||
$hours += $days * 24;
|
||
$mins += $hours * 60;
|
||
$secs += $mins * 60;
|
||
|
||
// Sometime used non standard years counter
|
||
if (count($timetick_array)) {
|
||
$years = array_pop($timetick_array);
|
||
$secs += $years * 31536000; // 365 * 24 * 60 * 60;
|
||
}
|
||
//print_vars($timetick_array);
|
||
}
|
||
$time = ($float ? (float)$secs + $microsecs / 100 : (int)$secs);
|
||
print_debug("Timeticks converted $timetick -> $time");
|
||
|
||
return $time;
|
||
}
|
||
|
||
/**
|
||
* Convert SNMP DateAndTime string into unixtime
|
||
*
|
||
* field octets contents range
|
||
* ----- ------ -------- -----
|
||
* 1 1-2 year 0..65536
|
||
* 2 3 month 1..12
|
||
* 3 4 day 1..31
|
||
* 4 5 hour 0..23
|
||
* 5 6 minutes 0..59
|
||
* 6 7 seconds 0..60
|
||
* (use 60 for leap-second)
|
||
* 7 8 deci-seconds 0..9
|
||
* 8 9 direction from UTC '+' / '-'
|
||
* 9 10 hours from UTC 0..11
|
||
* 10 11 minutes from UTC 0..59
|
||
*
|
||
* For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be displayed as:
|
||
* 1992-5-26,13:30:15.0,-4:0
|
||
*
|
||
* Note that if only local time is known, then timezone information (fields 8-10) is not present.
|
||
*
|
||
* @param string $datetime DateAndTime string
|
||
* @param boolean $use_gmt Return unixtime converted to GMT or Local (by default)
|
||
*
|
||
* @return integer Unixtime
|
||
*/
|
||
function datetime_to_unixtime($datetime, $use_gmt = FALSE)
|
||
{
|
||
$timezone = get_timezone();
|
||
|
||
$datetime = trim($datetime);
|
||
if (preg_match('/(?<year>\d+)-(?<mon>\d+)-(?<day>\d+)(?:,(?<hour>\d+):(?<min>\d+):(?<sec>\d+)(?<millisec>\.\d+)?(?:,(?<tzs>[+\-]?)(?<tzh>\d+):(?<tzm>\d+))?)/', $datetime, $matches)) {
|
||
if (isset($matches['tzh'])) {
|
||
// Use TZ offset from datetime string
|
||
$offset = $matches['tzs'] . ($matches['tzh'] * 3600 + $matches['tzm'] * 60); // Offset from GMT in seconds
|
||
} else {
|
||
// Or use system offset
|
||
$offset = $timezone['php_offset'];
|
||
}
|
||
$time_tmp = mktime($matches['hour'], $matches['min'], $matches['sec'], $matches['mon'], $matches['day'], $matches['year']); // Generate unixtime
|
||
|
||
$time_gmt = $time_tmp + ($offset * -1); // Unixtime from string in GMT
|
||
$time_local = $time_gmt + $timezone['php_offset']; // Unixtime from string in local timezone
|
||
} else {
|
||
$time_local = time(); // Current unixtime with local timezone
|
||
$time_gmt = $time_local - $timezone['php_offset']; // Current unixtime in GMT
|
||
}
|
||
|
||
if (OBS_DEBUG > 1) {
|
||
$debug_msg = 'UNIXTIME from DATETIME "' . ($time_tmp ? $datetime : 'time()') . '": ';
|
||
$debug_msg .= 'LOCAL (' . format_unixtime($time_local) . '), GMT (' . format_unixtime($time_gmt) . '), TZ (' . $timezone['php'] . ')';
|
||
print_message($debug_msg);
|
||
}
|
||
|
||
if ($use_gmt) {
|
||
return ($time_gmt);
|
||
}
|
||
|
||
return ($time_local);
|
||
}
|
||
|
||
/**
|
||
* Format seconds to requested time format.
|
||
*
|
||
* Default format is "long".
|
||
*
|
||
* Supported formats:
|
||
* long => '1 year, 1 day, 1h 1m 1s'
|
||
* longest => '1 year, 1 day, 1 hour 1 minute 1 second'
|
||
* short-3 => '1y 1d 1h'
|
||
* short-2 => '1y 1d'
|
||
* shorter => *same as short-2 above
|
||
* (else) => '1y 1d 1h 1m 1s'
|
||
*
|
||
* @param mixed $uptime Time is seconds
|
||
* @param string $format Optional format
|
||
*
|
||
* @return string
|
||
*/
|
||
function format_uptime($uptime, $format = "long") {
|
||
|
||
$uptime = clean_number($uptime);
|
||
if (!is_numeric($uptime)) {
|
||
print_debug("Passed incorrect value to " . __FUNCTION__ . "()");
|
||
print_debug_vars($uptime);
|
||
//return FALSE;
|
||
return '0s'; // incorrect, but for keep compatibility
|
||
}
|
||
|
||
$uptime = (int)round($uptime);
|
||
if ($uptime <= 0) {
|
||
return '0s';
|
||
}
|
||
|
||
$up['y'] = floor($uptime / 31536000);
|
||
$up['d'] = floor($uptime % 31536000 / 86400);
|
||
$up['h'] = floor($uptime % 86400 / 3600);
|
||
$up['m'] = floor($uptime % 3600 / 60);
|
||
$up['s'] = floor($uptime % 60);
|
||
|
||
$result = '';
|
||
|
||
if (str_starts_with($format, 'long')) {
|
||
if ($up['y'] > 0) {
|
||
$result .= $up['y'] . ' year' . ($up['y'] != 1 ? 's' : '');
|
||
if ($up['d'] > 0 || $up['h'] > 0 || $up['m'] > 0 || $up['s'] > 0) {
|
||
$result .= ', ';
|
||
}
|
||
}
|
||
|
||
if ($up['d'] > 0) {
|
||
$result .= $up['d'] . ' day' . ($up['d'] != 1 ? 's' : '');
|
||
if ($up['h'] > 0 || $up['m'] > 0 || $up['s'] > 0) {
|
||
$result .= ', ';
|
||
}
|
||
}
|
||
|
||
if ($format === 'longest') {
|
||
if ($up['h'] > 0) {
|
||
$result .= $up['h'] . ' hour' . ($up['h'] != 1 ? 's ' : ' ');
|
||
}
|
||
if ($up['m'] > 0) {
|
||
$result .= $up['m'] . ' minute' . ($up['m'] != 1 ? 's ' : ' ');
|
||
}
|
||
if ($up['s'] > 0) {
|
||
$result .= $up['s'] . ' second' . ($up['s'] != 1 ? 's ' : ' ');
|
||
}
|
||
} else {
|
||
if ($up['h'] > 0) {
|
||
$result .= $up['h'] . 'h ';
|
||
}
|
||
if ($up['m'] > 0) {
|
||
$result .= $up['m'] . 'm ';
|
||
}
|
||
if ($up['s'] > 0) {
|
||
$result .= $up['s'] . 's ';
|
||
}
|
||
}
|
||
} else {
|
||
$count = $format === 'shorter' ? 2 : 6;
|
||
if (str_starts_with($format, 'short-')) {
|
||
// short-2 => 2, short-3 => 3 and up to 6
|
||
$tmp = explode('-', $format, 2)[1];
|
||
if (is_numeric($tmp) && $tmp >= 1 && $tmp <= 6) {
|
||
$count = (int)$tmp;
|
||
}
|
||
}
|
||
|
||
foreach ($up as $period => $value) {
|
||
if ($value == 0) {
|
||
continue;
|
||
}
|
||
|
||
$result .= $value . $period . ' ';
|
||
$count--;
|
||
if ($count == 0) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return trim($result);
|
||
}
|
||
|
||
/**
|
||
* Get current timezones for mysql and php.
|
||
* Use this function when need display timedate from mysql
|
||
* for fix diffs between this timezones
|
||
*
|
||
* Example:
|
||
* Array
|
||
* (
|
||
* [mysql] => +03:00
|
||
* [php] => +03:00
|
||
* [php_abbr] => MSK
|
||
* [php_offset] => +10800
|
||
* [mysql_offset] => +10800
|
||
* [diff] => 0
|
||
* )
|
||
*
|
||
* @param bool $refresh Refresh timezones
|
||
*
|
||
* @return array Timezones info
|
||
*/
|
||
function get_timezone($refresh = FALSE) {
|
||
global $cache;
|
||
|
||
if ($refresh || !isset($cache['timezone'])) {
|
||
$timezone = [];
|
||
if ($refresh) {
|
||
// Call to external exec only when refresh (basically it's not required)
|
||
$timezone['system'] = external_exec('date "+%:z"'); // return '+03:00'
|
||
}
|
||
if (!OBS_DB_SKIP) {
|
||
$timezone['mysql'] = dbFetchCell('SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP);'); // return '03:00:00'
|
||
if ($timezone['mysql'][0] !== '-') {
|
||
$timezone['mysql'] = '+' . $timezone['mysql'];
|
||
}
|
||
$timezone['mysql'] = preg_replace('/:00$/', '', $timezone['mysql']); // convert to '+03:00'
|
||
}
|
||
[$timezone['php'], $timezone['php_abbr'], $timezone['php_name'], $timezone['php_daylight']] = explode('|', date('P|T|e|I'));
|
||
//$timezone['php'] = date('P'); // return '+03:00'
|
||
//$timezone['php_abbr'] = date('T'); // return 'MSK'
|
||
//$timezone['php_name'] = date('e'); // return 'Europe/Moscow'
|
||
//$timezone['php_daylight'] = date('I'); // return '0'
|
||
|
||
foreach (['php', 'mysql'] as $entry) {
|
||
if (!isset($timezone[$entry])) {
|
||
continue;
|
||
} // skip mysql if OBS_DB_SKIP
|
||
|
||
$sign = $timezone[$entry][0];
|
||
[$hours, $minutes] = explode(':', $timezone[$entry]);
|
||
$timezone[$entry . '_offset'] = $sign . (abs($hours) * 3600 + $minutes * 60); // Offset from GMT in seconds
|
||
}
|
||
|
||
if (OBS_DB_SKIP) {
|
||
// If mysql skipped, just return system/php timezones without caching
|
||
return $timezone;
|
||
}
|
||
|
||
// Get the difference in sec between mysql and php timezones
|
||
$timezone['diff'] = (int)$timezone['mysql_offset'] - (int)$timezone['php_offset'];
|
||
$cache['timezone'] = $timezone;
|
||
}
|
||
|
||
return $cache['timezone'];
|
||
}
|
||
|
||
function generate_timezone_list($refresh = FALSE) {
|
||
global $cache;
|
||
|
||
if ($refresh || !isset($cache['timezone_list'])) {
|
||
/*
|
||
$regions = [
|
||
DateTimeZone::AFRICA,
|
||
DateTimeZone::AMERICA,
|
||
DateTimeZone::ANTARCTICA,
|
||
DateTimeZone::ARCTIC,
|
||
DateTimeZone::ASIA,
|
||
DateTimeZone::ATLANTIC,
|
||
DateTimeZone::AUSTRALIA,
|
||
DateTimeZone::EUROPE,
|
||
DateTimeZone::INDIAN,
|
||
DateTimeZone::PACIFIC,
|
||
DateTimeZone::UTC
|
||
];
|
||
|
||
$t = [];
|
||
foreach($regions as $region) {
|
||
$t[] = DateTimeZone::listIdentifiers($region);
|
||
}
|
||
$timezones = array_merge([], ...$t);
|
||
*/
|
||
|
||
$timezone_offsets = [];
|
||
foreach (timezone_identifiers_list() as $timezone) {
|
||
$tz = new DateTimeZone($timezone);
|
||
$timezone_offsets[$timezone] = $tz->getOffset(new DateTime);
|
||
}
|
||
|
||
// sort timezone by offset
|
||
asort($timezone_offsets);
|
||
|
||
$timezone_list = [];
|
||
foreach ($timezone_offsets as $timezone => $offset) {
|
||
$offset_prefix = $offset < 0 ? '-' : '+';
|
||
$offset_formatted = gmdate('H:i', abs($offset));
|
||
|
||
$pretty_offset = "UTC{$offset_prefix}{$offset_formatted}";
|
||
|
||
$timezone_list[$timezone] = [ 'descr' => "({$pretty_offset}) $timezone", 'offset' => $offset ];
|
||
}
|
||
|
||
$cache['timezone_list'] = $timezone_list;
|
||
}
|
||
|
||
return $cache['timezone_list'];
|
||
}
|
||
|
||
/**
|
||
* Convert common MAC strings to fixed 12 char string
|
||
*
|
||
* @param string $mac MAC string (ie: 66:c:9b:1b:62:7e, 00 02 99 09 E9 84)
|
||
*
|
||
* @return string Cleaned MAC string (ie: 00029909e984)
|
||
*/
|
||
function mac_zeropad($mac) {
|
||
$mac = strtolower(trim($mac));
|
||
if (str_contains($mac, ':')) {
|
||
// STRING: 66:c:9b:1b:62:7e
|
||
$mac_parts = explode(':', $mac);
|
||
if (count($mac_parts) === 6) {
|
||
$mac = '';
|
||
foreach ($mac_parts as $part) {
|
||
$mac .= zeropad($part);
|
||
}
|
||
} else {
|
||
// stringified
|
||
// 30:30:2d:30:36:2d:33:39:2d:30:41:2d:35:46:2d:36:38 -> STRING: "00-06-39-0A-5F-68"
|
||
$hex_mac = str_replace(':', ' ', $mac);
|
||
$str_mac = snmp_hexstring($hex_mac);
|
||
if ($str_mac && $str_mac !== $hex_mac) {
|
||
return mac_zeropad($str_mac);
|
||
}
|
||
}
|
||
} else {
|
||
if (str_contains($mac, ' ') && strlen($mac) >= 35) { // 12 * 3 - 1
|
||
// stringified
|
||
// 30 30 2d 30 36 2d 33 39 2d 30 41 2d 35 46 2d 36 38 -> STRING: "00-06-39-0A-5F-68"
|
||
$str_mac = snmp_hexstring($mac);
|
||
if ($str_mac && $str_mac !== $mac) {
|
||
return mac_zeropad($str_mac);
|
||
}
|
||
}
|
||
|
||
// Hex-STRING: 00 02 99 09 E9 84
|
||
// Cisco MAC: 1234.5678.9abc
|
||
// Other Vendors: 00-0B-DC-00-68-AF
|
||
// Some other: 0x123456789ABC
|
||
$mac = str_replace([ ' ', '.', '-', '0x' ], '', $mac);
|
||
}
|
||
|
||
if (strlen($mac) === 12 && ctype_xdigit($mac)) {
|
||
return $mac;
|
||
}
|
||
|
||
// Strip out non-hex digits (Not sure when this required, copied for compat with old format_mac())
|
||
$mac = preg_replace('/[[:^xdigit:]]/', '', $mac);
|
||
return (strlen($mac) === 12) ? $mac : NULL;
|
||
}
|
||
|
||
/**
|
||
* Formats a MAC address string with the specified delimiter.
|
||
*
|
||
* @param string $mac MAC address string in any known format.
|
||
* @param string $split_char Allowed delimiters for specific formats: ':', '', ' ', '0x'.
|
||
*
|
||
* @return string The formatted MAC address string.
|
||
*/
|
||
function format_mac($mac, $split_char = ':')
|
||
{
|
||
// Clean MAC string
|
||
$mac = mac_zeropad($mac);
|
||
|
||
if (empty($mac)) {
|
||
return '';
|
||
}
|
||
|
||
// Add colons
|
||
$mac = preg_replace('/([[:xdigit:]]{2})(?!$)/', '$1:', $mac);
|
||
|
||
// Convert fake MACs to IP
|
||
//if (preg_match('/ff:fe:([[:xdigit:]]+):([[:xdigit:]]+):([[:xdigit:]]+):([[:xdigit:]]{1,2})/', $mac, $matches))
|
||
if (preg_match('/ff:fe:([[:xdigit:]]{2}):([[:xdigit:]]{2}):([[:xdigit:]]{2}):([[:xdigit:]]{2})/', $mac, $matches)) {
|
||
if ($matches[1] == '00' && $matches[2] == '00') {
|
||
$mac = hexdec($matches[3]) . '.' . hexdec($matches[4]) . '.X.X'; // Cisco, why you convert 192.88.99.1 to 0:0:c0:58 (should be c0:58:63:1)
|
||
} else {
|
||
$mac = hexdec($matches[1]) . '.' . hexdec($matches[2]) . '.' . hexdec($matches[3]) . '.' . hexdec($matches[4]);
|
||
}
|
||
}
|
||
|
||
if ($split_char === '') {
|
||
$mac = str_replace(':', $split_char, $mac);
|
||
} elseif ($split_char === ' ') {
|
||
$mac = strtoupper(str_replace(':', $split_char, $mac));
|
||
} elseif ($split_char === '.') {
|
||
// Cisco like format
|
||
$parts = explode(':', $mac, 6);
|
||
$mac = $parts[0] . $parts[1] . '.' . $parts[2] . $parts[3] . '.' . $parts[4] . $parts[5];
|
||
} elseif ($split_char === '0x') {
|
||
$mac = '0x' . strtoupper(str_replace(':', '', $mac));
|
||
}
|
||
|
||
return $mac;
|
||
}
|
||
|
||
/**
|
||
* Checks if the required exec functions are available.
|
||
*
|
||
* @return bool TRUE if proc_open and proc_get_status are available and not disabled, FALSE otherwise.
|
||
*/
|
||
function is_exec_available()
|
||
{
|
||
// Check if ini_get is not disabled
|
||
if (!function_exists('ini_get')) {
|
||
print_warning('WARNING: Function ini_get() is disabled via the `disable_functions` option in php.ini configuration file. Please clean this function from this list.');
|
||
|
||
return TRUE; // NOTE, this is not a critical function for functionally
|
||
}
|
||
|
||
$required_functions = ['proc_open', 'proc_get_status'];
|
||
$disabled_functions = explode(',', ini_get('disable_functions'));
|
||
foreach ($required_functions as $func) {
|
||
if (in_array($func, $disabled_functions)) {
|
||
print_error('ERROR: Function ' . $func . '() is disabled via the `disable_functions` option in php.ini configuration file. Please clean this function from this list.');
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* Opens pipes for a command, sets non-blocking mode for stdout and stderr, and returns the process resource.
|
||
*
|
||
* @param string $command The command to be executed.
|
||
* @param array $pipes An array that will be filled with pipe resources.
|
||
* @param string|null $cwd Optional. The working directory for the command. Defaults to NULL, which uses the current working directory.
|
||
* @param array $env Optional. An array of environment variables for the command. Defaults to an empty array.
|
||
*
|
||
* @return resource|false The process resource on success, or false on failure.
|
||
*/
|
||
function pipe_open($command, &$pipes, $cwd = NULL, $env = [])
|
||
{
|
||
$descriptorspec = [
|
||
0 => ['pipe', 'r'], // stdin
|
||
1 => ['pipe', 'w'], // stdout
|
||
2 => ['pipe', 'w'] // stderr
|
||
];
|
||
|
||
$process = proc_open($command, $descriptorspec, $pipes, $cwd, $env);
|
||
|
||
if (is_resource($process)) {
|
||
stream_set_blocking($pipes[1], 0);
|
||
stream_set_blocking($pipes[2], 0);
|
||
}
|
||
|
||
return $process;
|
||
}
|
||
|
||
/**
|
||
* Reads the output of a command executed through pipes.
|
||
*
|
||
* @param string $command The command to be executed.
|
||
* @param array $pipes An array of pipe resources.
|
||
* @param bool $fullread Optional. Determines if the entire output should be read. Default is true.
|
||
*
|
||
* @return string The output of the command with the last end-of-line character removed.
|
||
*/
|
||
function pipe_read($command, &$pipes, $fullread = TRUE)
|
||
{
|
||
// $pipes like this:
|
||
// 0 => writeable handle connected to child stdin
|
||
// 1 => readable handle connected to child stdout
|
||
// Any error output will be appended to /tmp/error-output.txt
|
||
|
||
fwrite($pipes[0], $command);
|
||
fclose($pipes[0]);
|
||
|
||
if ($fullread) {
|
||
// Read while not end of file
|
||
$stdout = '';
|
||
while (!feof($pipes[1])) {
|
||
$stdout .= fgets($pipes[1]);
|
||
}
|
||
} else {
|
||
// Output not matter, for rrdtool
|
||
$iter = 0;
|
||
$line = '';
|
||
$stdout = '';
|
||
while (strlen($line) < 1 && $iter < 1000) {
|
||
// wait for 10 milliseconds to loosen loop (max 1s)
|
||
usleep(1000);
|
||
$line = fgets($pipes[1], 1024);
|
||
$stdout .= $line;
|
||
$iter++;
|
||
}
|
||
}
|
||
|
||
return preg_replace('/(?:\n|\r\n|\r)$/D', '', $stdout); // remove last (only) eol
|
||
}
|
||
|
||
/**
|
||
* Closes the pipes and the process.
|
||
*
|
||
* @param resource $process The process resource.
|
||
* @param array $pipes An array of pipe resources.
|
||
*
|
||
* @return int The termination status of the process that was run.
|
||
*/
|
||
function pipe_close($process, &$pipes)
|
||
{
|
||
// Close each pipe resource if it's valid
|
||
foreach ($pipes as $key => $pipe) {
|
||
if (is_resource($pipe)) {
|
||
fclose($pipe);
|
||
}
|
||
}
|
||
|
||
// Close pipes before proc_close() to avoid deadlock
|
||
|
||
return proc_close($process);
|
||
}
|
||
|
||
/**
|
||
* Execute an external command and return its stdout, with execution details stored in a reference parameter.
|
||
*
|
||
* @param string $command The command to execute.
|
||
* @param array &$exec_status Reference parameter to store execution details (stdout, stderr, exitcode, runtime).
|
||
* @param int $timeout The timeout for the command in seconds. Set to null for no timeout.
|
||
* @param bool $debug If true, the debugging function will be called to print execution details.
|
||
*
|
||
* @return string The stdout from the executed command.
|
||
*/
|
||
function external_exec($command, &$exec_status = [], $timeout = NULL, $debug = FALSE)
|
||
{
|
||
$command = trim($command);
|
||
$debug = $debug || (defined('OBS_DEBUG') && OBS_DEBUG);
|
||
|
||
if ($debug > 0) {
|
||
external_exec_debug_cmd($command);
|
||
}
|
||
|
||
if ($command === '') {
|
||
$exec_status = [
|
||
'stdout' => '',
|
||
'stderr' => 'Empty command passed',
|
||
'exitcode' => -1,
|
||
'runtime' => 0
|
||
];
|
||
return '';
|
||
}
|
||
|
||
$descriptorspec = [
|
||
1 => ['pipe', 'w'], // stdout
|
||
2 => ['pipe', 'w'] // stderr
|
||
];
|
||
|
||
$process = proc_open('exec ' . $command, $descriptorspec, $pipes);
|
||
|
||
if (!is_resource($process)) {
|
||
$exec_status = [
|
||
'stdout' => FALSE,
|
||
'stderr' => '',
|
||
'exitcode' => -1,
|
||
'runtime' => 0
|
||
];
|
||
return FALSE;
|
||
}
|
||
|
||
stream_set_blocking($pipes[1], 0);
|
||
stream_set_blocking($pipes[2], 0);
|
||
|
||
$stdout = $stderr = '';
|
||
$exec_status = [];
|
||
|
||
$start = microtime(TRUE);
|
||
$runtime = 0;
|
||
|
||
while (TRUE) {
|
||
$read = [];
|
||
if (!feof($pipes[1])) {
|
||
$read[] = $pipes[1];
|
||
}
|
||
if (!feof($pipes[2])) {
|
||
$read[] = $pipes[2];
|
||
}
|
||
if (empty($read)) {
|
||
break;
|
||
}
|
||
$write = NULL;
|
||
$except = NULL;
|
||
|
||
if (stream_select($read, $write, $except, $timeout) === FALSE) {
|
||
break; // Handle stream_select() failure
|
||
}
|
||
|
||
foreach ($read as $pipe) {
|
||
if ($pipe === $pipes[1]) {
|
||
$stdout .= fread($pipe, 8192);
|
||
} elseif ($pipe === $pipes[2]) {
|
||
$stderr .= fread($pipe, 8192);
|
||
}
|
||
}
|
||
|
||
$runtime = elapsed_time($start);
|
||
$status = proc_get_status($process);
|
||
|
||
// Break from this loop if the process exited before timeout
|
||
if (!$status['running']) {
|
||
// Check for the rare situation of a wrong process status due to a proc_get_status() bug
|
||
// See: https://bugs.php.net/bug.php?id=69014
|
||
if (feof($pipes[1]) === FALSE) {
|
||
// Store the status in $status_fix to use later if needed
|
||
if (!isset($status_fix)) {
|
||
$status_fix = $status;
|
||
}
|
||
if ($debug > 1) {
|
||
print_error("Possible proc_get_status() bug encountered. Process is still running, but the status indicates otherwise.");
|
||
}
|
||
} else {
|
||
// Process exited normally, so we can break the loop
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ($timeout !== NULL) {
|
||
$timeout -= $runtime;
|
||
|
||
if ($timeout < 0) {
|
||
proc_terminate($process, 9);
|
||
$status['running'] = FALSE;
|
||
$status['exitcode'] = -1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
$exec_status['endtime'] = microtime(TRUE);
|
||
|
||
// FIXME -- Check if necessary in PHP7+
|
||
|
||
if ($status['running']) {
|
||
// Fix sometimes wrong status by adding a delay to wait for the process to finish
|
||
$delay = 0;
|
||
$delay_step = 5000; // Step increment of 5 milliseconds
|
||
$delay_max = 150000; // Maximum delay of 150 milliseconds
|
||
|
||
// Keep waiting in increments of delay_step while the process is running
|
||
// and the total delay is less than the maximum allowed delay (delay_max)
|
||
while ($status['running'] && $delay < $delay_max) {
|
||
usleep($delay_step); //
|
||
$status = proc_get_status($process);
|
||
$delay += $delay_step;
|
||
}
|
||
|
||
$exec_status['exitdelay'] = (int)($delay / 1000);
|
||
} elseif (isset($status_fix)) {
|
||
// If $status_fix is set, use it to correct the wrong process status
|
||
$status = $status_fix;
|
||
}
|
||
|
||
$stdout = preg_replace('/(?:\n|\r\n|\r)$/D', '', $stdout); // remove last (only) eol
|
||
$stderr = rtrim($stderr);
|
||
|
||
if ($status['running']) {
|
||
proc_terminate($process, 9);
|
||
}
|
||
|
||
fclose($pipes[1]);
|
||
fclose($pipes[2]);
|
||
proc_close($process);
|
||
|
||
$exec_status['stdout'] = $stdout;
|
||
$exec_status['stderr'] = $stderr;
|
||
$exec_status['exitcode'] = $status['exitcode'] ?? -1;
|
||
$exec_status['runtime'] = $runtime;
|
||
|
||
if ($debug) {
|
||
external_exec_debug($exec_status);
|
||
}
|
||
|
||
return $stdout;
|
||
}
|
||
|
||
/**
|
||
* Print sanitized debugging information for a command.
|
||
*
|
||
* This function hides sensitive data (such as SNMP authentication parameters
|
||
* and usernames/passwords) from the debug output to ensure security.
|
||
*
|
||
* @param string $command The command to execute and debug.
|
||
*/
|
||
function external_exec_debug_cmd($command) {
|
||
|
||
$debug_command = ($command === '' && isset($GLOBALS['snmp_command'])) ? $GLOBALS['snmp_command'] : $command;
|
||
if (OBS_DEBUG < 2 && $GLOBALS['config']['snmp']['hide_auth'] &&
|
||
preg_match("/snmp(bulk)?(get|getnext|walk)(\s+-(t|r|Cr)['\d\s]+){0,3}(\s+-Cc)?\s+-v[123]c?\s+/", $debug_command)) {
|
||
// Hide SNMP authentication parameters from debug output
|
||
$pattern = "/\s+(?:(\-[uxXaA])\s*(?:'.*?')|(\-c)\s*(?:'.*?(@\S+)?'))/"; // do not hide contexts, only community and v3 auth
|
||
$debug_command = preg_replace($pattern, ' \1\2 ***\3', $debug_command);
|
||
} elseif (OBS_DEBUG < 2 && preg_match("!\ --(user(?:name)?|password)=!", $debug_command)) {
|
||
// Hide any username/password in debug output, e.g. in WMIC
|
||
$pattern = "/ --(user(?:name)?|password)=(\S+|\'[^\']*\')/";
|
||
$debug_command = preg_replace($pattern, ' --\1=***', $debug_command);
|
||
}
|
||
print_console(PHP_EOL . 'CMD[%y' . $debug_command . '%n]' . PHP_EOL);
|
||
}
|
||
|
||
/**
|
||
* Print detailed debugging information for an executed command.
|
||
*
|
||
* This function displays the exit code, runtime, exit delay (if applicable),
|
||
* and the contents of stdout and stderr from the executed command.
|
||
*
|
||
* @param array $exec_status An array containing execution details:
|
||
* - 'stdout': The standard output of the command.
|
||
* - 'stderr': The standard error output of the command.
|
||
* - 'exitcode': The exit code returned by the command.
|
||
* - 'runtime': The time taken to execute the command.
|
||
* - 'exitdelay': The delay before the command exited (optional).
|
||
*/
|
||
function external_exec_debug($exec_status) {
|
||
|
||
print_console("CMD EXITCODE[" . ($exec_status['exitcode'] !== 0 ? '%r' : '%g') . $exec_status['exitcode'] . "%n]");
|
||
print_console("CMD RUNTIME[" . ($exec_status['runtime'] > 7 ? '%r' : '%g') . round($exec_status['runtime'], 4) . "s%n]");
|
||
|
||
if ($exec_status['exitdelay'] > 0) {
|
||
print_console("CMD EXITDELAY[%r" . $exec_status['exitdelay'] . "ms%n]");
|
||
}
|
||
|
||
print_console("STDOUT[" . PHP_EOL . $exec_status['stdout'] . PHP_EOL . "]");
|
||
|
||
if ($exec_status['exitcode'] && $exec_status['stderr']) {
|
||
print_console("STDERR[" . PHP_EOL . $exec_status['stderr'] . PHP_EOL . "]");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get information about process by it identifier (pid)
|
||
*
|
||
* @param int $pid The process identifier.
|
||
* @param boolean $stats If true, additionally show cpu/memory stats
|
||
*
|
||
* @return array|false Array with information about process, If process not found, return FALSE
|
||
*/
|
||
function get_pid_info($pid, $stats = FALSE)
|
||
{
|
||
$pid = (int)$pid;
|
||
if ($pid < 1) {
|
||
print_debug("Incorrect PID passed");
|
||
//trigger_error("PID ".$pid." doesn't exists", E_USER_WARNING);
|
||
return FALSE;
|
||
}
|
||
|
||
if (!$stats && stripos(PHP_OS, 'Linux') === 0) {
|
||
// Do not use call to ps on Linux and extended stat not required
|
||
// FIXME. Need something same on BSD and other Unix platforms
|
||
|
||
if ($pid_stat = lstat("/proc/$pid")) {
|
||
$pid_info = ['PID' => (string)$pid];
|
||
$ps_stat = explode(" ", file_get_contents("/proc/$pid/stat"));
|
||
$pid_info['PPID'] = $ps_stat[3];
|
||
$pid_info['UID'] = $pid_stat['uid'] . '';
|
||
$pid_info['GID'] = $pid_stat['gid'] . '';
|
||
$pid_info['STAT'] = $ps_stat[2];
|
||
$pid_info['COMMAND'] = trim(str_replace("\0", " ", file_get_contents("/proc/$pid/cmdline")));
|
||
$pid_info['STARTED'] = date("r", $pid_stat['mtime']);
|
||
$pid_info['STARTED_UNIX'] = $pid_stat['mtime'];
|
||
} else {
|
||
$pid_info = FALSE;
|
||
}
|
||
|
||
} else {
|
||
// Use ps call, have troubles on high load systems!
|
||
|
||
if ($stats) {
|
||
// Add CPU/Mem stats
|
||
$options = 'pid,ppid,uid,gid,pcpu,pmem,vsz,rss,tty,stat,time,lstart,args';
|
||
} else {
|
||
$options = 'pid,ppid,uid,gid,tty,stat,time,lstart,args';
|
||
}
|
||
|
||
//$timezone = get_timezone(); // Get system timezone info, for correct started time conversion
|
||
|
||
$ps = external_exec('/bin/ps -ww -o ' . $options . ' -p ' . $pid, $exec_status, 1); // Set timeout 1sec for exec
|
||
$ps = explode("\n", rtrim($ps));
|
||
|
||
if ($exec_status['exitcode'] === 127) {
|
||
print_debug("/bin/ps command not found, not possible to get process info.");
|
||
return NULL;
|
||
}
|
||
if ($exec_status['exitcode'] !== 0 || count($ps) < 2) {
|
||
print_debug("PID " . $pid . " doesn't exists");
|
||
//trigger_error("PID ".$pid." doesn't exists", E_USER_WARNING);
|
||
return FALSE;
|
||
}
|
||
|
||
// " PID PPID UID GID %CPU %MEM VSZ RSS TT STAT TIME STARTED COMMAND"
|
||
// "14675 10250 1000 1000 0.0 0.2 194640 11240 pts/4 S+ 00:00:00 Mon Mar 21 14:48:08 2016 php ./test_pid.php"
|
||
//
|
||
// " PID PPID UID GID TT STAT TIME STARTED COMMAND"
|
||
// "14675 10250 1000 1000 pts/4 S+ 00:00:00 Mon Mar 21 14:48:08 2016 php ./test_pid.php"
|
||
//print_vars($ps);
|
||
|
||
// Parse output
|
||
$keys = preg_split("/\s+/", $ps[0], -1, PREG_SPLIT_NO_EMPTY);
|
||
$entries = preg_split("/\s+/", $ps[1], count($keys) - 1, PREG_SPLIT_NO_EMPTY);
|
||
$started = preg_split("/\s+/", array_pop($entries), 6, PREG_SPLIT_NO_EMPTY);
|
||
$command = array_pop($started);
|
||
|
||
//$started[] = str_replace(':', '', $timezone['system']); // Add system TZ to started time
|
||
$started[] = external_exec('date "+%z"'); // Add system TZ to started time
|
||
$started_rfc = array_shift($started) . ','; // Sun
|
||
// Reimplode and convert to RFC2822 started date 'Sun, 20 Mar 2016 18:01:53 +0300'
|
||
$started_rfc .= ' ' . $started[1]; // 20
|
||
$started_rfc .= ' ' . $started[0]; // Mar
|
||
$started_rfc .= ' ' . $started[3]; // 2016
|
||
$started_rfc .= ' ' . $started[2]; // 18:01:53
|
||
$started_rfc .= ' ' . $started[4]; // +0300
|
||
//$started_rfc .= implode(' ', $started);
|
||
$entries[] = $started_rfc;
|
||
|
||
$entries[] = $command; // Re-add command
|
||
//print_vars($entries);
|
||
//print_vars($started);
|
||
|
||
$pid_info = [];
|
||
foreach ($keys as $i => $key) {
|
||
$pid_info[$key] = $entries[$i];
|
||
}
|
||
$pid_info['STARTED_UNIX'] = strtotime($pid_info['STARTED']);
|
||
//print_vars($pid_info);
|
||
|
||
}
|
||
|
||
return $pid_info;
|
||
}
|
||
|
||
/**
|
||
* Add information about process into DB
|
||
*
|
||
* @param array|int $device Device array
|
||
* @param int $pid PID for process. If empty used current PHP process ID
|
||
*
|
||
* @return int DB id for inserted row
|
||
*/
|
||
function add_process_info($device, $pid = NULL)
|
||
{
|
||
global $argv, $config;
|
||
|
||
$process_name = OBS_SCRIPT_NAME;
|
||
$process = OBS_PROCESS_NAME;
|
||
|
||
// Ability for skip any process checking
|
||
// WARNING. USE AT OWN RISK
|
||
if (isset($config['check_process'][$process]) && !$config['check_process'][$process]) {
|
||
if (OBS_DEBUG) {
|
||
print_error("WARNING. Process '$process_name' adding disabled.");
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
// Check if device_id passed instead array
|
||
if (is_numeric($device)) {
|
||
$device = ['device_id' => $device];
|
||
}
|
||
if (!is_numeric($pid)) {
|
||
$pid = getmypid();
|
||
}
|
||
$pid_info = get_pid_info($pid);
|
||
|
||
if (is_array($pid_info)) {
|
||
if ($process_name === 'poller.php' || $process_name === 'alerter.php') {
|
||
// Try detect parent poller wrapper
|
||
$parent_info = $pid_info;
|
||
do {
|
||
$found = FALSE;
|
||
$parent_info = get_pid_info($parent_info['PPID']);
|
||
if (str_contains($parent_info['COMMAND'], $process_name)) {
|
||
$found = TRUE;
|
||
} elseif (str_contains($parent_info['COMMAND'], 'poller-wrapper.py')) {
|
||
$pid_info['PPID'] = $parent_info['PID'];
|
||
}
|
||
} while ($found);
|
||
}
|
||
$update_array = [
|
||
'process_pid' => $pid,
|
||
'process_name' => $process_name,
|
||
'process_ppid' => $pid_info['PPID'],
|
||
'process_uid' => $pid_info['UID'],
|
||
'process_command' => $pid_info['COMMAND'],
|
||
'process_start' => $pid_info['STARTED_UNIX'],
|
||
'device_id' => $device['device_id']
|
||
];
|
||
if ($config['poller_id'] > 0 && is_cli()) {
|
||
$update_array['poller_id'] = $config['poller_id'];
|
||
}
|
||
return dbInsert($update_array, 'observium_processes');
|
||
}
|
||
print_debug("Process info not added for PID: $pid");
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* Delete information about process from DB
|
||
*
|
||
* @param array $device Device array
|
||
* @param int $pid PID for process. If empty used current PHP process ID
|
||
*
|
||
* @return int DB id for inserted row
|
||
*/
|
||
function del_process_info($device, $pid = NULL)
|
||
{
|
||
global $argv, $config;
|
||
|
||
$process_name = basename($argv[0]);
|
||
|
||
// Ability for skip any process checking
|
||
// WARNING. USE AT OWN RISK
|
||
$process = str_replace('.php', '', $process_name);
|
||
if (isset($config['check_process'][$process]) && !$config['check_process'][$process]) {
|
||
if (OBS_DEBUG) {
|
||
print_error("WARNING. Process '$process_name' delete disabled.");
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
// Check if device_id passed instead array
|
||
if (is_numeric($device)) {
|
||
$device = ['device_id' => $device];
|
||
}
|
||
if (!is_numeric($pid)) {
|
||
$pid = getmypid();
|
||
}
|
||
|
||
if ($pid) {
|
||
$params = [$pid, $process_name, $device['device_id'], $config['poller_id']];
|
||
|
||
return dbDelete('observium_processes', '`process_pid` = ? AND `process_name` = ? AND `device_id` = ? AND `poller_id` = ?', $params);
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
function check_process_run($device, $pid = NULL)
|
||
{
|
||
global $config, $argv;
|
||
|
||
$check = FALSE;
|
||
|
||
$process_name = basename($argv[0]);
|
||
|
||
// Ability for skip any process checking
|
||
// WARNING. USE AT OWN RISK
|
||
$process = str_replace('.php', '', $process_name);
|
||
if (isset($config['check_process'][$process]) && !$config['check_process'][$process]) {
|
||
if (OBS_DEBUG) {
|
||
print_error("WARNING. Process '$process_name' checking disabled.");
|
||
}
|
||
return $check;
|
||
}
|
||
|
||
// Check if device_id passed instead array
|
||
if (is_numeric($device)) {
|
||
$device = ['device_id' => $device];
|
||
}
|
||
|
||
$query = 'SELECT * FROM `observium_processes` WHERE `process_name` = ? AND `device_id` = ? AND `poller_id` = ?';
|
||
$params = [$process_name, $device['device_id'], $config['poller_id']];
|
||
if (is_numeric($pid)) {
|
||
$query .= ' AND `process_pid` = ?';
|
||
$params[] = (int)$pid;
|
||
}
|
||
|
||
foreach (dbFetchRows($query, $params) as $process) {
|
||
// We found processes in DB, check if it exist on system
|
||
$pid_info = get_pid_info($process['process_pid']);
|
||
if (is_array($pid_info) && strpos($pid_info['COMMAND'], $process_name) !== FALSE) {
|
||
// Process still running
|
||
$check = array_merge($pid_info, $process);
|
||
} else {
|
||
// Remove stalled DB entries
|
||
dbDelete('observium_processes', '`process_id` = ?', [$process['process_id']]);
|
||
}
|
||
}
|
||
|
||
return $check;
|
||
}
|
||
|
||
/**
|
||
* Determine array is associative?
|
||
*
|
||
* @param array $array
|
||
*
|
||
* @return boolean
|
||
*/
|
||
function is_array_assoc($array) {
|
||
return is_array($array) && !array_is_list($array);
|
||
}
|
||
|
||
/**
|
||
* Determine array is sequential list?
|
||
*
|
||
* @param array $array
|
||
*
|
||
* @return boolean
|
||
*/
|
||
function is_array_list($array) {
|
||
return is_array($array) && array_is_list($array);
|
||
}
|
||
|
||
function is_array_numeric($array) {
|
||
if (!is_array($array) || empty($array)) {
|
||
return FALSE;
|
||
}
|
||
foreach ($array as $value) {
|
||
// Check if the value is not numeric
|
||
if (!is_numeric($value)) {
|
||
// Return false immediately if a non-numeric value is found
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
// If the loop completes, all values are numeric
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* Detect if a needle exists in an array. Support mixed needle value.
|
||
*
|
||
* @param mixed $value Needle
|
||
* @param array $array Where to find
|
||
*
|
||
* @return bool
|
||
*/
|
||
function array_value_exist($value, $array) {
|
||
if (!is_array($array) || empty($array)) {
|
||
return FALSE;
|
||
}
|
||
|
||
// Non array needle
|
||
if (!is_array($value)) {
|
||
return in_array($value, $array);
|
||
}
|
||
|
||
// Get the intersection of both arrays.
|
||
$intersect = array_intersect($value, $array);
|
||
|
||
// Check if the intersection is not empty.
|
||
return !empty($intersect);
|
||
}
|
||
|
||
/**
|
||
* Checks if the given key or index exists in the array.
|
||
* Case-insensitive implementation
|
||
*
|
||
* @param string|int $key Value to check.
|
||
* @param array $array An array with keys to check.
|
||
*
|
||
* @return bool
|
||
*/
|
||
function array_key_iexists($key, $array) {
|
||
if (!is_array($array)) {
|
||
return FALSE;
|
||
}
|
||
return in_array(strtolower($key), array_map('strtolower', array_keys($array)), TRUE);
|
||
}
|
||
|
||
/**
|
||
* Case-insensitive in_array()
|
||
*
|
||
* @param string $needle
|
||
* @param array $array
|
||
*
|
||
* @return bool
|
||
*/
|
||
function in_iarray($needle, $array) {
|
||
if (!is_array($array)) {
|
||
return FALSE;
|
||
}
|
||
// Convert both the needle and haystack elements to lowercase
|
||
return in_array(strtolower($needle), array_map('strtolower', $array), TRUE);
|
||
}
|
||
|
||
/**
|
||
* Get all values from specific key in a multidimensional array
|
||
*
|
||
* @param $key string
|
||
* @param $arr array
|
||
*
|
||
* @return null|string|array
|
||
*/
|
||
function array_value_recursive($key, array $arr)
|
||
{
|
||
$val = [];
|
||
array_walk_recursive($arr, static function ($v, $k) use ($key, &$val) {
|
||
if ($k == $key) {
|
||
array_push($val, $v);
|
||
}
|
||
});
|
||
|
||
return count($val) > 1 ? $val : array_pop($val);
|
||
}
|
||
|
||
/**
|
||
* @param $array
|
||
* @param $string
|
||
* @param string $delimiter
|
||
*
|
||
* @return mixed|null
|
||
*/
|
||
function array_get_nested($array, $string, $delimiter = '->')
|
||
{
|
||
foreach (explode($delimiter, $string) as $key) {
|
||
if (!array_key_exists($key, (array)$array)) {
|
||
return NULL;
|
||
}
|
||
$array = $array[$key];
|
||
}
|
||
|
||
return $array;
|
||
}
|
||
|
||
/**
|
||
* Insert a value or key/value pair after a specific key in an array. If key doesn't exist, value is appended
|
||
* to the end of the array.
|
||
*
|
||
* @param array $array
|
||
* @param string $key
|
||
* @param array|string $new
|
||
*
|
||
* @return array
|
||
*/
|
||
function array_push_after(array $array, $key, $new)
|
||
{
|
||
$keys = array_keys($array);
|
||
$index = array_search($key, $keys, TRUE);
|
||
$count = count($array);
|
||
if ($index === FALSE) {
|
||
return array_merge_recursive($array, (array)$new);
|
||
}
|
||
$pos = $index + 1;
|
||
|
||
return array_merge_recursive(array_slice($array, 0, $pos, TRUE), (array)$new, array_slice($array, $pos, $count - 1, TRUE));
|
||
}
|
||
|
||
function array_filter_key($array, $keys = [], $condition = TRUE) {
|
||
|
||
if ($condition === 'starts' || $condition === 'starts_with') {
|
||
return array_filter($array, static function($key) use ($keys) { return str_starts_with($key, (string)$keys); }, ARRAY_FILTER_USE_KEY);
|
||
}
|
||
if ($condition === 'contains') {
|
||
return array_filter($array, static function($key) use ($keys) { return str_contains($key, (string)$keys); }, ARRAY_FILTER_USE_KEY);
|
||
}
|
||
|
||
// keys is array
|
||
if (!is_array($array) || ($condition === TRUE && empty($keys)) || !array_is_list($keys)) {
|
||
return [];
|
||
}
|
||
|
||
if ($condition === FALSE || $condition === '!=' || $condition === '!==') {
|
||
return array_filter($array, static function($key) use ($keys) { return !in_array($key, $keys, TRUE); }, ARRAY_FILTER_USE_KEY);
|
||
}
|
||
return array_filter($array, static function($key) use ($keys) { return in_array($key, $keys, TRUE); }, ARRAY_FILTER_USE_KEY);
|
||
}
|
||
|
||
/**
|
||
* Fast string compare function, checks if string contain $needle
|
||
* Note: function renamed from str_contains() for not to intersect with php8 function.
|
||
*
|
||
* @param string $string The string to search in
|
||
* @param mixed $needle If needle is not a string, it is converted to an string
|
||
* @param mixed $encoding For use "slow" multibyte compare, pass required encoding here (ie: UTF-8)
|
||
* @param bool $case_insensitivity If case_insensitivity is TRUE, comparison is case insensitive
|
||
*
|
||
* @return bool Returns TRUE if $string starts with $needle or FALSE otherwise
|
||
*/
|
||
function str_contains_array($string, $needle, $encoding = FALSE, $case_insensitivity = FALSE)
|
||
{
|
||
if (is_array($string)) {
|
||
// This function required string to search
|
||
return FALSE;
|
||
}
|
||
|
||
// If needle is array, use recursive compare
|
||
if (is_array($needle)) {
|
||
foreach ($needle as $findme) {
|
||
if (str_contains_array($string, (string)$findme, $encoding, $case_insensitivity)) {
|
||
$GLOBALS['str_last_needle'] = (string)$findme;
|
||
return TRUE;
|
||
}
|
||
}
|
||
$GLOBALS['str_last_needle'] = (string)$findme;
|
||
return FALSE;
|
||
}
|
||
|
||
$needle = (string)$needle;
|
||
$string = (string)$string;
|
||
$GLOBALS['str_last_needle'] = $needle;
|
||
$compare = $string === $needle;
|
||
if ($needle === '') {
|
||
return $compare;
|
||
}
|
||
if ($case_insensitivity) {
|
||
// Case-INsensitive
|
||
|
||
// NOTE, multibyte compare required mb_* functions and slower than general functions
|
||
if ($encoding && check_extension_exists('mbstring') &&
|
||
mb_strlen($string, $encoding) !== strlen($string)) {
|
||
//$encoding = 'UTF-8';
|
||
//return mb_strripos($string, $needle, -mb_strlen($string, $encoding), $encoding) !== FALSE;
|
||
return $compare || mb_stripos($string, $needle) !== FALSE;
|
||
}
|
||
|
||
return $compare || stripos($string, $needle) !== FALSE;
|
||
}
|
||
|
||
// Case-sensitive
|
||
return $compare || str_contains((string)$string, $needle);
|
||
}
|
||
|
||
function str_icontains_array($string, $needle, $encoding = FALSE)
|
||
{
|
||
return str_contains_array($string, $needle, $encoding, TRUE);
|
||
}
|
||
|
||
/**
|
||
* Fast string compare function, checks if string begin with $needle
|
||
*
|
||
* @param string $string The string to search in
|
||
* @param mixed $needle If needle is not a string, it is converted to an string
|
||
* @param mixed $encoding For use "slow" multibyte compare, pass required encoding here (ie: UTF-8)
|
||
* @param boolean $case_insensitivity If case_insensitivity is TRUE, comparison is case insensitive
|
||
*
|
||
* @return boolean Returns TRUE if $string starts with $needle or FALSE otherwise
|
||
*/
|
||
function str_starts($string, $needle, $encoding = FALSE, $case_insensitivity = FALSE)
|
||
{
|
||
if (is_array($string)) {
|
||
// This function required string to search
|
||
return FALSE;
|
||
}
|
||
|
||
// If needle is array, use recursive compare
|
||
if (is_array($needle)) {
|
||
foreach ($needle as $findme) {
|
||
if (str_starts($string, (string)$findme, $encoding, $case_insensitivity)) {
|
||
$GLOBALS['str_last_needle'] = (string)$findme;
|
||
return TRUE;
|
||
}
|
||
}
|
||
$GLOBALS['str_last_needle'] = (string)$findme;
|
||
return FALSE;
|
||
}
|
||
|
||
$needle = (string)$needle;
|
||
$string = (string)$string;
|
||
$GLOBALS['str_last_needle'] = $needle;
|
||
if ($needle === '') {
|
||
return $string === $needle;
|
||
}
|
||
if ($case_insensitivity) {
|
||
// Case-INsensitive
|
||
|
||
// NOTE, multibyte compare required mb_* functions and slower than general functions
|
||
if ($encoding &&
|
||
check_extension_exists('mbstring') && mb_strlen($string, $encoding) !== strlen($string)) {
|
||
//$encoding = 'UTF-8';
|
||
return mb_strripos($string, $needle, -mb_strlen($string, $encoding), $encoding) !== FALSE;
|
||
}
|
||
|
||
return $needle !== ''
|
||
? strncasecmp($string, $needle, strlen($needle)) === 0
|
||
: $string === '';
|
||
}
|
||
|
||
// Case-sensitive
|
||
return str_starts_with((string)$string, $needle);
|
||
}
|
||
|
||
function str_istarts($string, $needle, $encoding = FALSE)
|
||
{
|
||
return str_starts($string, $needle, $encoding, TRUE);
|
||
}
|
||
|
||
/**
|
||
* Fast string compare function, checks if string end with $needle
|
||
*
|
||
* @param string $string The string to search in
|
||
* @param mixed $needle If needle is not a string, it is converted to an string
|
||
* @param mixed $encoding For use "slow" multibyte compare, pass required encoding here (ie: UTF-8)
|
||
* @param boolean $case_insensitivity If case_insensitivity is TRUE, comparison is case insensitive
|
||
*
|
||
* @return boolean Returns TRUE if $string ends with $needle or FALSE otherwise
|
||
*/
|
||
function str_ends($string, $needle, $encoding = FALSE, $case_insensitivity = FALSE)
|
||
{
|
||
if (is_array($string)) {
|
||
// This function required string to search
|
||
return FALSE;
|
||
}
|
||
|
||
// If needle is array, use recursive compare
|
||
if (is_array($needle)) {
|
||
foreach ($needle as $findme) {
|
||
if (str_ends($string, (string)$findme, $encoding, $case_insensitivity)) {
|
||
$GLOBALS['str_last_needle'] = (string)$findme;
|
||
return TRUE;
|
||
}
|
||
}
|
||
$GLOBALS['str_last_needle'] = (string)$findme;
|
||
return FALSE;
|
||
}
|
||
|
||
$needle = (string)$needle;
|
||
$string = (string)$string;
|
||
$GLOBALS['str_last_needle'] = $needle;
|
||
$compare = $needle !== '';
|
||
if ($needle === '') {
|
||
return $string === $needle;
|
||
}
|
||
|
||
// NOTE, multibyte compare required mb_* functions and slower than general functions
|
||
if ($encoding && $compare &&
|
||
check_extension_exists('mbstring') && mb_strlen($string, $encoding) !== strlen($string)) {
|
||
//$encoding = 'UTF-8';
|
||
$diff = mb_strlen($string, $encoding) - mb_strlen($needle, $encoding);
|
||
if ($case_insensitivity) {
|
||
return $diff >= 0 && mb_stripos($string, $needle, $diff, $encoding) !== FALSE;
|
||
}
|
||
return $diff >= 0 && mb_strpos($string, $needle, $diff, $encoding) !== FALSE;
|
||
}
|
||
|
||
// Case sensitive compare
|
||
if (!$case_insensitivity) {
|
||
return str_ends_with((string)$string, $needle);
|
||
}
|
||
|
||
$nlen = strlen($needle);
|
||
|
||
return $compare
|
||
? substr_compare($string, $needle, -$nlen, $nlen, $case_insensitivity) === 0
|
||
: $string === '';
|
||
}
|
||
|
||
function str_iends($string, $needle, $encoding = FALSE)
|
||
{
|
||
return str_ends($string, $needle, $encoding, TRUE);
|
||
}
|
||
|
||
/**
|
||
* Compress long strings to hexified compressed string. Can be uncompressed by str_decompress().
|
||
*
|
||
* @param string $string
|
||
*
|
||
* @return string
|
||
*/
|
||
function str_compress($string)
|
||
{
|
||
if (!is_string($string)) {
|
||
return $string;
|
||
}
|
||
|
||
if ($compressed = gzdeflate($string, 9)) {
|
||
$compressed = gzdeflate($compressed, 9);
|
||
|
||
if (OBS_DEBUG > 1) {
|
||
$compressed = safe_base64_encode($compressed);
|
||
print_cli("DEBUG: String '$string' [" . strlen($string) . "] compressed to '" . $compressed . "' [" . strlen($compressed) . "].");
|
||
return $compressed;
|
||
}
|
||
|
||
return safe_base64_encode($compressed);
|
||
}
|
||
|
||
return $string;
|
||
}
|
||
|
||
/**
|
||
* Decompress strings compressed by str_compress().
|
||
*
|
||
* @param string $compressed
|
||
*
|
||
* @return string
|
||
*/
|
||
function str_decompress($compressed)
|
||
{
|
||
if (!is_string($compressed) || !ctype_print($compressed) || !$bin = safe_base64_decode($compressed)) {
|
||
return FALSE;
|
||
}
|
||
|
||
$string = gzinflate(gzinflate($bin));
|
||
if (!is_string($string)) {
|
||
// Not an compressed string?
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
if (OBS_DEBUG > 1) {
|
||
print_cli("DEBUG: String '$compressed' [" . strlen($compressed) . "] decompressed to '" . $string . "' [" . strlen($string) . "].");
|
||
}
|
||
return $string;
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function is_cli()
|
||
{
|
||
if (defined('__PHPUNIT_PHAR__') && isset($GLOBALS['cache']['is_cli'])) {
|
||
// Allow override is_cli() in PHPUNIT
|
||
return $GLOBALS['cache']['is_cli'];
|
||
}
|
||
if (!defined('OBS_CLI')) {
|
||
define('OBS_CLI', PHP_SAPI === 'cli' && empty($_SERVER['REMOTE_ADDR']));
|
||
if (defined('OBS_DEBUG') && OBS_DEBUG > 1) {
|
||
print_cli("DEBUG: is_cli() == " . (OBS_CLI ? 'TRUE' : 'FALSE') . ", PHP_SAPI: '" . PHP_SAPI . "', REMOTE_ADDR: '" . $_SERVER['REMOTE_ADDR'] . "'");
|
||
}
|
||
}
|
||
|
||
return OBS_CLI;
|
||
}
|
||
|
||
function cli_is_piped()
|
||
{
|
||
if (!defined('OBS_CLI_PIPED')) {
|
||
define('OBS_CLI_PIPED', check_extension_exists('posix') && !posix_isatty(STDOUT));
|
||
}
|
||
|
||
return OBS_CLI_PIPED;
|
||
}
|
||
|
||
// Detect if script runned from crontab
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function is_cron()
|
||
{
|
||
if (!defined('OBS_CRON')) {
|
||
$cron = is_cli() && !isset($_SERVER['TERM']);
|
||
// For more accurate check if STDOUT exist (but this requires posix extension)
|
||
if ($cron) {
|
||
$cron = $cron && cli_is_piped();
|
||
}
|
||
define('OBS_CRON', $cron);
|
||
}
|
||
|
||
return OBS_CRON;
|
||
}
|
||
|
||
/**
|
||
* Detect if current URI is link to graph
|
||
*
|
||
* @return boolean TRUE if current script is graph
|
||
*/
|
||
// TESTME needs unit testing
|
||
function is_graph()
|
||
{
|
||
if (!defined('OBS_GRAPH')) {
|
||
// defined in html/graph.php
|
||
define('OBS_GRAPH', FALSE);
|
||
}
|
||
|
||
return OBS_GRAPH;
|
||
}
|
||
|
||
/**
|
||
* Detect if current URI is API
|
||
*
|
||
* @return boolean TRUE if current script is API
|
||
*/
|
||
// TESTME needs unit testing
|
||
function is_api()
|
||
{
|
||
if (!defined('OBS_API')) {
|
||
// defined in html/graph.php
|
||
define('OBS_API', FALSE);
|
||
}
|
||
|
||
return OBS_API;
|
||
}
|
||
|
||
function is_ajax()
|
||
{
|
||
if (!defined('OBS_AJAX')) {
|
||
define('OBS_AJAX', (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') ||
|
||
str_starts_with($_SERVER['REQUEST_URI'], '/ajax/'));
|
||
}
|
||
|
||
return OBS_AJAX;
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function is_ssl()
|
||
{
|
||
if (isset($_SERVER['HTTPS'])) {
|
||
if ($_SERVER['HTTPS'] === '1' || strtolower($_SERVER['HTTPS']) === 'on') {
|
||
return TRUE;
|
||
}
|
||
} elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
|
||
return TRUE;
|
||
} elseif (isset($_SERVER['HTTP_FRONT_END_HTTPS']) && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) === 'on') {
|
||
return TRUE;
|
||
} elseif (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == '443')) {
|
||
return TRUE;
|
||
}
|
||
//elseif (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') { return TRUE; }
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
function is_iframe() {
|
||
//bdump($_SERVER['HTTP_SEC_FETCH_DEST']);
|
||
// Note, Safari detect as iframe: <object data=""
|
||
return isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] === 'iframe';
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function print_prompt($text, $default_yes = FALSE)
|
||
{
|
||
if (is_cli()) {
|
||
if (cli_is_piped()) {
|
||
// If now not have interactive TTY skip any prompts, return default
|
||
return TRUE && $default_yes;
|
||
}
|
||
|
||
$question = ($default_yes ? 'Y/n' : 'y/N');
|
||
echo trim($text), " [$question]: ";
|
||
$handle = fopen('php://stdin', 'r');
|
||
$line = strtolower(trim(fgets($handle, 3)));
|
||
fclose($handle);
|
||
if ($default_yes) {
|
||
$return = ($line === 'no' || $line === 'n');
|
||
} else {
|
||
$return = ($line === 'yes' || $line === 'y');
|
||
}
|
||
} else {
|
||
// Here placeholder for web prompt
|
||
$return = TRUE && $default_yes;
|
||
}
|
||
|
||
return $return;
|
||
}
|
||
|
||
/**
|
||
* This function echoes text with style 'debug', see print_message().
|
||
* Here checked constant OBS_DEBUG, if OBS_DEBUG not set output - empty.
|
||
*
|
||
* @param string $text
|
||
* @param boolean $strip Stripe special characters (for web) or html tags (for cli)
|
||
*/
|
||
function print_debug($text, $strip = FALSE) {
|
||
if (defined('OBS_DEBUG') && OBS_DEBUG > 0) {
|
||
print_message($text, 'debug', $strip);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* This function echoes text with style 'error', see print_message().
|
||
*
|
||
* @param string $text
|
||
* @param boolean $strip Stripe special characters (for web) or html tags (for cli)
|
||
*/
|
||
function print_error($text, $strip = TRUE) {
|
||
print_message($text, 'error', $strip);
|
||
}
|
||
|
||
/**
|
||
* This function echoes text with style 'warning', see print_message().
|
||
*
|
||
* @param string $text
|
||
* @param boolean $strip Stripe special characters (for web) or html tags (for cli)
|
||
*/
|
||
function print_warning($text, $strip = TRUE) {
|
||
print_message($text, 'warning', $strip);
|
||
}
|
||
|
||
/**
|
||
* This function echoes text with style 'success', see print_message().
|
||
*
|
||
* @param string $text
|
||
* @param boolean $strip Stripe special characters (for web) or html tags (for cli)
|
||
*/
|
||
function print_success($text, $strip = TRUE) {
|
||
print_message($text, 'success', $strip);
|
||
}
|
||
|
||
function print_console($text, $strip = FALSE) {
|
||
print_message($text, 'console', $strip);
|
||
}
|
||
|
||
/**
|
||
* This function echoes text with specific styles (different for cli and web output).
|
||
*
|
||
* @param string $text
|
||
* @param string $type Supported types: default, success, warning, error, debug
|
||
* @param boolean $strip Stripe special characters (for web) or html tags (for cli)
|
||
*/
|
||
function print_message($text, $type = '', $strip = TRUE) {
|
||
|
||
// Do nothing if an input text not any string (like NULL, array or other). (Empty string '' still printed).
|
||
if (!is_string($text) && !is_numeric($text)) {
|
||
return NULL;
|
||
}
|
||
|
||
$type = strtolower(trim($type));
|
||
switch ($type) {
|
||
case 'ok':
|
||
case 'success':
|
||
$cli_class = '%g'; // green
|
||
$cli_color = FALSE; // by default cli coloring disabled
|
||
$class = 'alert alert-success'; // green
|
||
break;
|
||
|
||
case 'warning':
|
||
$cli_class = '%b'; // blue
|
||
$cli_color = FALSE; // by default cli coloring disabled
|
||
$class = 'alert alert-warning'; // yellow
|
||
break;
|
||
|
||
case 'error':
|
||
case 'danger':
|
||
case 'alert':
|
||
case 'debug':
|
||
$cli_class = '%r'; // red
|
||
$cli_color = FALSE; // by default cli coloring disabled
|
||
$class = 'alert alert-danger'; // red
|
||
break;
|
||
|
||
case 'suppressed':
|
||
$cli_class = '%m'; // magenta
|
||
$cli_color = FALSE; // by default cli coloring disabled
|
||
$class = 'alert alert-suppressed'; // magenta
|
||
break;
|
||
|
||
case 'color':
|
||
$cli_class = ''; // none
|
||
$cli_color = TRUE; // allow using coloring
|
||
$class = 'alert alert-info'; // blue
|
||
break;
|
||
|
||
case 'console':
|
||
// This is a special type used nl2br conversion for display console messages on WUI with correct line breaks
|
||
$cli_class = ''; // none
|
||
$cli_color = TRUE; // allow using coloring
|
||
$class = 'alert alert-suppressed'; // purple
|
||
break;
|
||
|
||
case 'info':
|
||
default:
|
||
$cli_class = '%W'; // bold
|
||
$cli_color = FALSE; // by default cli coloring disabled
|
||
$class = 'alert alert-info'; // blue
|
||
break;
|
||
}
|
||
|
||
// Strip tags from a text, separate for cli/web
|
||
if ($strip) {
|
||
$text = message_strip_tags($text, $cli_color);
|
||
}
|
||
|
||
// Store non debug message to global var
|
||
if ($type !== 'debug') {
|
||
$GLOBALS['last_message'] = $text;
|
||
}
|
||
|
||
if (is_cli()) {
|
||
if ($type === 'debug' && !$cli_color) {
|
||
// For debug just echo message.
|
||
echo $text . PHP_EOL;
|
||
} else {
|
||
print_cli($cli_class . $text . '%n' . PHP_EOL, $cli_color);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (safe_empty($text) ||
|
||
($type !== 'debug' && (is_graph() || is_api()))) {
|
||
// Do not web output if the string is empty or graph or api
|
||
return;
|
||
}
|
||
|
||
if (str_starts_with($type, 'box-')) {
|
||
// Boxed Web UI output
|
||
print_box($text, str_replace('box-', '', $type));
|
||
return;
|
||
}
|
||
|
||
// General Web UI output
|
||
$msg = PHP_EOL . ' <div class="' . $class . '">';
|
||
if (!str_contains_array($type, [ 'warning', 'error', 'danger' ])) {
|
||
// Dismiss button
|
||
$msg .= '<button type="button" class="close" data-dismiss="alert">×</button>';
|
||
}
|
||
if ($type === 'console') {
|
||
$text = nl2br(trim($text)); // Convert newline to <br /> for console messages with line breaks
|
||
}
|
||
|
||
$msg .= '
|
||
<div>' . $text . '</div>
|
||
</div>' . PHP_EOL;
|
||
|
||
echo $msg;
|
||
}
|
||
|
||
/**
|
||
* @param string $text
|
||
* @param bool $color
|
||
*
|
||
* @return string
|
||
*/
|
||
function message_strip_tags($text, $color = TRUE) {
|
||
|
||
if (is_cli()) {
|
||
// Strip in CLI output
|
||
$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); // Convert special HTML entities back to characters
|
||
$text = str_ireplace([ '<br />', '<br>', '<br/>' ], PHP_EOL, $text); // Convert html <br> to into newline
|
||
|
||
// FIXME. Convert some color label classes to cli tags?
|
||
return strip_tags($text);
|
||
}
|
||
|
||
// Strip in WEB output
|
||
if ($text === strip_tags($text)) {
|
||
// Convert special characters to HTML entities only if text not have html tags
|
||
$text = escape_html($text);
|
||
}
|
||
|
||
if ($color) {
|
||
// Replace some Pear::Console_Color2 color codes with html styles
|
||
$to = [
|
||
'%', // '%%'
|
||
'</span>', // '%n'
|
||
'<span class="label label-warning">', // '%y'
|
||
'<span class="label label-success">', // '%g'
|
||
'<span class="label label-danger">', // '%r'
|
||
'<span class="label label-primary">', // '%b'
|
||
'<span class="label label-info">', // '%c'
|
||
'<span class="label label-default">', // '%W'
|
||
'<span class="label label-default" style="color:black;">', // '%k'
|
||
'<span style="font-weight: bold;">', // '%_'
|
||
'<span style="text-decoration: underline;">', // '%U'
|
||
];
|
||
} else {
|
||
$to = [ '%', '' ];
|
||
}
|
||
|
||
// Cli colored tags (Pear::Console_Color2)
|
||
$from = [ '%%', '%n', '%y', '%g', '%r', '%b', '%c', '%W', '%k', '%_', '%U' ];
|
||
|
||
return str_replace($from, $to, $text);
|
||
}
|
||
|
||
function get_last_message()
|
||
{
|
||
if (!isset($GLOBALS['last_message'])) {
|
||
return NULL;
|
||
}
|
||
$text = str_replace(['%%', '%n', '%y', '%g', '%r', '%b', '%c', '%W', '%k', '%_', '%U'], '', $GLOBALS['last_message']);
|
||
|
||
// Reset message for prevent errors in loops
|
||
//unset($GLOBALS['last_message']);
|
||
|
||
if (preg_match('/^[A-Z\_ ]+\[(.+)\]$/s', $text, $matches)) {
|
||
// CLI messages like:
|
||
// RESPONSE ERROR[You must use an API key to authenticate each request]
|
||
return $matches[1];
|
||
}
|
||
return strip_tags($text);
|
||
}
|
||
|
||
function print_cli($text, $colour = TRUE)
|
||
{
|
||
|
||
$msg = new Console_Color2();
|
||
|
||
// Always append reset colour at text end
|
||
if ($colour && str_contains($text, '%')) {
|
||
$text .= '%n';
|
||
}
|
||
|
||
echo $msg -> convert($text, $colour);
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function print_obsolete_config($filter = '')
|
||
{
|
||
global $config;
|
||
|
||
$list = [];
|
||
foreach ($config['obsolete_config'] as $entry) {
|
||
if ($filter && strpos($entry['old'], $filter) === FALSE) {
|
||
continue;
|
||
}
|
||
$old = explode('->', $entry['old']);
|
||
// Founded obsolete config in overall $config
|
||
$found = !is_null(array_get_nested($config, $entry['old']));
|
||
if ($found && is_null(array_get_nested(get_defined_settings(), $entry['old']))) {
|
||
// Check if this is in config.php or in DB config
|
||
$found = FALSE;
|
||
// FIXME need migrate old DB config to new
|
||
}
|
||
|
||
if ($found) {
|
||
$new = explode('->', $entry['new']);
|
||
$info = strlen($entry['info']) ? ' (' . $entry['info'] . ')' : '';
|
||
$list[] = " %r\$config['" . implode("']['", $old) . "']%n -> %g\$config['" . implode("']['", $new) . "']%n" . $info;
|
||
}
|
||
}
|
||
|
||
if ($list) {
|
||
$msg = "%WWARNING%n Obsolete configuration(s) found in config.php file, please rename respectively:\n" . implode(PHP_EOL, $list);
|
||
print_message($msg, 'color');
|
||
return TRUE;
|
||
} else {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
// Check if php extension exist, than warn or fail
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function check_extension_exists($extension, $text = FALSE, $fatal = FALSE)
|
||
{
|
||
$extension = strtolower($extension);
|
||
|
||
if (isset($GLOBALS['cache']['extension'][$extension])) {
|
||
// Cached
|
||
$exist = $GLOBALS['cache']['extension'][$extension];
|
||
} else {
|
||
|
||
$extension_functions = [
|
||
'ldap' => 'ldap_connect',
|
||
'mysql' => 'mysql_connect',
|
||
'mysqli' => 'mysqli_connect',
|
||
'mbstring' => 'mb_detect_encoding',
|
||
'mcrypt' => 'mcrypt_encrypt', // CLEANME, mcrypt not used anymore (deprecated since php 7.1, removed since php 7.2)
|
||
'posix' => 'posix_isatty',
|
||
'session' => 'session_name',
|
||
'svn' => 'svn_log'
|
||
];
|
||
|
||
if (isset($extension_functions[$extension])) {
|
||
$exist = @function_exists($extension_functions[$extension]);
|
||
} else {
|
||
$exist = @extension_loaded($extension);
|
||
}
|
||
|
||
// Cache
|
||
$GLOBALS['cache']['extension'][$extension] = $exist;
|
||
}
|
||
|
||
if (!$exist) {
|
||
// Print error (only if $text not equals to FALSE)
|
||
if ($text === '' || $text === TRUE) {
|
||
// Generic message
|
||
print_error("The extension '$extension' is missing. Please check your PHP configuration.");
|
||
} elseif ($text !== FALSE) {
|
||
// Custom message
|
||
print_error("The extension '$extension' is missing. $text");
|
||
} else {
|
||
// Debug message
|
||
print_debug("The extension '$extension' is missing. Please check your PHP configuration.");
|
||
}
|
||
|
||
// Exit if $fatal set to TRUE
|
||
if ($fatal) {
|
||
exit(2);
|
||
}
|
||
}
|
||
|
||
return $exist;
|
||
}
|
||
|
||
/**
|
||
* Sign function
|
||
*
|
||
* This function extracts the sign of the number.
|
||
* Returns -1 (negative), 0 (zero), 1 (positive)
|
||
*
|
||
* @param integer|float $int
|
||
*
|
||
* @return integer
|
||
*/
|
||
function sgn($int)
|
||
{
|
||
if ($int < 0) {
|
||
return -1;
|
||
}
|
||
return $int > 0 ? 1 : 0;
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function truncate($substring, $max = 50, $rep = '...')
|
||
{
|
||
if ($rep === '...' && !is_cli()) {
|
||
// in html use entities triple dot
|
||
$rep_len = 1;
|
||
//$rep = '…';
|
||
$rep = '…';
|
||
} else {
|
||
$rep_len = strlen($rep);
|
||
}
|
||
if (safe_empty($substring)) {
|
||
//$string = $rep;
|
||
return $rep;
|
||
}
|
||
|
||
$string = (string)$substring;
|
||
|
||
if (strlen($string) > $max) {
|
||
$leave = $max - $rep_len;
|
||
return substr_replace($string, $rep, $leave);
|
||
}
|
||
return $string;
|
||
}
|
||
|
||
/**
|
||
* Truncate a string to a specified length, and replace the removed part with a replacement string.
|
||
*
|
||
* @param string $substring The input string to be truncated.
|
||
* @param int $max The maximum allowed length of the truncated string. Default is 50.
|
||
* @param string $rep The replacement string to be used when truncating. Default is '...'.
|
||
*
|
||
* @return string The truncated string.
|
||
*/
|
||
function truncate_ng($substring, $max = 50, $rep = '...')
|
||
{
|
||
// If not in a command-line interface environment, use the HTML entity for the triple dot
|
||
if (!is_cli() && $rep === '...') {
|
||
$rep = '…';
|
||
}
|
||
|
||
// Truncate the string using mb_strimwidth() and add the replacement string if needed
|
||
return mb_strimwidth($substring, 0, $max, $rep, 'UTF-8');
|
||
}
|
||
|
||
|
||
/**
|
||
* Escape HTML characters in a string using htmlspecialchars() while allowing specific tags and entities.
|
||
*
|
||
* @param string|null $string The input string to be escaped.
|
||
* @param int $flags Flags to be used with htmlspecialchars(). Default is ENT_QUOTES.
|
||
*
|
||
* @return string|null The escaped string, or null if the input is null.
|
||
*/
|
||
function escape_html($string, $flags = ENT_QUOTES)
|
||
{
|
||
if (empty($string)) {
|
||
return $string;
|
||
}
|
||
|
||
$string = htmlspecialchars($string, $flags, 'UTF-8');
|
||
|
||
// Un-escape allowed tags using a callback
|
||
if (str_contains($string, '<')) {
|
||
$allowed_tags = $GLOBALS['config']['escape_html']['tags'];
|
||
$string = preg_replace_callback('/<(\/?(.+?)(?: *\/)?)>/', static function ($matches) use ($allowed_tags) {
|
||
$tag = $matches[2];
|
||
|
||
if (in_array($tag, $allowed_tags, TRUE)) {
|
||
return '<' . $matches[1] . '>';
|
||
}
|
||
|
||
return $matches[0];
|
||
}, $string);
|
||
}
|
||
|
||
// Un-escape allowed entities using a callback
|
||
if (str_contains($string, '&')) {
|
||
$allowed_entities = $GLOBALS['config']['escape_html']['entities'];
|
||
$string = preg_replace_callback('/&([^;]+);/', static function ($matches) use ($allowed_entities) {
|
||
$entity = $matches[1];
|
||
|
||
if (in_array($entity, $allowed_entities, TRUE)) {
|
||
return '&' . $entity . ';';
|
||
}
|
||
|
||
return $matches[0];
|
||
}, $string);
|
||
}
|
||
|
||
return $string;
|
||
}
|
||
|
||
|
||
/**
|
||
* Generate a random string with a given length and an optional character set.
|
||
*
|
||
* @param int $max The length of the generated random string. Default is 16.
|
||
* @param string|null $characters An optional string of characters to be used for generating the random string. If not provided, a default set of alphanumeric
|
||
* characters will be used.
|
||
*
|
||
* @return string The generated random string.
|
||
*/
|
||
function random_string($max = 16, $characters = NULL)
|
||
{
|
||
if (!$characters || !is_string($characters)) {
|
||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||
}
|
||
|
||
$randstring = '';
|
||
$length = strlen($characters) - 1;
|
||
|
||
for ($i = 0; $i < $max; $i++) {
|
||
$randstring .= $characters[random_int(0, $length)];
|
||
}
|
||
|
||
return $randstring;
|
||
}
|
||
|
||
/**
|
||
* Generate a cryptographically secure random string with a given length and an optional encoding.
|
||
*
|
||
* @param int $length The length of the generated random string. Default is 16.
|
||
* @param string $encoding The encoding to use for the generated string. Supports 'hex' and 'base64'. Default is 'hex'.
|
||
*
|
||
* @return string|null The generated random string, or null if an invalid encoding is provided.
|
||
*/
|
||
function generate_secure_random_string($length = 16, $encoding = 'hex')
|
||
{
|
||
$randomBytes = random_bytes($length);
|
||
|
||
switch ($encoding) {
|
||
case 'hex':
|
||
return bin2hex($randomBytes);
|
||
case 'base64':
|
||
return rtrim(base64_encode($randomBytes), '=');
|
||
default:
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sanitize a filename by replacing any non-alphanumeric or non-standard characters with underscores.
|
||
*
|
||
* @param string $filename The input filename to be sanitized.
|
||
*
|
||
* @return string The sanitized filename with non-alphanumeric or non-standard characters replaced by underscores.
|
||
*/
|
||
function safename($filename)
|
||
{
|
||
return preg_replace('/[^a-zA-Z0-9._\-]/', '_', $filename);
|
||
}
|
||
|
||
/**
|
||
* Pad a number with zeros on the left side up to the specified length.
|
||
*
|
||
* @param int|string $num The input number to be zero-padded.
|
||
* @param int $length The desired length of the zero-padded number. Default is 2.
|
||
*
|
||
* @return string The zero-padded number as a string.
|
||
*/
|
||
function zeropad($num, $length = 2)
|
||
{
|
||
return str_pad($num, $length, '0', STR_PAD_LEFT);
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function format_bps($value, $round = 2, $sf = 3)
|
||
{
|
||
return format_si($value, $round, $sf) . "bps";
|
||
}
|
||
|
||
/**
|
||
* Format a storage value in bytes using the binary prefix system (KB, MB, GB, etc.), and round the value
|
||
* to a specified number of decimal places.
|
||
*
|
||
* @param float $value The input value in bytes to be formatted.
|
||
* @param int $round The number of decimal places to round the value to. Default is 2.
|
||
* @param int $sf The number of significant figures to use in the formatted value. Default is 3.
|
||
*
|
||
* @return string The formatted storage value with the appropriate binary prefix.
|
||
*/
|
||
function format_bytes($value, $round = 2, $sf = 3)
|
||
{
|
||
return format_bi($value, $round, $sf) . 'B';
|
||
}
|
||
|
||
/**
|
||
* Format a numeric value using the SI prefix system, and round the value to a specified number of decimal places.
|
||
*
|
||
* @param mixed $value The input value to be formatted.
|
||
* @param int $round The number of decimal places to round the value to. Default is 2.
|
||
* @param int $sf The number of significant figures to use in the formatted value. Default is 3.
|
||
*
|
||
* @return string The formatted value with the appropriate SI prefix.
|
||
*/
|
||
function format_si($value, $round = 2, $sf = 3) {
|
||
$value = clean_number($value);
|
||
if (!is_numeric($value)) {
|
||
print_debug("Passed incorrect value to " . __FUNCTION__ . "()");
|
||
print_debug_vars($value);
|
||
//return FALSE;
|
||
return '0'; // incorrect, but for keep compatibility
|
||
}
|
||
|
||
if ($value < 0) {
|
||
$neg = TRUE;
|
||
$value *= -1;
|
||
} else {
|
||
$neg = FALSE;
|
||
}
|
||
|
||
// https://physics.nist.gov/cuu/Units/prefixes.html
|
||
if ($value >= 0.1) {
|
||
$sizes = [ '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ];
|
||
$ext = $sizes[0];
|
||
for ($i = 1; (($i < count($sizes)) && ($value >= 1000)); $i++) {
|
||
$value /= 1000;
|
||
$ext = $sizes[$i];
|
||
}
|
||
} else {
|
||
$sizes = [ '', 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y' ];
|
||
$ext = $sizes[0];
|
||
for ($i = 1; (($i < count($sizes)) && ($value != 0) && ($value <= 0.1)); $i++) {
|
||
$value *= 1000;
|
||
$ext = $sizes[$i];
|
||
}
|
||
}
|
||
|
||
if ($neg) {
|
||
$value *= -1;
|
||
}
|
||
//print_warning("$value " . round($value, $round));
|
||
|
||
return format_number_short(round($value, $round), $sf) . $ext;
|
||
}
|
||
|
||
/**
|
||
* Format a value using binary prefixes (k, M, G, T, P, E) and round the value
|
||
* to a specified number of decimal places.
|
||
*
|
||
* @param mixed $value The input value to be formatted.
|
||
* @param int $round The number of decimal places to round the value to. Default is 2.
|
||
* @param int $sf The number of significant figures to use in the formatted value. Default is 3.
|
||
*
|
||
* @return string The formatted value with the appropriate binary prefix.
|
||
*/
|
||
function format_bi($value, $round = 2, $sf = 3) {
|
||
$value = clean_number($value);
|
||
if (!is_numeric($value)) {
|
||
print_debug("Passed incorrect value to " . __FUNCTION__ . "()");
|
||
print_debug_vars($value);
|
||
//return FALSE;
|
||
return '0'; // incorrect, but for keep compatibility
|
||
}
|
||
|
||
if ($value < 0) {
|
||
$neg = TRUE;
|
||
$value *= -1;
|
||
} else {
|
||
$neg = FALSE;
|
||
}
|
||
$sizes = [ '', 'k', 'M', 'G', 'T', 'P', 'E' ];
|
||
$ext = $sizes[0];
|
||
for ($i = 1; (($i < count($sizes)) && ($value >= 1024)); $i++) {
|
||
$value /= 1024;
|
||
$ext = $sizes[$i];
|
||
}
|
||
|
||
if ($neg) {
|
||
$value *= -1;
|
||
}
|
||
|
||
return format_number_short(round($value, $round), $sf) . $ext;
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function format_number($value, $base = '1000', $round = 2, $sf = 3) {
|
||
if ($base == '1000') {
|
||
return format_si($value, $round, $sf);
|
||
}
|
||
return format_bi($value, $round, $sf);
|
||
}
|
||
|
||
// DOCME needs phpdoc block
|
||
// TESTME needs unit testing
|
||
function format_value($value, $format = '', $round = 2, $sf = 3) {
|
||
|
||
switch (strtolower((string)$format)) {
|
||
case 'si':
|
||
case '1000':
|
||
$value = format_si($value, $round, $sf);
|
||
break;
|
||
case 'bi':
|
||
case '1024':
|
||
$value = format_bi($value, $round, $sf);
|
||
break;
|
||
|
||
case 'shorttime':
|
||
$value = format_uptime($value, 'short');
|
||
break;
|
||
|
||
case 'uptime':
|
||
case 'time':
|
||
$value = format_uptime($value);
|
||
break;
|
||
|
||
default:
|
||
if ($value === TRUE) {
|
||
return 'TRUE';
|
||
}
|
||
if ($value === FALSE) {
|
||
return 'FALSE';
|
||
}
|
||
if (is_null($value)) {
|
||
return 'NULL';
|
||
}
|
||
if (safe_empty($value)) {
|
||
return '""';
|
||
}
|
||
$num = clean_number($value);
|
||
if (is_numeric($num)) {
|
||
$orig = $num;
|
||
$value = sprintf("%01.{$round}f", $num);
|
||
if (abs($orig) > 0 && preg_match('/^\-?0\.0+$/', $value)) {
|
||
// prevent show small values as zero
|
||
// ie 0.000627 as 0.00
|
||
//r($orig);
|
||
//r($value);
|
||
$value = format_number_short($orig, $sf);
|
||
//r($value);
|
||
} else {
|
||
$value = preg_replace(['/\.0+$/', '/(\.\d)0+$/'], '\1', $value);
|
||
}
|
||
}
|
||
}
|
||
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Format a number so that it contains at most $sf significant figures, including at most one decimal point.
|
||
* This version does not use scientific notation.
|
||
*
|
||
* Examples:
|
||
* - 723.42 (sf=3) -> 723
|
||
* - 72.34 (sf=3) -> 72.3
|
||
* - 2.23 (sf=3) -> 2.23
|
||
* - 0.00001 (sf=3) -> 0.000
|
||
*
|
||
* @param float|int|string $num The input number to format.
|
||
* @param int $sf The number of significant figures to keep.
|
||
*
|
||
* @return string The formatted number as a string.
|
||
*/
|
||
function format_number_short($num, $sf) {
|
||
// remove non-numeric chars from end of string
|
||
$number = clean_number($num);
|
||
if (!is_numeric($number)) {
|
||
// passed not numeric, return original
|
||
return $num;
|
||
}
|
||
if (is_intnum($number)) {
|
||
return (int)$number ? (string)$number : '0';
|
||
}
|
||
|
||
// Next part only for float numbers
|
||
$exponent = floor(log10(abs($number)));
|
||
|
||
if ($exponent >= -$sf && $exponent < $sf) {
|
||
$mantissa = $number / (10 ** $exponent);
|
||
$formatted_number = number_format($mantissa * (10 ** $exponent), $sf - $exponent - 1, '.', '');
|
||
} elseif ($exponent < 0) {
|
||
$formatted_number = number_format($number, $sf - $exponent - 1, '.', '');
|
||
// numbers less 0.01
|
||
} else {
|
||
$formatted_number = number_format($number, $sf - 1, '.', '');
|
||
}
|
||
|
||
return trim_number($formatted_number);
|
||
}
|
||
|
||
/**
|
||
* Clean commas and non-numeric chars
|
||
*
|
||
* @param mixed $num Numeric value
|
||
* @return float|int|string
|
||
*/
|
||
function clean_number($num) {
|
||
if (!is_string($num) || is_numeric($num)) {
|
||
return $num;
|
||
}
|
||
|
||
// remove non-numeric chars from end of string
|
||
$num = preg_replace('/\D+$/', '', $num);
|
||
|
||
// number_format() by default return numbers with comma
|
||
// number_format('7619627.0010', 4) -> 7,619,627.0010
|
||
if (str_contains($num, ',')) {
|
||
//var_dump($a);
|
||
return str_replace(',', '', $num);
|
||
}
|
||
return $num;
|
||
}
|
||
|
||
/**
|
||
* Trim unimportant zeroes from string float number,
|
||
* examples:
|
||
* 1.0 -> 1
|
||
* 1.020 -> 1.02
|
||
*
|
||
* @param float|int|string $number
|
||
*
|
||
* @return float|int|string
|
||
*/
|
||
function trim_number($number) {
|
||
if (!is_string($number)) {
|
||
return $number;
|
||
}
|
||
$number = clean_number($number);
|
||
return str_contains($number, '.') ? rtrim(rtrim($number, '0'), '.') : $number;
|
||
}
|
||
|
||
/**
|
||
* Is Valid Hostname
|
||
*
|
||
* See: http://stackoverflow.com/a/4694816
|
||
* http://stackoverflow.com/a/2183140
|
||
*
|
||
* The Internet standards (Request for Comments) for protocols mandate that
|
||
* component hostname labels may contain only the ASCII letters 'a' through 'z'
|
||
* (in a case-insensitive manner), the digits '0' through '9', and the hyphen
|
||
* ('-'). The original specification of hostnames in RFC 952, mandated that
|
||
* labels could not start with a digit or with a hyphen, and must not end with
|
||
* a hyphen. However, a subsequent specification (RFC 1123) permitted hostname
|
||
* labels to start with digits. No other symbols, punctuation characters, or
|
||
* white space are permitted. While a hostname may not contain other characters,
|
||
* such as the underscore character (_), other DNS names may contain the underscore
|
||
*
|
||
* @param string $hostname
|
||
* @param bool $fqdn
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_valid_hostname($hostname, $fqdn = FALSE) {
|
||
// Pre-check if hostname is FQDN
|
||
if ($fqdn && !preg_match('/\.(xn\-\-[a-z0-9]{2,}|[a-z]{2,})$/i', $hostname)) {
|
||
return FALSE;
|
||
}
|
||
|
||
return (preg_match("/^(_?[a-z\d](-*[_a-z\d])*)(\.(_?[a-z\d](-*[_a-z\d])*))*$/i", $hostname) // valid chars check
|
||
&& preg_match("/^.{1,253}$/", $hostname) // overall length check
|
||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $hostname)); // length of each label
|
||
}
|
||
|
||
/**
|
||
* Correct alternative for is_int(), is_integer() and ctype_digit() for validated integer numbers.
|
||
* Work with string and numbers.
|
||
*
|
||
* @param string|mixed $value
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_intnum($value) {
|
||
if (!is_numeric($value)) {
|
||
return FALSE;
|
||
}
|
||
$value = (string)$value;
|
||
if ($value[0] === '-') {
|
||
$value = substr($value, 1);
|
||
} // negative number
|
||
return ctype_digit($value);
|
||
}
|
||
|
||
/**
|
||
* Validate returned values for common parameters like hardware/version/serial/location.
|
||
*
|
||
* @param string $string
|
||
* @param string $type
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_valid_param($string, $type = '') {
|
||
|
||
// Empty or not string is invalid
|
||
if (!(is_string($string) || is_numeric($string)) || safe_empty($string)) {
|
||
print_debug("Detected empty value for param '$type'.");
|
||
return FALSE;
|
||
}
|
||
|
||
// --, **, .., **--.--**
|
||
$poor_default_pattern = '/^[\*\.\-]+$/';
|
||
// single words with possible brackets: <none>, (none), Unknown
|
||
$poor_words_pattern = 'unknown|uninitialized|private|public|default|test|none|unset|not set|not available|default string|none|empty|snmpv2|n/?a|1234567890|0123456789|\?';
|
||
// start/end for a poor words pattern (brackets)
|
||
$poor_pattern_start = '!^[<\\\(]*\s*('; // <, \, (
|
||
$poor_pattern_end = ')\s*[>\\\)]*$!i'; // >, \, )
|
||
|
||
switch (strtolower($type)) {
|
||
case 'asset_tag':
|
||
case 'hardware':
|
||
case 'vendor':
|
||
case 'serial':
|
||
case 'version':
|
||
case 'revision':
|
||
// extra words for sysLocation
|
||
$poor_snmp_pattern = $poor_pattern_start . $poor_words_pattern . '|sim|No Asset Tag|Tag 12345' . $poor_pattern_end;
|
||
$poor_snmp_contains = [ ' not set', 'denied', 'No Such' ];
|
||
$valid = ctype_print($string) &&
|
||
!(str_istarts($string, [ 'Not Avail', 'Not Specified', 'To be filled by O.E.M.' ]) ||
|
||
str_contains_array($string, $poor_snmp_contains) ||
|
||
preg_match($poor_default_pattern, $string) ||
|
||
preg_match($poor_snmp_pattern, $string));
|
||
break;
|
||
|
||
case 'mac':
|
||
// Note 00:00:00:00:00:00 still valid mac address
|
||
$valid = !safe_empty(mac_zeropad($string));
|
||
break;
|
||
|
||
case 'ip':
|
||
$valid = get_ip_version($string);
|
||
break;
|
||
|
||
case 'location':
|
||
case 'syslocation':
|
||
// derp cmd for grep locations:
|
||
// egrep -r --include="*.snmprec" '^1.3.6.1.2.1.1.6.0' . | awk -F '|' '{print $3}' | sort | uniq -c | sort -n
|
||
// extra words for sysLocation
|
||
$poor_snmp_pattern = $poor_pattern_start . $poor_words_pattern .
|
||
'|office|address|here|location|snmplocation|syslocation|(No|System) Location' .
|
||
$poor_pattern_end;
|
||
$poor_snmp_contains = [ ' not set', 'Sitting on the Dock of the Bay', 'Right here, right now', 'edit /etc/snmp/snmpd.conf' ];
|
||
$valid = strlen($string) > 4 && !(str_contains_array($string, $poor_snmp_contains) || preg_match($poor_snmp_pattern, $string));
|
||
break;
|
||
|
||
case 'contact':
|
||
case 'syscontact':
|
||
$valid = !(preg_match($poor_default_pattern, $string) ||
|
||
preg_match($poor_pattern_start . $poor_words_pattern . $poor_pattern_end, $string));
|
||
break;
|
||
|
||
case 'sysobjectid':
|
||
$valid = preg_match('/^\.?\d+(\.\d+)*$/', $string);
|
||
break;
|
||
|
||
case 'type':
|
||
$valid = array_key_exists($string, (array)$GLOBALS['config']['devicetypes']);
|
||
break;
|
||
|
||
case 'port':
|
||
case 'snmp_port':
|
||
// port 0 also valid, but we exclude because it reserved
|
||
$valid = is_intnum($string) && $string > 0 && $string <= 65353;
|
||
break;
|
||
|
||
case 'filename':
|
||
$len = strlen($string);
|
||
if ($len > 255 || !$len) {
|
||
$valid = FALSE;
|
||
} else {
|
||
$valid = strpbrk($string, '|\'\\?*&<";:>+[]=/') === FALSE;
|
||
}
|
||
break;
|
||
|
||
case 'path':
|
||
$valid = preg_match(OBS_PATTERN_PATH_UNIX, $string);
|
||
if (!$valid) {
|
||
$valid = preg_match(OBS_PATTERN_PATH_WIN, $string);
|
||
}
|
||
break;
|
||
|
||
case 'posix_username':
|
||
// strict posix (https://unix.stackexchange.com/questions/157426/what-is-the-regex-to-validate-linux-users):
|
||
// ^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$
|
||
$valid = preg_match('/^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$/', $string);
|
||
break;
|
||
|
||
case 'username':
|
||
// posix 32 chars
|
||
// windows/ldap 20 chars
|
||
// pre-windows2000 256 chars
|
||
//$valid = strlen($string) <= 256 && preg_match('/^\w[\w\-\\\\]+\$?$/u', $string); // allow utf8 usernames
|
||
$valid = preg_match('/^[\w!][\w@!#^~+\$\-\.\ \\\\]{0,254}[\w\$]$/u', $string); // allow utf8 usernames
|
||
break;
|
||
|
||
case 'password':
|
||
$valid = preg_match('/^[[:print:]]+$/u', $string); // allow any printable utf8
|
||
break;
|
||
|
||
case 'snmp_community':
|
||
// allow all common latin and special chars
|
||
$valid = preg_match('/^[\w\ %!@#\$%\^&\*\(\)_\-\+~`\[\]\{\}\|\\\\<>,\.\/\?;:]{1,32}$/', $string);
|
||
break;
|
||
|
||
case 'snmp_timeout':
|
||
$valid = is_intnum($string) && $string > 0 && $string <= 120;
|
||
break;
|
||
|
||
case 'snmp_retries':
|
||
$valid = is_intnum($string) && $string > 0 && $string <= 10;
|
||
break;
|
||
|
||
case 'snmp_authalgo':
|
||
// MD5|SHA|SHA-224|SHA-256|SHA-384|SHA-512
|
||
$valid = preg_match('/^(md5|sha(\-?(224|256|384|512))?)$/i', $string);
|
||
break;
|
||
|
||
case 'snmp_cryptoalgo':
|
||
// DES|AES|AES-192|AES-192-C|AES-256|AES-256-C
|
||
$valid = preg_match('/^(des|aes(\-?(192|256)(\-?c)?)?)$/i', $string);
|
||
break;
|
||
|
||
default:
|
||
// --, **, .., **--.--**
|
||
$valid = !preg_match($poor_default_pattern, $string);
|
||
}
|
||
|
||
if (!$valid) {
|
||
print_debug("Detected invalid value '$string' for param '$type'.");
|
||
}
|
||
|
||
return (bool)$valid;
|
||
}
|
||
|
||
/**
|
||
* BOOLEAN safe function to check if hostname resolves as IPv4 or IPv6 address
|
||
*
|
||
* @param string $hostname
|
||
* @param string|array $options all - request any record ipv4/ipv6, ipv4 or a - only ipv4, ipv6 or aaaa - only ipv6
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_domain_resolves($hostname, $options = 'all') {
|
||
return (is_valid_hostname($hostname) && gethostbyname6($hostname, $options));
|
||
}
|
||
|
||
/**
|
||
* Get host record from /etc/hosts
|
||
*
|
||
* @param string $host Hostname for resolve
|
||
* @param string|array $options all - request any record ipv4/ipv6, ipv4 or a - only ipv4, ipv6 or aaaa - only ipv6
|
||
*
|
||
* @return string|false
|
||
*/
|
||
function ip_from_hosts($host, $options = 'all') {
|
||
$host = strtolower($host);
|
||
|
||
$try_a = array_value_exist($options, [ 'all', 'ipv4', 'a' ]);
|
||
$try_aaaa = array_value_exist($options, [ 'all', 'ipv6', 'aaaa' ]);
|
||
|
||
if (OBS_DEBUG > 1) {
|
||
if ($try_a && $try_aaaa) {
|
||
$debug_msg = 'IPv4/IPv6';
|
||
} elseif ($try_a) {
|
||
$debug_msg = 'IPv4 only';
|
||
} else {
|
||
$debug_msg = 'IPv6 only';
|
||
}
|
||
print_cli("Try resolve '$host' in /etc/hosts as $debug_msg..\n");
|
||
}
|
||
|
||
try {
|
||
foreach (new SplFileObject('/etc/hosts') as $line) {
|
||
// skip empty and comments
|
||
if (str_contains($line, '#')) {
|
||
// remove inline comments
|
||
$line = explode('#', $line, 2)[0];
|
||
}
|
||
$line = trim($line);
|
||
if (safe_empty($line)) {
|
||
continue;
|
||
}
|
||
|
||
$hosts = preg_split('/\s/', strtolower($line), -1, PREG_SPLIT_NO_EMPTY);
|
||
|
||
//print_debug_vars($hosts);
|
||
$ip = array_shift($hosts);
|
||
//$hosts = array_map('strtolower', $d);
|
||
if (in_array($host, $hosts, TRUE)) {
|
||
if (($try_a && str_contains($ip, '.')) ||
|
||
($try_aaaa && str_contains($ip, ':'))) {
|
||
|
||
print_debug("Host '$host' found in /etc/hosts: $ip");
|
||
return $ip;
|
||
}
|
||
}
|
||
}
|
||
} catch (Exception $e) {
|
||
print_warning("Could not open the file /etc/hosts! This file should be world readable, also check that SELinux is not in enforcing mode.");
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/**
|
||
* Same as gethostbyname(), but work with both IPv4 and IPv6.
|
||
* Get the IPv4 or IPv6 address corresponding to a given Internet hostname.
|
||
* By default, return IPv4 address (A record) if exist,
|
||
* else IPv6 address (AAAA record) if exist.
|
||
* For get only IPv6 record use gethostbyname6($hostname, 'ipv6').
|
||
*
|
||
* @param string $host
|
||
* @param string|array $options all - request any record ipv4/ipv6, ipv4 or a - only ipv4, ipv6 or aaaa - only ipv6
|
||
*
|
||
* @return false|mixed
|
||
*/
|
||
function gethostbyname6($host, $options = 'all') {
|
||
// get AAAA record for $host
|
||
// if option ipv4 is set and AAAA fails, it tries for A
|
||
// the first match found is returned
|
||
// otherwise returns FALSE
|
||
|
||
if ($dns = gethostbynamel6($host, $options, TRUE)) {
|
||
return array_shift($dns);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/**
|
||
* Same as gethostbynamel(), but work with both IPv4 and IPv6.
|
||
* By default, returns both IPv4/IPv6 addresses (A and AAAA records),
|
||
* for get only IPv6 addresses use gethostbynamel6($hostname, 'ipv6').
|
||
*
|
||
* @param string $host
|
||
* @param string|array $options all - request any record ipv4/ipv6, ipv4 or a - only ipv4, ipv6 or aaaa - only ipv6
|
||
* @param bool $first // Return first found record, for gethostbyname6()
|
||
*
|
||
* @return array|false
|
||
*/
|
||
function gethostbynamel6($host, $options = 'all', $first = FALSE) {
|
||
|
||
$try_a = array_value_exist($options, [ 'all', 'ipv4', 'a' ]);
|
||
$try_aaaa = array_value_exist($options, [ 'all', 'ipv6', 'aaaa' ]);
|
||
|
||
// get AAAA records for $host,
|
||
// if $try_a is true, if AAAA fails, it tries for A
|
||
// results are returned in an array of ips found matching type
|
||
// otherwise returns FALSE
|
||
|
||
$ip6 = [];
|
||
$ip4 = [];
|
||
|
||
if ($try_a) {
|
||
// First try /etc/hosts (v4)
|
||
$etc4 = ip_from_hosts($host, 'ipv4');
|
||
if ($etc4) {
|
||
$ip4[] = $etc4;
|
||
|
||
if ($first) {
|
||
return $ip4;
|
||
}
|
||
}
|
||
|
||
// Second try /etc/hosts (v6)
|
||
$etc6 = $try_aaaa ? ip_from_hosts($host, 'ipv6') : FALSE;
|
||
if ($etc6) {
|
||
$ip6[] = $etc6;
|
||
|
||
if ($first) {
|
||
return $ip6;
|
||
}
|
||
}
|
||
|
||
// Separate A and AAAA queries, see: https://www.mail-archive.com/observium@observium.org/msg09239.html
|
||
$dns = dns_get_record($host, DNS_A);
|
||
print_debug_vars($dns);
|
||
if (!is_array($dns)) {
|
||
$dns = [];
|
||
}
|
||
|
||
// Request AAAA record (when requested only first record and A record exists, skip)
|
||
if ($try_aaaa && !($first && count($dns))) {
|
||
$dns6 = dns_get_record($host, DNS_AAAA);
|
||
print_debug_vars($dns6);
|
||
if (is_array($dns6)) {
|
||
$dns = array_merge($dns, $dns6);
|
||
}
|
||
}
|
||
} elseif ($try_aaaa) {
|
||
// First try /etc/hosts (v6)
|
||
$etc6 = ip_from_hosts($host, 'ipv6');
|
||
if ($etc6) {
|
||
$ip6[] = $etc6;
|
||
|
||
if ($first) {
|
||
return $ip6;
|
||
}
|
||
}
|
||
$dns = dns_get_record($host, DNS_AAAA);
|
||
print_debug_vars($dns);
|
||
} else {
|
||
// Not A or AAAA record requested
|
||
return FALSE;
|
||
}
|
||
|
||
foreach ($dns as $record) {
|
||
switch ($record['type']) {
|
||
case 'A':
|
||
$ip4[] = $record['ip'];
|
||
break;
|
||
case 'AAAA':
|
||
$ip6[] = $record['ipv6'];
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ($try_a && count($ip4)) {
|
||
// Merge ipv4 & ipv6
|
||
$ip6 = array_merge($ip4, $ip6);
|
||
}
|
||
|
||
if (count($ip6)) {
|
||
return $ip6;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/**
|
||
* Get the Internet hostname corresponding to a given IP address.
|
||
* Support IPv4 and IPv6.
|
||
*
|
||
* @param string $ip IPv4/IPv6 address
|
||
*
|
||
* @return string|false PTR name or FALSE
|
||
*/
|
||
function gethostbyaddr6($ip) {
|
||
|
||
$resolver = new Net_DNS2_Resolver();
|
||
try {
|
||
if ($response = $resolver->query($ip, 'PTR')) {
|
||
return $response->answer[0]->ptrdname;
|
||
}
|
||
} catch (Net_DNS2_Exception $e) {
|
||
print_debug("gethostbyaddr6($ip) failed: " . $e->getMessage() . PHP_EOL);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
function elapsed_time($microtime_start, $precision = NULL) {
|
||
$time_elapsed = microtime(TRUE) - $microtime_start;
|
||
|
||
return is_numeric($precision) ? round($time_elapsed, $precision) : $time_elapsed;
|
||
}
|
||
|
||
/**
|
||
* Return named unix times from now.
|
||
* Note, 'now' static unixtime over process run.
|
||
* For undate run get_time('new')
|
||
*
|
||
* @param string $period Named time from now, ie: fiveminute, twelvehour, year
|
||
* @param bool $future If TRUE, return unix time in the future.
|
||
*
|
||
* @return int
|
||
*/
|
||
function get_time($period = 'now', $future = FALSE) {
|
||
global $config;
|
||
|
||
/*
|
||
$config['time']['fiveminute'] = $config['time']['now'] - 300; //time() - (5 * 60);
|
||
$config['time']['fourhour'] = $config['time']['now'] - 14400; //time() - (4 * 60 * 60);
|
||
$config['time']['sixhour'] = $config['time']['now'] - 21600; //time() - (6 * 60 * 60);
|
||
$config['time']['twelvehour'] = $config['time']['now'] - 43200; //time() - (12 * 60 * 60);
|
||
$config['time']['day'] = $config['time']['now'] - 86400; //time() - (24 * 60 * 60);
|
||
$config['time']['twoday'] = $config['time']['now'] - 172800; //time() - (2 * 24 * 60 * 60);
|
||
$config['time']['week'] = $config['time']['now'] - 604800; //time() - (7 * 24 * 60 * 60);
|
||
$config['time']['twoweek'] = $config['time']['now'] - 1209600; //time() - (2 * 7 * 24 * 60 * 60);
|
||
$config['time']['month'] = $config['time']['now'] - 2678400; //time() - (31 * 24 * 60 * 60);
|
||
$config['time']['twomonth'] = $config['time']['now'] - 5356800; //time() - (2 * 31 * 24 * 60 * 60);
|
||
$config['time']['threemonth'] = $config['time']['now'] - 8035200; //time() - (3 * 31 * 24 * 60 * 60);
|
||
$config['time']['sixmonth'] = $config['time']['now'] - 16070400; //time() - (6 * 31 * 24 * 60 * 60);
|
||
$config['time']['year'] = $config['time']['now'] - 31536000; //time() - (365 * 24 * 60 * 60);
|
||
$config['time']['twoyear'] = $config['time']['now'] - 63072000; //time() - (2 * 365 * 24 * 60 * 60);
|
||
$config['time']['threeyear'] = $config['time']['now'] - 94608000; //time() - (3 * 365 * 24 * 60 * 60);
|
||
*/
|
||
|
||
// Set times needed by loads of scripts
|
||
$config['time']['now'] = $config['time']['now'] ?? time();
|
||
|
||
$period = empty($period) ? 'now' : strtolower(trim($period));
|
||
|
||
if ($period === 'now') {
|
||
return $config['time']['now'];
|
||
}
|
||
if ($period === 'new') {
|
||
return $config['time']['now'] = time();
|
||
}
|
||
$time = $config['time']['now'];
|
||
|
||
$multipliers = [
|
||
'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5, 'six' => 6,
|
||
'seven' => 7, 'eight' => 8, 'nine' => 9, 'ten' => 10, 'eleven' => 11, 'twelve' => 12
|
||
];
|
||
$times = [
|
||
'year' => 31536000,
|
||
'month' => 2678400,
|
||
'week' => 604800,
|
||
'day' => 86400,
|
||
'hour' => 3600,
|
||
'minute' => 60
|
||
];
|
||
$time_pattern = '/^(?<multiplier>' . implode('|', array_keys($multipliers)) . '|\d+)?(?<time>' . implode('|', array_keys($times)) . ')s?$/';
|
||
//r($time_pattern);
|
||
if (preg_match($time_pattern, $period, $matches)) {
|
||
$multiplier = $multipliers[$matches['multiplier']] ?? (is_numeric($matches['multiplier']) ? $matches['multiplier'] : 1);
|
||
|
||
$diff = $multiplier * $times[$matches['time']];
|
||
|
||
if ($future) {
|
||
$time += $diff;
|
||
} else {
|
||
$time -= $diff;
|
||
}
|
||
}
|
||
|
||
return (int)$time;
|
||
}
|
||
|
||
/**
|
||
* Format date string.
|
||
* If passed 'now' return formatted current datetime.
|
||
*
|
||
* This function convert date/time string to format from
|
||
* config option $config['timestamp_format'].
|
||
* If date/time not detected in string, function return original string.
|
||
* Example conversions to format 'd-m-Y H:i':
|
||
* '2012-04-18 14:25:01' -> '18-04-2012 14:25'
|
||
* 'Star wars' -> 'Star wars'
|
||
*
|
||
* @param string $str
|
||
*
|
||
* @return string
|
||
*/
|
||
// TESTME needs unit testing
|
||
function format_timestamp($str = 'now') {
|
||
global $config;
|
||
|
||
if ($str === 'now') {
|
||
// Use for get formatted current time
|
||
$timestamp = get_time($str);
|
||
} elseif (($timestamp = strtotime($str)) === FALSE) {
|
||
return $str;
|
||
}
|
||
|
||
return date($config['timestamp_format'], $timestamp);
|
||
}
|
||
|
||
/**
|
||
* Format unixtime.
|
||
*
|
||
* This function convert unixtime string to format from
|
||
* config option $config['timestamp_format'].
|
||
* Can take an optional format parameter, which is passed to date();
|
||
*
|
||
* @param string $time Unixtime in seconds since the Unix Epoch (also allowed microseconds)
|
||
* @param string $format Common date format
|
||
*
|
||
* @return string
|
||
*/
|
||
// TESTME needs unit testing
|
||
function format_unixtime($time, $format = NULL) {
|
||
|
||
[ $sec, $usec ] = explode('.', (string)$time);
|
||
if (!safe_empty($usec)) {
|
||
$date = date_create_from_format('U.u', number_format($time, 6, '.', ''));
|
||
} else {
|
||
$date = date_create_from_format('U', $sec);
|
||
}
|
||
|
||
// If something wrong with create data object, just return empty string (and yes, we never use zero unixtime)
|
||
if (!$date || $time == 0) {
|
||
return '';
|
||
}
|
||
|
||
// Set correct timezone
|
||
$tz = get_timezone();
|
||
//r($tz);
|
||
try {
|
||
$date_timezone = new DateTimeZone(str_replace(':', '', $tz['php']));
|
||
//$date_timezone = new DateTimeZone($tz['php_name']);
|
||
$date->setTimeZone($date_timezone);
|
||
} catch (Throwable $throwable) {
|
||
print_debug($throwable -> getMessage());
|
||
}
|
||
//r($date);
|
||
|
||
if (safe_empty($format)) {
|
||
$format = $GLOBALS['config']['timestamp_format'];
|
||
}
|
||
|
||
return date_format($date, (string)$format);
|
||
}
|
||
|
||
/**
|
||
* Reformat US-based dates to display based on $config['date_format']
|
||
*
|
||
* Supported input formats:
|
||
* DD/MM/YYYY
|
||
* DD/MM/YY
|
||
*
|
||
* Handling of YY -> YYYY years is passed on to PHP's strtotime, which
|
||
* is currently cut off at 1970/2069.
|
||
*
|
||
* @param string $date Erroneous date format
|
||
*
|
||
* @return string $date
|
||
*/
|
||
function reformat_us_date($date)
|
||
{
|
||
global $config;
|
||
|
||
$date = trim($date);
|
||
if (preg_match('!^\d{1,2}/\d{1,2}/(\d{2}|\d{4})$!', $date)) {
|
||
// Only date
|
||
$format = $config['date_format'];
|
||
} elseif (preg_match('!^\d{1,2}/\d{1,2}/(\d{2}|\d{4})\s+\d{1,2}:\d{1,2}(:\d{1,2})?$!', $date)) {
|
||
// Date + time
|
||
$format = $config['timestamp_format'];
|
||
} else {
|
||
return $date;
|
||
}
|
||
|
||
return date($format, strtotime($date));
|
||
}
|
||
|
||
/**
|
||
* This function convert human written Uptime to seconds.
|
||
* Opposite function for format_uptime().
|
||
*
|
||
* Also applicable for some uptime formats in MIB, like EigrpUpTimeString:
|
||
* 'hh:mm:ss', reflecting hours, minutes, and seconds
|
||
* If the up time is greater than 24 hours, is less precise and
|
||
* the minutes and seconds are not reflected. Instead only the days
|
||
* and hours are shown and the string will be formatted like this: 'xxxdxxh'
|
||
*
|
||
* @param string $uptime Uptime in human readable string or timetick
|
||
*
|
||
* @return int Uptime in seconds
|
||
*/
|
||
function uptime_to_seconds($uptime)
|
||
{
|
||
if (str_contains($uptime, 'Wrong Type')) {
|
||
// Wrong Type (should be Timeticks): 1632295600
|
||
$seconds = timeticks_to_sec($uptime);
|
||
} elseif (str_contains($uptime, ':') &&
|
||
!preg_match('/[a-zA-Z]/', $uptime)) { // exclude strings: 315 days18:50:04
|
||
$seconds = timeticks_to_sec($uptime);
|
||
} else {
|
||
$uptime = preg_replace('/^[a-z]+ */i', '', $uptime); // Clean "up" string
|
||
$seconds = age_to_seconds($uptime);
|
||
}
|
||
|
||
return $seconds;
|
||
}
|
||
|
||
/**
|
||
* Convert age string to seconds.
|
||
*
|
||
* This function converts age string to seconds.
|
||
* If age is numeric, then it is in seconds.
|
||
* The supplied age accepts values such as 31d, 240h, 1.5d etc.
|
||
* Accepted age scales are:
|
||
* y (years), M (months), w (weeks), d (days), h (hours), m (minutes), s (seconds), ms (milliseconds).
|
||
* NOTE, for month use CAPITAL 'M'
|
||
* With wrong and negative returns 0
|
||
*
|
||
* '3y 4M 6w 5d 3h 1m 3s' -> 109191663
|
||
* '184 days 22 hrs 02 min 38 sec' ->
|
||
* '3y4M6w5d3h1m3s' -> 109191663
|
||
* '1.5w' -> 907200
|
||
* -886732 -> 0
|
||
* 'Star wars' -> 0
|
||
*
|
||
* @param string|int $age
|
||
* @param bool $float When TRUE, return value as float
|
||
*
|
||
* @return int|float
|
||
*/
|
||
// TESTME needs unit testing
|
||
function age_to_seconds($age, $float = FALSE) {
|
||
$age = trim($age);
|
||
|
||
if (is_numeric($age)) {
|
||
if ($age > 0) {
|
||
return $float ? (float)$age : (int)$age;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
$pattern = '/^';
|
||
$pattern .= '(?:(?<years>\d+(?:\.\d+)*)\ ?(?:[yY][eE][aA][rR]\(?[sS]?\)?|[yY])[,\ ]*)*'; // y (years)
|
||
$pattern .= '(?:(?<months>\d+(?:\.\d+)*)\ ?(?:[mM][oO][nN][tT][hH]\(?[sS]?\)?|M)[,\ ]*)*'; // M (months)
|
||
$pattern .= '(?:(?<weeks>\d+(?:\.\d+)*)\ ?(?:[wW][eE][eE][kK]\(?[sS]?\)?|[wW])[,\ ]*)*'; // w (weeks)
|
||
$pattern .= '(?:(?<days>\d+(?:\.\d+)*)\ ?(?:[dD][aA][yY]\(?[sS]?\)?|[dD])[,\ ]*)*'; // d (days)
|
||
$pattern .= '(?:(?:';
|
||
$pattern .= '(?:(?<hours>\d+(?:\.\d+)*)\ ?(?:[hH][oO][uU][rR]\(?[sS]?\)?|[hH][rR][sS]|[hH])[,\ ]*)*'; // h (hours)
|
||
$pattern .= '(?:(?<minutes>\d+(?:\.\d+)*)\ ?(?:[mM][iI][nN][uU][tT][eE]\(?[sS]?\)?|[mM][iI][nN]|m)[,\ ]*)*'; // m (minutes)
|
||
$pattern .= '(?:(?<seconds>\d+(?:\.\d+)*)\ ?(?:[sS][eE][cC][oO][nN][dD]\(?[sS]?\)?|[sS][eE][cC]|[sS])[,\ ]*)*'; // s (seconds)
|
||
$pattern .= '(?:(?<mseconds>\d+(?:\.\d+)*)\ ?(?:[mM][iI][lL][lL][iI][sS][eE][cC][oO][nN][dD]\(?[sS]?\)?|[mM][sS][eE][cC]|[mM][sS]))*'; // ms (milliseconds)
|
||
$pattern .= '|(?:(?<hours>\d{1,2}):(?<minutes>\d{1,2}):(?<seconds>\d{1,2}(?:\.\d+)*))'; // hh:mm:ss.s
|
||
$pattern .= '))';
|
||
$pattern .= '$/J';
|
||
//print_vars($pattern); echo PHP_EOL;
|
||
|
||
if (!empty($age) && preg_match($pattern, $age, $matches)) {
|
||
$ages = [
|
||
'years' => 31536000, // year = 365 * 24 * 60 * 60
|
||
'months' => 2628000, // month = year / 12
|
||
'weeks' => 604800, // week = 7 days
|
||
'days' => 86400, // day = 24 * 60 * 60
|
||
'hours' => 3600, // hour = 60 * 60
|
||
'minutes' => 60, // minute = 60
|
||
'mseconds' => 0.001, // milliseconds = 60
|
||
];
|
||
$seconds = isset($matches['seconds']) ? (float)$matches['seconds'] : 0;
|
||
foreach ($ages as $period => $scale) {
|
||
if (isset($matches[$period])) {
|
||
$seconds += (float)$matches[$period] * $scale;
|
||
}
|
||
}
|
||
|
||
return $float ? (float)$seconds : (int)$seconds;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Convert age string to unixtime.
|
||
*
|
||
* This function convert age string to unixtime.
|
||
*
|
||
* Description and notes same as for age_to_seconds()
|
||
*
|
||
* Additional check if $age more than minimal age in seconds
|
||
*
|
||
* '3y 4M 6w 5d 3h 1m 3s' -> time() - 109191663
|
||
* '3y4M6w5d3h1m3s' -> time() - 109191663
|
||
* '1.5w' -> time() - 907200
|
||
* -886732 -> 0
|
||
* 'Star wars' -> 0
|
||
*
|
||
* @param string|int|float $age
|
||
* @param string|int $min_age
|
||
*
|
||
* @return int
|
||
*/
|
||
// TESTME needs unit testing
|
||
function age_to_unixtime($age, $min_age = 1) {
|
||
$age = age_to_seconds($age);
|
||
if ($age >= $min_age) {
|
||
return time() - $age;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Convert an variable to base64 encoded string
|
||
*
|
||
* This function converts any array or other variable to encoded string
|
||
* which can be used in urls.
|
||
* Can use serialize and json(default) methods.
|
||
*
|
||
* NOTE. In PHP < 5.4 json converts UTF-8 characters to Unicode escape sequences
|
||
* also json rounds float numbers (98172397.1234567890 ==> 98172397.123457)
|
||
*
|
||
* @param mixed $var
|
||
* @param string $method
|
||
*
|
||
* @return string
|
||
*/
|
||
function var_encode($var, $method = 'json')
|
||
{
|
||
switch ($method) {
|
||
case 'serialize':
|
||
$string = base64_encode(serialize($var));
|
||
break;
|
||
default:
|
||
//$tmp = json_encode($var, OBS_JSON_ENCODE);
|
||
//echo PHP_EOL . 'precision = ' . ini_get('precision') . "\n";
|
||
//echo 'serialize_precision = ' . ini_get('serialize_precision');
|
||
//echo("\n---\n"); var_dump($var); echo("\n---\n"); var_dump($tmp);
|
||
$string = base64_encode(json_encode($var, OBS_JSON_ENCODE));
|
||
break;
|
||
}
|
||
return $string;
|
||
}
|
||
|
||
/**
|
||
* Decode an previously encoded string by var_encode() to original variable
|
||
*
|
||
* This function converts base64 encoded string to original variable.
|
||
* Can use serialize and json(default) methods.
|
||
* If json/serialize not detected returns original var
|
||
*
|
||
* NOTE. In PHP < 5.4 json converts UTF-8 characters to Unicode escape sequences,
|
||
* also json rounds float numbers (98172397.1234567890 ==> 98172397.123457)
|
||
*
|
||
* @param string $string
|
||
* @param string $method
|
||
*
|
||
* @return mixed
|
||
*/
|
||
function var_decode($string, $method = 'json')
|
||
{
|
||
if (!is_string($string)) {
|
||
// Decode only string vars
|
||
return $string;
|
||
}
|
||
|
||
if ((strlen($string) % 4) > 0) {
|
||
// BASE64 length must be multiple by 4
|
||
return $string;
|
||
}
|
||
|
||
$value = base64_decode($string, TRUE);
|
||
if ($value === FALSE) {
|
||
// This is not base64 string, return original var
|
||
return $string;
|
||
}
|
||
|
||
switch ($method) {
|
||
case 'serialize':
|
||
case 'unserialize':
|
||
if ($value === 'b:0;') {
|
||
return FALSE;
|
||
}
|
||
$decoded = safe_unserialize($value);
|
||
if ($decoded !== FALSE) {
|
||
// Serialized encoded string detected
|
||
return $decoded;
|
||
}
|
||
break;
|
||
default:
|
||
if ($string === 'bnVsbA==') {
|
||
return NULL;
|
||
}
|
||
if (OBS_JSON_DECODE > 0) {
|
||
$decoded = @json_decode($value, TRUE, 512, OBS_JSON_DECODE);
|
||
} else {
|
||
// Prevent to broke on old php (5.3), where supported only 3 params
|
||
$decoded = @json_decode($value, TRUE, 512);
|
||
}
|
||
switch (json_last_error()) {
|
||
case JSON_ERROR_STATE_MISMATCH:
|
||
case JSON_ERROR_SYNTAX:
|
||
// Critical json errors, return original string
|
||
break;
|
||
case JSON_ERROR_NONE:
|
||
default:
|
||
if ($decoded !== NULL) {
|
||
// JSON encoded string detected
|
||
return $decoded;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
// In all other cases return original var
|
||
return $string;
|
||
}
|
||
|
||
/**
|
||
* @param mixed $var
|
||
* @param mixed $true
|
||
*
|
||
* @return bool
|
||
*/
|
||
function get_var_true($var, $true = NULL) {
|
||
if (is_string($var)) {
|
||
$var = strtolower($var);
|
||
}
|
||
return $var === '1' || $var === 1 ||
|
||
$var === 'on' || $var === 'yes' || $var === 'true' ||
|
||
$var === TRUE ||
|
||
// allow extra param for true, ie confirm
|
||
(!empty($true) && $var === $true);
|
||
}
|
||
|
||
/**
|
||
* @param mixed $var
|
||
* @param mixed $false
|
||
*
|
||
* @return bool
|
||
*/
|
||
function get_var_false($var, $false = NULL) {
|
||
if (is_string($var)) {
|
||
$var = strtolower($var);
|
||
}
|
||
return $var === '0' || $var === 0 ||
|
||
$var === 'off' || $var === 'no' || $var === 'false' ||
|
||
$var === FALSE || (is_null($false) && $var === NULL) || // FIXME. I not sure about null, because it's here as alternative for isset
|
||
// allow extra param for false, ie confirm
|
||
(!empty($false) && $var === $false);
|
||
}
|
||
|
||
/**
|
||
* Convert CSV like string to array or keep as is if not csv.
|
||
*
|
||
* @param mixed $str String probably CSV list or encoded.
|
||
* @param bool $encoded If TRUE and string is encoded (by var_encode()) decode it
|
||
*
|
||
* @return array|mixed
|
||
*/
|
||
function get_var_csv($str, $encoded = FALSE)
|
||
{
|
||
if (!is_string($str)) {
|
||
// If variable already array, keep as is
|
||
return $str;
|
||
}
|
||
|
||
// Better to understand quoted vars
|
||
$values = str_getcsv($str);
|
||
if (count($values) === 1) {
|
||
// not comma list, but can be quoted value
|
||
$values = $values[0];
|
||
|
||
// Try decode var if original value not csv
|
||
if ($encoded && $str === $values) {
|
||
$values = var_decode($str);
|
||
} else {
|
||
$values = var_comma_safe($values);
|
||
}
|
||
} else {
|
||
$values = var_comma_safe($values);
|
||
}
|
||
|
||
return $values;
|
||
}
|
||
|
||
function var_comma_safe($value)
|
||
{
|
||
if (is_array($value)) {
|
||
foreach ($value as &$entry) {
|
||
$entry = var_comma_safe($entry);
|
||
}
|
||
return $value;
|
||
}
|
||
|
||
if (str_contains($value, '%1F')) {
|
||
$value = str_replace('%1F', ',', $value); // %1F (US, unit separator) - not defined in HTML 4 standard
|
||
}
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Parse CSV files with or without header, and return a multidimensional array
|
||
*
|
||
* @param string $content
|
||
* @param bool $has_header
|
||
* @param string $separator
|
||
*
|
||
* @return array
|
||
*/
|
||
function parse_csv($content, $has_header = TRUE, $separator = ",") {
|
||
$lines = explode("\n", $content);
|
||
$lines = array_filter(array_map('trim', $lines), 'strlen'); // clean empty lines
|
||
|
||
# If the CSV file has a header, load up the titles into $headers
|
||
if ($has_header) {
|
||
$header = array_shift($lines);
|
||
$headers = array_map('trim', str_getcsv($header, $separator));
|
||
//print_vars($headers);
|
||
}
|
||
|
||
# Process every line
|
||
$result = [];
|
||
foreach ($lines as $line) {
|
||
$csv = array_map('trim', str_getcsv($line, $separator));
|
||
//print_vars($csv);
|
||
if ($has_header) {
|
||
$result[] = array_combine($headers, $csv);
|
||
} else {
|
||
$result[] = $csv;
|
||
}
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Parse number with units to numeric.
|
||
*
|
||
* This function converts numbers with units (e.g. 100MB) to their value
|
||
* in bytes (e.g. 104857600).
|
||
*
|
||
* @param string $str
|
||
* @param int $unit_base Use custom rigid unit base (1000 or 1024)
|
||
*
|
||
* @return float
|
||
*/
|
||
function unit_string_to_numeric($str, $unit_base = NULL) {
|
||
$value = is_string($str) ? trim($str) : $str;
|
||
|
||
// If it's already a number, return original value
|
||
if (is_numeric($value)) {
|
||
return (float)$value;
|
||
}
|
||
// Any not numeric values return as is (array, booleans)
|
||
if (!is_string($value)) {
|
||
return $str;
|
||
}
|
||
|
||
//preg_match('/(\d+\.?\d*)\ ?(\w+)/', $str, $matches);
|
||
$pattern = '/^(?<number>\d+\.?\d*)\ ?(?<prefix>[kmgtpezy]i?)?(?<unit>[a-z]*)$/i';
|
||
preg_match($pattern, $value, $matches);
|
||
|
||
// Error, return original string
|
||
if (!is_numeric($matches['number'])) {
|
||
return $str;
|
||
}
|
||
|
||
// Unit base 1000 or 1024
|
||
$prefix_len = strlen($matches['prefix']);
|
||
$any_unit = FALSE;
|
||
if (in_array($unit_base, [ 1000, 1024 ])) {
|
||
// Use rigid unit base, this interprets any units with hard multiplier base
|
||
$base = (int)$unit_base;
|
||
// Convert any unit, ie 17.3kVA,
|
||
$any_unit = $base === 1000 && $prefix_len === 1;
|
||
} elseif ($prefix_len === 2) {
|
||
// IEC prefixes Ki, Gi, Ti, etc
|
||
$base = 1024;
|
||
} else {
|
||
switch ($matches['unit']) {
|
||
case '':
|
||
case 'B':
|
||
case 'Byte':
|
||
case 'byte':
|
||
$base = 1024;
|
||
break;
|
||
case 'b':
|
||
case 'Bps':
|
||
case 'bit':
|
||
case 'bps':
|
||
$base = 1000;
|
||
break;
|
||
default:
|
||
$base = 1024;
|
||
}
|
||
}
|
||
|
||
// https://en.wikipedia.org/wiki/Binary_prefix
|
||
$prefixes = [
|
||
//'b' => 0,
|
||
'k' => 1, 'ki' => 1,
|
||
'm' => 2, 'mi' => 2,
|
||
'g' => 3, 'gi' => 3,
|
||
't' => 4, 'ti' => 4,
|
||
'p' => 5, 'pi' => 5,
|
||
'e' => 6, 'ei' => 6,
|
||
'z' => 7, 'zi' => 7,
|
||
'y' => 8, 'yi' => 8
|
||
];
|
||
|
||
$power = 0;
|
||
if ($prefix_len) {
|
||
$prefix = strtolower($matches['prefix']);
|
||
if (isset($prefixes[$prefix])) {
|
||
$power = $prefixes[$prefix];
|
||
} else {
|
||
// incorrect prefixes, return original value
|
||
return $str;
|
||
}
|
||
}
|
||
switch ($matches['unit']) {
|
||
case '':
|
||
case 'B':
|
||
case 'Byte':
|
||
case 'Bytes':
|
||
case 'byte':
|
||
case 'bytes':
|
||
$base = $base ?? 1024;
|
||
break;
|
||
|
||
case 'b':
|
||
case 'Bps':
|
||
case 'bit':
|
||
case 'bits':
|
||
case 'bps':
|
||
$base = $base ?? 1000;
|
||
break;
|
||
|
||
default:
|
||
if (!$any_unit) {
|
||
// unknown unit, return original value
|
||
return $str;
|
||
}
|
||
}
|
||
|
||
$multiplier = $base ** $power;
|
||
|
||
return (float)($matches[1] * $multiplier);
|
||
}
|
||
|
||
/**
|
||
* Generate Unique ID from string, based on crc32b hash. This ID unique for specific string and not changed over next call.
|
||
*
|
||
* @param string $string String
|
||
*
|
||
* @return int Unique ID
|
||
*/
|
||
function string_to_id($string)
|
||
{
|
||
return hexdec(hash("crc32b", $string));
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert the value of sensor from known unit to defined SI unit (used in poller/discovery)
|
||
*
|
||
* @param float|string $value Value
|
||
* @param string $unit_from Unit name/symbol for value
|
||
* @param string $class Type of value
|
||
* @param string|array $unit_to Unit name/symbol for convert value (by default used sensor class default unit)
|
||
*
|
||
* @return array Array with values converted to unit_from
|
||
*/
|
||
function value_to_units($value, $unit_from, $class, $unit_to = []) {
|
||
global $config;
|
||
|
||
// Convert symbols to lib supported units
|
||
$unit_from = str_replace([ '<sup>', '</sup>' ], [ '^', '' ], $unit_from); // I.e. mg/m<sup>3</sup> => mg/m^3
|
||
$unit_from = html_entity_decode($unit_from); // I.e. °C => °C
|
||
|
||
// Non numeric values
|
||
if (!is_numeric($value)) {
|
||
return [ $unit_from => $value ];
|
||
}
|
||
|
||
switch ($class) {
|
||
case 'temperature':
|
||
$value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Temperature($value, $unit_from);
|
||
break;
|
||
|
||
case 'pressure':
|
||
$value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Pressure($value, $unit_from);
|
||
break;
|
||
|
||
case 'power':
|
||
case 'dbm':
|
||
$value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Power($value, $unit_from);
|
||
break;
|
||
|
||
case 'waterflow':
|
||
case 'airflow':
|
||
$value_from = new PhpUnitsOfMeasure\PhysicalQuantity\VolumeFlow($value, $unit_from);
|
||
break;
|
||
|
||
case 'velocity':
|
||
$value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Velocity($value, $unit_from);
|
||
break;
|
||
|
||
case 'lifetime':
|
||
case 'uptime':
|
||
case 'time':
|
||
if (empty($unit_from)) {
|
||
$unit_from = 's';
|
||
}
|
||
$value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Time($value, $unit_from);
|
||
break;
|
||
|
||
default:
|
||
// Unknown, return original value
|
||
return [ $unit_from => $value ];
|
||
}
|
||
|
||
// Use our default unit (if not passed)
|
||
if (empty($unit_to) && isset($config['sensor_types'][$class]['symbol'])) {
|
||
$unit_to = $config['sensor_types'][$class]['symbol'];
|
||
}
|
||
|
||
// Convert to units
|
||
$units = [];
|
||
foreach ((array)$unit_to as $to) {
|
||
// Convert symbols to lib supported units
|
||
$tou = str_replace([ '<sup>', '</sup>' ], [ '^', '' ], $to); // I.e. mg/m<sup>3</sup> => mg/m^3
|
||
$tou = html_entity_decode($tou); // I.e. °C => °C
|
||
|
||
$units[$to] = $value_from->toUnit($tou);
|
||
}
|
||
|
||
return $units;
|
||
}
|
||
|
||
/**
|
||
* Replace all newlines in string to space char (except string begin and end)
|
||
*
|
||
* @param string $string Input string
|
||
*
|
||
* @return string Output string without NL characters
|
||
*/
|
||
function nl2space($string)
|
||
{
|
||
if (!is_string($string) || $string == '') {
|
||
return $string;
|
||
}
|
||
|
||
$string = trim($string, "\n\r");
|
||
return preg_replace('/ ?(\r\n|\r|\n) ?/', ' ', $string);
|
||
}
|
||
|
||
/**
|
||
* This noob function replaces windows/mac newline character to unix newline
|
||
*
|
||
* @param string $string Input string
|
||
*
|
||
* @return string Clean output string
|
||
*/
|
||
function nl2nl($string)
|
||
{
|
||
if (!is_string($string) || $string == '') {
|
||
return $string;
|
||
}
|
||
|
||
return preg_replace('/\r\n|\r/', PHP_EOL, $string);
|
||
}
|
||
|
||
/**
|
||
* Microtime
|
||
*
|
||
* This function returns the current Unix timestamp seconds, accurate to the
|
||
* nearest microsecond.
|
||
*
|
||
* @return float
|
||
*/
|
||
function utime()
|
||
{
|
||
return microtime(TRUE);
|
||
}
|
||
|
||
|
||
/**
|
||
* Bitwise checking if flags set
|
||
*
|
||
* Examples:
|
||
* if (is_flag_set(FLAG_A, some_var)) // eg: some_var = 0b01100000000010
|
||
* if (is_flag_set(FLAG_A | FLAG_F | FLAG_L, some_var)) // to check if at least one flag is set
|
||
* if (is_flag_set(FLAG_A | FLAG_J | FLAG_M | FLAG_D, some_var, TRUE)) // to check if all flags are set
|
||
*
|
||
* @param int $flag Checked flags
|
||
* @param int $param Parameter for checking
|
||
* @param bool $all Check all flags
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_flag_set($flag, $param, $all = FALSE)
|
||
{
|
||
$set = $flag & $param;
|
||
|
||
if ($set and !$all) {
|
||
return TRUE;
|
||
} // at least one of the flags passed is set
|
||
elseif ($all and ($set == $flag)) {
|
||
return TRUE;
|
||
} // to check that all flags are set
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/**
|
||
* Return file extension when it exists on a system, limited extensions (default is svg and png).
|
||
*
|
||
* @param string $file
|
||
* @param array|string $ext_list
|
||
* @return false|string
|
||
*/
|
||
function is_file_ext($file, $ext_list = [ 'svg', 'png' ]) {
|
||
global $cache;
|
||
|
||
if (isset($cache['is_file_ext'][$file]) &&
|
||
in_array($cache['is_file_ext'][$file], (array)$ext_list, TRUE)) {
|
||
// reduce is_file() calls
|
||
return $cache['is_file_ext'][$file];
|
||
}
|
||
foreach ((array)$ext_list as $ext) {
|
||
if (is_file($file . ".$ext")) {
|
||
//if ($ext === 'svg') { r($file . ".$ext"); }
|
||
$cache['is_file_ext'][$file] = $ext;
|
||
return $ext;
|
||
}
|
||
}
|
||
return FALSE;
|
||
}
|
||
|
||
/**
|
||
* Checks if a string is composed solely of lower case letters.
|
||
* Primarily used to sanitise strings used for file inclusion
|
||
*
|
||
* @param $string
|
||
*
|
||
* @return false|int
|
||
*/
|
||
function is_alpha($string)
|
||
{
|
||
return preg_match(OBS_PATTERN_ALPHA, $string);
|
||
}
|
||
|
||
/**
|
||
* Convert "smart quotes" to real quotes and emdash into a hyphen.
|
||
*
|
||
* @url https://stackoverflow.com/questions/1262038/how-to-replace-microsoft-encoded-quotes-in-php
|
||
* @param string $string
|
||
*
|
||
* @return string
|
||
*/
|
||
function smart_quotes($string) {
|
||
if (!is_string($string)) {
|
||
return $string;
|
||
}
|
||
|
||
$quotes = [
|
||
"\xC2\xAB" => '"', // « (U+00AB) in UTF-8
|
||
"\xC2\xBB" => '"', // » (U+00BB) in UTF-8
|
||
"\xE2\x80\x98" => "'", // ‘ (U+2018) in UTF-8
|
||
"\xE2\x80\x99" => "'", // ’ (U+2019) in UTF-8
|
||
"\xE2\x80\x9A" => "'", // ‚ (U+201A) in UTF-8
|
||
"\xE2\x80\x9B" => "'", // ‛ (U+201B) in UTF-8
|
||
"\xE2\x80\x9C" => '"', // “ (U+201C) in UTF-8
|
||
"\xE2\x80\x9D" => '"', // ” (U+201D) in UTF-8
|
||
"\xE2\x80\x9E" => '"', // „ (U+201E) in UTF-8
|
||
"\xE2\x80\x9F" => '"', // ‟ (U+201F) in UTF-8
|
||
"\xE2\x80\xB9" => "'", // ‹ (U+2039) in UTF-8
|
||
"\xE2\x80\xBA" => "'", // › (U+203A) in UTF-8
|
||
"\xE2\x80\xB2" => "'", // ′ (U+2032) in UTF-8
|
||
"\xE2\x80\xB3" => '"', // ″ (U+2033) in UTF-8
|
||
// dashes
|
||
"\xE2\x80\x94" => '-', // — (U+2014) in UTF-8
|
||
"\xEF\xB9\x98" => '-', // ﹘ (U+FE58) in UTF-8
|
||
];
|
||
|
||
return strtr($string, $quotes);
|
||
}
|
||
|
||
// JSON escaping for JS see:
|
||
// https://stackoverflow.com/questions/7462394/php-json-string-escape-double-quotes-for-js-output
|
||
function json_escape($str) {
|
||
if (!is_string($str)) {
|
||
return $str;
|
||
}
|
||
|
||
return substr(safe_json_encode(smart_quotes($str)), 1, -1);
|
||
}
|
||
|
||
function safe_json_encode($var, $options = 0) {
|
||
$options |= OBS_JSON_ENCODE; // JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
|
||
|
||
$str = @json_encode($var, $options);
|
||
|
||
if (OBS_DEBUG && json_last_error() !== JSON_ERROR_NONE) {
|
||
print_message('JSON ENCODE[%r' . json_last_error_msg() . '%n]');
|
||
echo "JSON VAR[\n" . print_vars($var) . "\n]\n";
|
||
}
|
||
|
||
return $str;
|
||
}
|
||
|
||
function safe_json_decode($str, $options = 0) {
|
||
if (!is_string($str)) {
|
||
// When not string passed return original variable
|
||
// This is not same as json_decode do, but better for us
|
||
if (OBS_DEBUG) {
|
||
print_message('JSON DECODE[%rNot string passed%n]');
|
||
echo 'JSON RAW['.PHP_EOL;
|
||
print_vars($str);
|
||
echo PHP_EOL.']'.PHP_EOL;
|
||
}
|
||
return $str;
|
||
}
|
||
if ($str === '') {
|
||
if (OBS_DEBUG) {
|
||
print_message('JSON DECODE[%yEmpty string%n]');
|
||
}
|
||
return $str;
|
||
}
|
||
|
||
$options |= OBS_JSON_DECODE; // JSON_BIGINT_AS_STRING
|
||
|
||
$json = @json_decode(smart_quotes($str), TRUE, 512, $options);
|
||
|
||
$json_error = json_last_error();
|
||
if ($json_error !== JSON_ERROR_NONE) {
|
||
$msg = json_last_error_msg();
|
||
|
||
if ($json_error === JSON_ERROR_CTRL_CHAR) {
|
||
// Try to fix "Control character error, possibly incorrectly encoded"
|
||
$str_fix = preg_replace('/[[:cntrl:]]/', '', smart_quotes($str));
|
||
print_debug_vars($str_fix);
|
||
} elseif (function_exists('mb_ord')) {
|
||
// Try fix utf errors
|
||
$str_fix = fix_json_unicode(smart_quotes($str));
|
||
print_debug_vars($str_fix);
|
||
} else {
|
||
// https://jira.observium.org/browse/OBS-4881
|
||
// Prevent php fatal errors in poller without mbstring
|
||
print_debug("WARNING! PHP module mbstring not exist. Please read Observium requirements.");
|
||
$str_fix = FALSE;
|
||
}
|
||
if ($str_fix) {
|
||
$json_fix = @json_decode($str_fix, TRUE, 512, $options);
|
||
if (json_last_error() === JSON_ERROR_NONE) {
|
||
//print_vars(smart_quotes(fix_json_unicode($str)));
|
||
//print_vars($json_fix);
|
||
return $json_fix;
|
||
}
|
||
}
|
||
if (OBS_DEBUG) {
|
||
print_message('JSON DECODE[%r' . $msg . '%n]');
|
||
echo "JSON[" . PHP_EOL . $str . PHP_EOL . "]" . PHP_EOL;
|
||
}
|
||
}
|
||
|
||
return $json;
|
||
}
|
||
|
||
/*
|
||
* PHP json_decode not correctly convert UTF8 encoded chars, but correct decode escaped unicode :/
|
||
* "ËЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ" ->
|
||
* "\u00cb\u0419\u0426\u0423\u041a\u0415\u041d\u0413\u0428\u0429\u0417\u0425\u042a\u0424\u042b\u0412\u0410\u041f\u0420\u041e\u041b\u0414\u0416\u042d\u042f\u0427\u0421\u041c\u0418\u0422\u042c\u0411\u042e"
|
||
*/
|
||
function fix_json_unicode($string) {
|
||
if (!function_exists('mb_ord')) {
|
||
// Safe return original string, for prevent php fatal errors
|
||
print_debug("WARNING! PHP module mbstring requered for fix_json_unicode().");
|
||
return $string;
|
||
}
|
||
|
||
return preg_replace_callback('/([\x{0080}-\x{FFFF}])/u', function ($match) {
|
||
return '\\u' . str_pad(dechex(mb_ord($match[1], 'UTF-8')), 4, '0', STR_PAD_LEFT);
|
||
}, $string);
|
||
}
|
||
|
||
function safe_unserialize($str)
|
||
{
|
||
if (is_array($str)) {
|
||
return NULL;
|
||
}
|
||
|
||
return @unserialize($str, ['allowed_classes' => FALSE]);
|
||
}
|
||
|
||
function safe_count($array, $mode = COUNT_NORMAL)
|
||
{
|
||
if (is_countable($array)) {
|
||
return count($array, $mode);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Report if var empty (only empty array [], string '' and NULL)
|
||
* Note FALSE, 0 and '0' return TRUE (not empty)
|
||
*
|
||
* @param $var
|
||
*
|
||
* @return bool
|
||
*/
|
||
function safe_empty($var)
|
||
{
|
||
return $var !== 0 && $var !== '0' && $var !== FALSE && empty($var);
|
||
}
|
||
|
||
/**
|
||
* Creates a RecursiveIteratorIterator object for a given directory with a specified maximum depth.
|
||
* key - full file path
|
||
* entry->getFilename() - filename only
|
||
*
|
||
* @param string $dir The path to the directory.
|
||
* @param int $max_depth The maximum allowed subdirectory depth. Defaults to -1, which allows for any depth.
|
||
*
|
||
* @return RecursiveIteratorIterator Returns a RecursiveIteratorIterator object for the specified directory.
|
||
*/
|
||
function get_recursive_directory_iterator($dir, $max_depth = -1)
|
||
{
|
||
$directory = new RecursiveDirectoryIterator($dir, FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS);
|
||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::LEAVES_ONLY, RecursiveIteratorIterator::CATCH_GET_CHILD);
|
||
$iterator -> setMaxDepth($max_depth);
|
||
|
||
return $iterator;
|
||
}
|
||
|
||
/**
|
||
* Checks if the filesystem of the given path is full, based on a specified threshold.
|
||
*
|
||
* @param string $path The path for which to check the filesystem.
|
||
*
|
||
* @return bool Returns true if the filesystem is considered full, false otherwise.
|
||
*
|
||
* The function calculates the percentage of free disk space and compares it to a threshold.
|
||
* If the percentage of free space is lower than the threshold, the function returns true,
|
||
* indicating that the filesystem is considered full. Otherwise, it returns false.
|
||
*/
|
||
function is_filesystem_full($path)
|
||
{
|
||
if ($disk_total_space = disk_total_space($path))
|
||
{
|
||
$disk_free_space = disk_free_space($path);
|
||
$disk_full_percentage = float_div($disk_free_space, $disk_total_space) * 100;
|
||
|
||
return $disk_full_percentage < 1;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
// EOF
|