CACHE CLEAR SET. 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 = '' . $php_memory_limit_text . '';
}
// 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 '
' . escape_html(OBSERVIUM_PRODUCT) . ' | ' . escape_html(OBSERVIUM_VERSION_LONG) . ' (' . escape_html($observium_date) . ') |
OS | ' . escape_html($os_version) . ' |
Apache | ' . escape_html($http_version) . ' |
PHP | ' . $php_version . ' (Memory: ' . $php_memory_limit_text . ') |
Python | ' . $python_version . ' |
' . $mysql_name . ' | ' . $mysql_version . ' |
SNMP | ' . escape_html($snmp_version) . ' |
RRDtool | ' . $rrdtool_version . ' |
Fping | ' . escape_html($fping_version) . ' |
Fetch | ' . $curl_version . ' |
' . 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('/(?\d+)-(?\d+)-(?\d+)(?:,(?\d+):(?\d+):(?\d+)(?\.\d+)?(?:,(?[+\-]?)(?\d+):(?\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: