1<?php 2 3/** 4 * Observium 5 * 6 * This file is part of Observium. 7 * 8 * @package observium 9 * @subpackage common 10 * @author Adam Armstrong <adama@observium.org> 11 * @copyright (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited 12 * 13 */ 14 15// Common Functions 16/// FIXME. There should be functions that use only standard php (and self) functions. 17 18/** 19 * Autoloader for Classes used in Observium 20 * 21 */ 22function observium_autoload($class_name) 23{ 24 //var_dump($class_name); 25 $base_dir = $GLOBALS['config']['install_dir'] . '/libs/'; 26 27 $class_array = explode('\\', $class_name); 28 $class_file = str_replace('_', '/', implode($class_array, '/')) . '.php'; 29 //print_vars($class_array); 30 switch ($class_array[0]) 31 { 32 case 'cli': 33 include_once($base_dir . 'cli/cli.php'); // Cli classes required base functions 34 $class_file = str_replace('/Table/', '/table/', $class_file); 35 //var_dump($class_file); 36 break; 37 38 case 'Psr': 39 // Legacy for phpFastCache 40 if ($class_array[1] == 'Cache') 41 { 42 $class_file = array_pop($class_array) . '.php'; 43 $class_file = 'phpFastCache/legacy/' . implode($class_array, '/') . '/src/' . $class_file; 44 // print_vars($class_file); 45 } 46 break; 47 48 case 'Flight': 49 $class_file = array_pop($class_array) . '.php'; 50 $class_file = 'flight/flight/' . $class_file; 51 break; 52 53 case 'Ramsey': 54 $class_file = str_replace('Ramsey/', '', $class_file); 55 break; 56 57 case 'Defuse': 58 $class_file = str_replace('Defuse/', '', $class_file); 59 break; 60 61 case 'PhpUnitsOfMeasure': 62 include_once($base_dir . 'PhpUnitsOfMeasure/UnitOfMeasureInterface.php'); 63 break; 64 65 default: 66 if (is_file($base_dir . 'pear/' . $class_file)) 67 { 68 // By default try Pear file 69 $class_file = 'pear/' . $class_file; 70 } 71 else if (is_dir($base_dir . 'pear/' . $class_name)) 72 { 73 // And Pear dir 74 $class_file = 'pear/' . $class_name . '/' . $class_file; 75 } 76 //else if (!is_cli() && is_file($GLOBALS['config']['html_dir'] . '/includes/' . $class_file)) 77 //{ 78 // // For WUI check class files in html_dir 79 // $base_dir = $GLOBALS['config']['html_dir'] . '/includes/'; 80 //} 81 } 82 $full_path = $base_dir . $class_file; 83 84 $status = is_file($full_path); 85 if ($status) 86 { 87 $status = include_once($full_path); 88 } 89 if (OBS_DEBUG > 1) 90 { 91 print_message("%WLoad class '$class_name' from '$full_path': " . ($status ? '%gOK' : '%rFAIL'), 'console'); 92 } 93 return $status; 94} 95// Register autoload function 96spl_autoload_register('observium_autoload'); 97 98// DOCME needs phpdoc block 99// TESTME needs unit testing 100// MOVEME to includes/functions.inc.php 101function del_obs_attrib($attrib_type) 102{ 103 if (isset($GLOBALS['cache']['attribs'])) { unset($GLOBALS['cache']['attribs']); } // Reset attribs cache 104 105 return dbDelete('observium_attribs', "`attrib_type` = ?", array($attrib_type)); 106} 107 108// DOCME needs phpdoc block 109// TESTME needs unit testing 110// MOVEME to includes/functions.inc.php 111function set_obs_attrib($attrib_type, $attrib_value) 112{ 113 if (isset($GLOBALS['cache']['attribs'])) { unset($GLOBALS['cache']['attribs']); } // Reset attribs cache 114 115 //if (dbFetchCell("SELECT COUNT(*) FROM `observium_attribs` WHERE `attrib_type` = ?;", array($attrib_type))) 116 if (dbExist('observium_attribs', '`attrib_type` = ?', array($attrib_type))) 117 { 118 $status = dbUpdate(array('attrib_value' => $attrib_value), 'observium_attribs', "`attrib_type` = ?", array($attrib_type)); 119 } else { 120 $status = dbInsert(array('attrib_type' => $attrib_type, 'attrib_value' => $attrib_value), 'observium_attribs'); 121 if ($status !== FALSE) { $status = TRUE; } // Note dbInsert return IDs if exist or 0 for not indexed tables 122 } 123 return $status; 124} 125 126// DOCME needs phpdoc block 127// TESTME needs unit testing 128// MOVEME to includes/functions.inc.php 129function get_obs_attribs($type_filter) 130{ 131 if (!isset($GLOBALS['cache']['attribs'])) 132 { 133 $attribs = array(); 134 foreach (dbFetchRows("SELECT * FROM `observium_attribs`") as $entry) 135 { 136 $attribs[$entry['attrib_type']] = $entry['attrib_value']; 137 } 138 $GLOBALS['cache']['attribs'] = $attribs; 139 } 140 141 if (strlen($type_filter)) 142 { 143 $attribs = array(); 144 foreach ($GLOBALS['cache']['attribs'] as $type => $value) 145 { 146 if (strpos($type, $type_filter) !== FALSE) 147 { 148 $attribs[$type] = $value; 149 } 150 } 151 return $attribs; // Return filtered attribs 152 } 153 154 return $GLOBALS['cache']['attribs']; // All cached attribs 155} 156 157// DOCME needs phpdoc block 158// TESTME needs unit testing 159// MOVEME to includes/functions.inc.php 160function get_obs_attrib($attrib_type) 161{ 162 if (isset($GLOBALS['cache']['attribs'][$attrib_type])) 163 { 164 return $GLOBALS['cache']['attribs'][$attrib_type]; 165 } 166 167 if ($row = dbFetchRow("SELECT `attrib_value` FROM `observium_attribs` WHERE `attrib_type` = ?;", array($attrib_type))) 168 { 169 return $row['attrib_value']; 170 } else { 171 return NULL; 172 } 173} 174 175// FIXME. Function temporary placed here, since cache_* functions currently included in WUI only. 176// MOVEME includes/cache.inc.php 177/** 178 * Add clear cache attrib, this will request for clering cache in next request. 179 * 180 * @param string $target Clear cache target: wui or cli (default if wui) 181 */ 182function set_cache_clear($target = 'wui') 183{ 184 if (OBS_DEBUG || OBS_CACHE_DEBUG) 185 { 186 print_error('<span class="text-warning">CACHE CLEAR SET.</span> Cache clear set.'); 187 } 188 if (!$GLOBALS['config']['cache']['enable']) 189 { 190 // Cache not enabled 191 return; 192 } 193 194 switch (strtolower($target)) 195 { 196 case 'cli': 197 // Add clear CLI cache attrib. Currently not used 198 set_obs_attrib('cache_cli_clear', get_request_id()); 199 break; 200 default: 201 // Add clear WUI cache attrib 202 set_obs_attrib('cache_wui_clear', get_request_id()); 203 } 204} 205 206function set_status_var($var, $value) 207{ 208 $GLOBALS['cache']['status_vars'][$var] = $value; 209 return TRUE; 210} 211 212function isset_status_var($var) 213{ 214 return in_array($var, array_keys($GLOBALS['cache']['status_vars'])); 215} 216 217function get_status_var($var) 218{ 219 return $GLOBALS['cache']['status_vars'][$var]; 220} 221 222/** 223 * Generate and store Unique ID for current system. Store in DB at first run. 224 * IDs is RFC 4122 version 4 (without dashes, varchar(32)), i.e. c39b2386c4e8487fad4a87cd367b279d 225 * 226 * @return string Unique system ID 227 */ 228function get_unique_id() 229{ 230 if (!defined('OBS_UNIQUE_ID')) 231 { 232 $unique_id = get_obs_attrib('unique_id'); 233 234 if (!strlen($unique_id)) 235 { 236 // Old, low entropy 237 //$unique_id = str_replace('.', '', uniqid(NULL, TRUE)); // i.e. 55b24d7f1fa57330542020 238 // Generate a version 4 (random) UUID object 239 $uuid4 = Ramsey\Uuid\Uuid::uuid4(); 240 //$unique_id = $uuid4->toString(); // i.e. c39b2386-c4e8-487f-ad4a-87cd367b279d 241 $unique_id = $uuid4->getHex(); // i.e. c39b2386c4e8487fad4a87cd367b279d 242 dbInsert(array('attrib_type' => 'unique_id', 'attrib_value' => $unique_id), 'observium_attribs'); 243 } 244 define('OBS_UNIQUE_ID', $unique_id); 245 } 246 247 return OBS_UNIQUE_ID; 248} 249 250/** 251 * Generate and store Unique Request ID for current script/page. 252 * ID unique between 2 different requests or page loads 253 * IDs is RFC 4122 version 4, i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a 254 * 255 * @return string Unique Request ID 256 */ 257function get_request_id() 258{ 259 if (!defined('OBS_REQUEST_ID')) 260 { 261 // Generate a version 4 (random) UUID object 262 $uuid4 = Ramsey\Uuid\Uuid::uuid4(); 263 $request_id = $uuid4->toString(); // i.e. 25769c6c-d34d-4bfe-ba98-e0ee856f3e7a 264 define('OBS_REQUEST_ID', $request_id); 265 } 266 267 return OBS_REQUEST_ID; 268} 269 270/** 271 * Set new DB Schema version 272 * 273 * @param integer $db_rev New DB schema revision 274 * @param boolean $schema_insert Update (by default) or insert by first install db schema 275 * @return boolean Status of DB schema update 276 */ 277function set_db_version($db_rev, $schema_insert = FALSE) 278{ 279 if ($db_rev >= 211) // Do not remove this check, since before this revision observium_attribs table not exist! 280 { 281 $status = set_obs_attrib('dbSchema', $db_rev); 282 } else { 283 if ($schema_insert) 284 { 285 $status = dbInsert(array('version' => $db_rev), 'dbSchema'); 286 if ($status !== FALSE) { $status = TRUE; } // Note dbInsert return IDs if exist or 0 for not indexed tables 287 } else { 288 $status = dbUpdate(array('version' => $db_rev), 'dbSchema'); 289 } 290 } 291 292 if ($status) 293 { 294 $GLOBALS['cache']['db_version'] = $db_rev; // Cache new db version 295 } 296 297 return $status; 298} 299 300/** 301 * Get current DB Schema version 302 * 303 * @return string DB schema version 304 */ 305// TESTME needs unit testing 306function get_db_version() 307{ 308 if (!isset($GLOBALS['cache']['db_version'])) 309 { 310 if ($db_rev = @get_obs_attrib('dbSchema')) {} else 311 { 312 // CLEANME remove fallback at r7000 313 // not r7000, but after one next CE release! 314 if ($db_rev = @dbFetchCell("SELECT `version` FROM `dbSchema` ORDER BY `version` DESC LIMIT 1")) {} else 315 { 316 $db_rev = 0; 317 } 318 } 319 $db_rev = (int)$db_rev; 320 if ($db_rev > 0) 321 { 322 $GLOBALS['cache']['db_version'] = $db_rev; 323 } else { 324 // Do not cache zero value 325 return $db_rev; 326 } 327 } 328 return $GLOBALS['cache']['db_version']; 329} 330 331/** 332 * Get current DB Size 333 * 334 * @return string DB size in bytes 335 */ 336// TESTME needs unit testing 337function get_db_size() 338{ 339 $db_size = dbFetchCell('SELECT SUM(`data_length` + `index_length`) AS `size` FROM `information_schema`.`tables` WHERE `table_schema` = ?;', array($GLOBALS['config']['db_name'])); 340 return $db_size; 341} 342 343/** 344 * Get local hostname 345 * 346 * @return string FQDN local hostname 347 */ 348function get_localhost() 349{ 350 global $cache; 351 352 if (!isset($cache['localhost'])) 353 { 354 $cache['localhost'] = php_uname('n'); 355 if (!strpos($cache['localhost'], '.')) 356 { 357 // try use hostname -f for get FQDN hostname 358 $localhost_t = external_exec('/bin/hostname -f'); 359 if (strpos($localhost_t, '.')) 360 { 361 $cache['localhost'] = $localhost_t; 362 } 363 } 364 } 365 366 return $cache['localhost']; 367} 368 369/** 370 * Get the directory size 371 * 372 * @param string $directory 373 * @return integer Directory size in bytes 374 */ 375function get_dir_size($directory) 376{ 377 $size = 0; 378 379 foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory), RecursiveIteratorIterator::LEAVES_ONLY, RecursiveIteratorIterator::CATCH_GET_CHILD) as $file) 380 { 381 if ($file->getFileName() != '..') { $size += $file->getSize(); } 382 } 383 384 return $size; 385} 386 387// DOCME needs phpdoc block 388// TESTME needs unit testing 389// MOVEME to includes/alerts.inc.php 390function get_alert_entry_by_id($id) 391{ 392 return dbFetchRow("SELECT * FROM `alert_table`". 393 //" LEFT JOIN `alert_table-state` ON `alert_table`.`alert_table_id` = `alert_table-state`.`alert_table_id`". 394 " WHERE `alert_table`.`alert_table_id` = ?", array($id)); 395} 396 397/** 398 * Percent Class 399 * 400 * Given a percentage value return a class name (for CSS). 401 * 402 * @param int|string $percent 403 * @return string 404 */ 405function percent_class($percent) 406{ 407 if ($percent < "25") 408 { 409 $class = "info"; 410 } elseif ($percent < "50") { 411 $class = ""; 412 } elseif ($percent < "75") { 413 $class = "success"; 414 } elseif ($percent < "90") { 415 $class = "warning"; 416 } else { 417 $class = "danger"; 418 } 419 420 return $class; 421} 422 423/** 424 * Percent Colour 425 * 426 * This function returns a colour based on a 0-100 value 427 * It scales from green to red from 0-100 as default. 428 * 429 * @param integer $percent 430 * @param integer $brightness 431 * @param integer $max 432 * @param integer $min 433 * @param integer $thirdColorHex 434 * @return string 435 */ 436function percent_colour($value, $brightness = 128, $max = 100, $min = 0, $thirdColourHex = '00') 437{ 438 if ($value > $max) { $value = $max; } 439 if ($value < $min) { $value = $min; } 440 441 // Calculate first and second colour (Inverse relationship) 442 $first = (1-($value/$max))*$brightness; 443 $second = ($value/$max)*$brightness; 444 445 // Find the influence of the middle Colour (yellow if 1st and 2nd are red and green) 446 $diff = abs($first-$second); 447 $influence = ($brightness-$diff)/2; 448 $first = intval($first + $influence); 449 $second = intval($second + $influence); 450 451 // Convert to HEX, format and return 452 $firstHex = str_pad(dechex($first),2,0,STR_PAD_LEFT); 453 $secondHex = str_pad(dechex($second),2,0,STR_PAD_LEFT); 454 455 return '#'.$secondHex . $firstHex . $thirdColourHex; 456 457 // alternatives: 458 // return $thirdColourHex . $firstHex . $secondHex; 459 // return $firstHex . $thirdColourHex . $secondHex; 460} 461 462/** 463 * Convert sequence of numbers in an array to range of numbers. 464 * Example: 465 * array(1,2,3,4,5,6,7,8,9,10) -> '1-10' 466 * array(1,2,3,5,7,9,10,11,12,14) -> '1-3,5,7,9-12,14' 467 * 468 * @param array $arr Array with sequence of numbers 469 * @param string $separator Use this separator for list 470 * @param bool $sort Sort input array or not 471 * @return string 472 */ 473function range_to_list($arr, $separator = ',', $sort = TRUE) 474{ 475 if ($sort) { sort($arr, SORT_NUMERIC); } 476 477 for ($i = 0; $i < count($arr); $i++) 478 { 479 $rstart = $arr[$i]; 480 $rend = $rstart; 481 while (isset($arr[$i+1]) && $arr[$i+1] - $arr[$i] == 1) 482 { 483 $rend = $arr[$i+1]; 484 $i++; 485 } 486 if (is_numeric($rstart) && is_numeric($rend)) 487 { 488 $ranges[] = ($rstart == $rend) ? $rstart : $rstart.'-'.$rend; 489 } else { 490 return ''; // Not numeric value(s) 491 } 492 } 493 $list = implode($separator, $ranges); 494 495 return $list; 496} 497 498// DOCME needs phpdoc block 499// Write a line to the specified logfile (or default log if not specified) 500// We open & close for every line, somewhat lower performance but this means multiple concurrent processes could write to the file. 501// Now marking process and pid, if things are running simultaneously you can still see what's coming from where. 502// TESTME needs unit testing 503function logfile($filename, $string = NULL) 504{ 505 global $config, $argv; 506 507 // Use default logfile if none specified 508 if ($string == NULL) { $string = $filename; $filename = $config['log_file']; } 509 510 // Place logfile in log directory if no path specified 511 if (basename($filename) == $filename) { $filename = $config['log_dir'] . '/' . $filename; } 512 // Create logfile if not exist 513 if (is_file($filename)) 514 { 515 if (!is_writable($filename)) 516 { 517 print_debug("Log file '$filename' is not writable, check file permissions."); 518 return FALSE; 519 } 520 $fd = fopen($filename, 'a'); 521 } else { 522 $fd = fopen($filename, 'wb'); 523 // Check writable file (only after creation for speedup) 524 if (!is_writable($filename)) 525 { 526 print_debug("Log file '$filename' is not writable or not created."); 527 fclose($fd); 528 return FALSE; 529 } 530 } 531 532 //$string = '[' . date('Y/m/d H:i:s O') . '] ' . basename($argv[0]) . '(' . getmypid() . '): ' . trim($string) . PHP_EOL; 533 $string = '[' . date('Y/m/d H:i:s O') . '] ' . basename($_SERVER['SCRIPT_FILENAME']) . '(' . getmypid() . '): ' . trim($string) . PHP_EOL; 534 fputs($fd, $string); 535 fclose($fd); 536} 537 538/** 539 * Get used system versions 540 * @return array 541 */ 542function get_versions() 543{ 544 if (isset($GLOBALS['cache']['versions'])) 545 { 546 // Already cached 547 return $GLOBALS['cache']['versions']; 548 } 549 $versions = array(); // Init 550 551 // Local system OS version 552 if (is_executable($GLOBALS['config']['install_dir'].'/scripts/distro')) 553 { 554 $os = explode('|', external_exec($GLOBALS['config']['install_dir'].'/scripts/distro'), 6); 555 $versions['os_system'] = $os[0]; 556 $versions['os_version'] = $os[1]; 557 $versions['os_arch'] = $os[2]; 558 $versions['os_distro'] = $os[3]; 559 $versions['os_distro_version'] = $os[4]; 560 $versions['os_virt'] = $os[5]; 561 $versions['os_text'] = $os[0].' '.$os[1].' ['.$os[2].'] ('.$os[3].' '.$os[4].')'; 562 } 563 564 // PHP 565 $php_version = PHP_VERSION; 566 $versions['php_full'] = $php_version; 567 $versions['php_version'] = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION; 568 // PHP OPcache 569 $versions['php_opcache'] = FALSE; 570 if (extension_loaded('Zend OPcache')) 571 { 572 $opcache = ini_get('opcache.enable'); 573 $php_version .= ' (OPcache: '; 574 if ($opcache && is_cli() && ini_get('opcache.enable_cli')) // CLI 575 { 576 $php_version .= 'ENABLED)'; 577 $versions['php_opcache'] = 'ENABLED'; 578 } 579 else if ($opcache && !is_cli()) // WUI 580 { 581 $php_version .= 'ENABLED)'; 582 $versions['php_opcache'] = 'ENABLED'; 583 } else { 584 $php_version .= 'DISABLED)'; 585 $versions['php_opcache'] = 'DISABLED'; 586 } 587 } 588 $versions['php_text'] = $php_version; 589 /* 590 // PHP memory_limit 591 $php_memory_limit = ini_get('memory_limit'); 592 $versions['php_memory_limit'] = $php_memory_limit; 593 if ($php_memory_limit < 0) 594 { 595 $versions['php_memory_limit_text'] = 'Unlimited'; 596 } else { 597 $versions['php_memory_limit_text'] = formatStorage($php_memory_limit); 598 } 599 */ 600 601 // Python 602 $python_version = str_replace('Python ', '', external_exec('/usr/bin/env python --version 2>&1')); 603 $versions['python_version'] = $python_version; 604 $versions['python_text'] = $python_version; 605 606 // MySQL 607 $mysql_client = dbClientInfo(); 608 if (preg_match('/(\d+\.[\d\w\.\-]+)/', $mysql_client, $matches)) 609 { 610 $mysql_client = $matches[1]; 611 } 612 $versions['mysql_client'] = $mysql_client; 613 $mysql_version = dbFetchCell("SELECT version();"); 614 $versions['mysql_full'] = $mysql_version; 615 list($versions['mysql_version']) = explode('-', $mysql_version); 616 $mysql_version .= ' (extension: ' . OBS_DB_EXTENSION . ' ' . $mysql_client . ')'; 617 $versions['mysql_text'] = $mysql_version; 618 619 // SNMP 620 if (is_executable($GLOBALS['config']['snmpget'])) 621 { 622 $snmp_version = str_replace(' version:', '', external_exec($GLOBALS['config']['snmpget'] . " --version 2>&1")); 623 } else { 624 $snmp_version = 'not found'; 625 } 626 $versions['snmp_version'] = str_replace('NET-SNMP ', '', $snmp_version); 627 $versions['snmp_text'] = $snmp_version; 628 629 // RRDtool 630 if (is_executable($GLOBALS['config']['rrdtool'])) 631 { 632 list(,$rrdtool_version) = explode(' ', external_exec($GLOBALS['config']['rrdtool'] . ' --version | head -n1')); 633 $versions['rrdtool_version'] = $rrdtool_version; 634 635 if (strlen($GLOBALS['config']['rrdcached'])) 636 { 637 if (OBS_RRD_NOLOCAL) 638 { 639 // Remote rrdcached daemon (unknown version) 640 $rrdtool_version .= ' (rrdcached remote: ' . $GLOBALS['config']['rrdcached'] . ')'; 641 } else { 642 $rrdcached_exec = str_replace('rrdtool', 'rrdcached', $GLOBALS['config']['rrdtool']); 643 if (!is_executable($rrdcached_exec)) 644 { 645 $rrdcached_exec = '/usr/bin/env rrdcached -h'; 646 } 647 list(,$versions['rrdcached_version']) = explode(' ', external_exec($rrdcached_exec . ' -h | head -n1')); 648 $rrdtool_version .= ' (rrdcached ' . $versions['rrdcached_version'] . ': ' . $GLOBALS['config']['rrdcached'] . ')'; 649 } 650 } 651 } else { 652 $rrdtool_version = 'not found'; 653 $versions['rrdtool_version'] = $rrdtool_version; 654 } 655 $versions['rrdtool_text'] = $rrdtool_version; 656 657 // Fping 658 $fping_version = 'not found'; 659 if (is_executable($GLOBALS['config']['fping'])) 660 { 661 $fping = external_exec($GLOBALS['config']['fping'] . " -v 2>&1"); 662 if (preg_match('/Version\s+(\d\S+)/', $fping, $matches)) 663 { 664 $fping_version = $matches[1]; 665 $fping_text = $fping_version; 666 667 if (is_executable($GLOBALS['config']['fping6'])) 668 { 669 $fping_text .= ' (IPv4 and IPv6)'; 670 } else { 671 $fping_text .= ' (IPv4 only)'; 672 } 673 } 674 } 675 $versions['fping_version'] = $fping_version; 676 $versions['fping_text'] = $fping_text; 677 678 // Apache (or any http used?) 679 if (is_cli()) 680 { 681 foreach (array('apache2', 'httpd') as $http_cmd) 682 { 683 if (is_executable('/usr/sbin/'.$http_cmd)) 684 { 685 $http_cmd = '/usr/sbin/'.$http_cmd; 686 } else { 687 $http_cmd = '/usr/bin/env '.$http_cmd; 688 } 689 $http_version = external_exec($http_cmd.' -v | awk \'/Server version:/ {print $3}\''); 690 691 if ($http_version) { break; } 692 } 693 if (empty($http_version)) 694 { 695 $http_version = 'not found'; 696 } 697 $versions['http_full'] = $http_version; 698 } else { 699 $versions['http_full'] = $_SERVER['SERVER_SOFTWARE']; 700 } 701 $versions['http_version'] = str_replace('Apache/', '', $versions['http_full']); 702 $versions['http_text'] = $versions['http_version']; 703 704 $GLOBALS['cache']['versions'] = $versions; 705 //print_vars($GLOBALS['cache']['versions']); 706 707 return $versions; 708} 709 710/** 711 * Print version information about used Observium and additional softwares. 712 * 713 * @return NULL 714 */ 715function print_versions() 716{ 717 get_versions(); 718 719 $os_version = $GLOBALS['cache']['versions']['os_text']; 720 $php_version = $GLOBALS['cache']['versions']['php_text']; 721 $python_version = $GLOBALS['cache']['versions']['python_text']; 722 $mysql_version = $GLOBALS['cache']['versions']['mysql_text']; 723 $snmp_version = $GLOBALS['cache']['versions']['snmp_text']; 724 $rrdtool_version = $GLOBALS['cache']['versions']['rrdtool_text']; 725 $fping_version = $GLOBALS['cache']['versions']['fping_text']; 726 $http_version = $GLOBALS['cache']['versions']['http_text']; 727 728 if (is_cli()) 729 { 730 $timezone = get_timezone(); 731 //print_vars($timezone); 732 733 $mysql_mode = dbFetchCell("SELECT @@SESSION.sql_mode;"); 734 $mysql_charset = dbShowVariables('SESSION', "LIKE 'character_set_connection'"); 735 736 // PHP memory_limit 737 //$php_memory_limit = $GLOBALS['cache']['versions']['php_memory_limit']; 738 //$php_memory_limit_text = $GLOBALS['cache']['versions']['php_memory_limit_text']; 739 740 $php_memory_limit = unit_string_to_numeric(ini_get('memory_limit')); 741 if ($php_memory_limit < 0) 742 { 743 $php_memory_limit_text = 'Unlimited'; 744 } else { 745 $php_memory_limit_text = formatStorage($php_memory_limit); 746 } 747 748 echo(PHP_EOL); 749 print_cli_heading("Software versions"); 750 print_cli_data("OS", $os_version); 751 print_cli_data("Apache", $http_version); 752 print_cli_data("PHP", $php_version); 753 print_cli_data("Python", $python_version); 754 print_cli_data("MySQL", $mysql_version); 755 print_cli_data("SNMP", $snmp_version); 756 print_cli_data("RRDtool", $rrdtool_version); 757 print_cli_data("Fping", $fping_version); 758 759 // Additionally in CLI always display Memory Limit, MySQL Mode and Charset info 760 761 echo(PHP_EOL); 762 print_cli_heading("Memory Limit", 3); 763 print_cli_data("PHP", ($php_memory_limit >= 0 && $php_memory_limit < 268435456 ? '%r' : '') . $php_memory_limit_text, 3); 764 765 echo(PHP_EOL); 766 print_cli_heading("MySQL mode", 3); 767 print_cli_data("MySQL", $mysql_mode, 3); 768 769 echo(PHP_EOL); 770 print_cli_heading("Charset info", 3); 771 print_cli_data("PHP", ini_get("default_charset"), 3); 772 print_cli_data("MySQL", $mysql_charset['character_set_connection'], 3); 773 774 echo(PHP_EOL); 775 print_cli_heading("Timezones info", 3); 776 print_cli_data("Date", date("l, d-M-y H:i:s T"), 3); 777 print_cli_data("PHP", $timezone['php'], 3); 778 print_cli_data("MySQL", ($timezone['diff'] !== 0 ? '%r' : '') . $timezone['mysql'], 3); 779 echo(PHP_EOL); 780 781 } else { 782 $observium_date = format_unixtime(strtotime(OBSERVIUM_DATE), 'jS F Y'); 783 784 echo generate_box_open(array('title' => 'Version Information')); 785 echo(' 786 <table class="table table-striped table-condensed-more"> 787 <tbody> 788 <tr><td><b>'.escape_html(OBSERVIUM_PRODUCT).'</b></td><td>'.escape_html(OBSERVIUM_VERSION).' ('.escape_html($observium_date).')</td></tr> 789 <tr><td><b>OS</b></td><td>'.escape_html($os_version).'</td></tr> 790 <tr><td><b>Apache</b></td><td>'.escape_html($http_version).'</td></tr> 791 <tr><td><b>PHP</b></td><td>'.escape_html($php_version).'</td></tr> 792 <tr><td><b>Python</b></td><td>'.escape_html($python_version).'</td></tr> 793 <tr><td><b>MySQL</b></td><td>'.escape_html($mysql_version).'</td></tr> 794 <tr><td><b>SNMP</b></td><td>'.escape_html($snmp_version).'</td></tr> 795 <tr><td><b>RRDtool</b></td><td>'.escape_html($rrdtool_version).'</td></tr> 796 <tr><td><b>Fping</b></td><td>'.escape_html($fping_version).'</td></tr> 797 </tbody> 798 </table>'.PHP_EOL); 799 echo generate_box_close(); 800 } 801} 802 803// DOCME needs phpdoc block 804// Observium's SQL debugging. Chooses nice output depending upon web or cli 805// TESTME needs unit testing 806function print_sql($query) 807{ 808 if ($GLOBALS['cli']) 809 { 810 print_vars($query); 811 } else { 812 if (class_exists('SqlFormatter')) 813 { 814 // Hide it under a "database icon" popup. 815 #echo overlib_link('#', '<i class="oicon-databases"> </i>', SqlFormatter::highlight($query)); 816 $query = SqlFormatter::compress($query); 817 echo '<p>',SqlFormatter::highlight($query),'</p>'; 818 } else { 819 print_vars($query); 820 } 821 } 822} 823 824// DOCME needs phpdoc block 825// Observium's variable debugging. Chooses nice output depending upon web or cli 826// TESTME needs unit testing 827function print_vars($vars, $trace = NULL) 828{ 829 if ($GLOBALS['cli']) 830 { 831 if (function_exists('rt')) 832 { 833 ref::config('shortcutFunc', array('print_vars', 'print_debug_vars')); 834 ref::config('showUrls', FALSE); 835 if (OBS_DEBUG > 0) 836 { 837 if (is_null($trace)) 838 { 839 $backtrace = defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace(); 840 } else { 841 $backtrace = $trace; 842 } 843 ref::config('Backtrace', $backtrace); // pass original backtrace 844 ref::config('showStringMatches', FALSE); 845 } else { 846 ref::config('showBacktrace', FALSE); 847 ref::config('showResourceInfo', FALSE); 848 ref::config('showStringMatches', FALSE); 849 ref::config('showMethods', FALSE); 850 } 851 rt($vars); 852 } else { 853 print_r($vars); 854 } 855 } else { 856 if (function_exists('r')) 857 { 858 ref::config('shortcutFunc', array('print_vars', 'print_debug_vars')); 859 ref::config('showUrls', FALSE); 860 if (OBS_DEBUG > 0) 861 { 862 if (is_null($trace)) 863 { 864 $backtrace = defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace(); 865 } else { 866 $backtrace = $trace; 867 } 868 ref::config('Backtrace', $backtrace); // pass original backtrace 869 } else { 870 ref::config('showBacktrace', FALSE); 871 ref::config('showResourceInfo', FALSE); 872 ref::config('showStringMatches', FALSE); 873 ref::config('showMethods', FALSE); 874 } 875 //ref::config('stylePath', $GLOBALS['config']['html_dir'] . '/css/ref.css'); 876 //ref::config('scriptPath', $GLOBALS['config']['html_dir'] . '/js/ref.js'); 877 r($vars); 878 } else { 879 print_r($vars); 880 } 881 } 882} 883 884/** 885 * Call to print_vars in debug mode only 886 * By default var displayed only for debug level 2 887 * 888 * @param mixed $vars Variable to print 889 * @param integer $debug_level Minimum debug level, default 2 890 */ 891function print_debug_vars($vars, $debug_level = 2) 892{ 893 // For level 2 display always (also empty), for level 1 only non empty vars 894 if (OBS_DEBUG && OBS_DEBUG >= $debug_level && (OBS_DEBUG > 1 || count($vars))) 895 { 896 $trace = defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace(); 897 print_vars($vars, $trace); 898 } 899} 900 901/** 902 * Convert SNMP timeticks string into seconds 903 * 904 * SNMP timeticks can be in two different normal formats: 905 * - "(2105)" == 21.05 sec 906 * - "0:0:00:21.05" == 21.05 sec 907 * Sometime devices return wrong type or numeric instead timetick: 908 * - "Wrong Type (should be Timeticks): 1632295600" == 16322956 sec 909 * - "1546241903" == 15462419.03 sec 910 * Parse the timeticks string and convert it to seconds. 911 * 912 * @param string $timetick 913 * @param bool $float - Return a float with microseconds 914 * 915 * @return int|float 916 */ 917function timeticks_to_sec($timetick, $float = FALSE) 918{ 919 if (strpos($timetick, 'Wrong Type') !== FALSE) 920 { 921 // Wrong Type (should be Timeticks): 1632295600 922 list(, $timetick) = explode(': ', $timetick, 2); 923 } 924 925 $timetick = trim($timetick, " \t\n\r\0\x0B\"()"); // Clean string 926 if (is_numeric($timetick)) 927 { 928 // When "Wrong Type" or timetick as an integer, than time with count of ten millisecond ticks 929 $time = $timetick / 100; 930 return ($float ? (float)$time : (int)$time); 931 } 932 if (!preg_match('/^[\d\.: ]+$/', $timetick)) { return FALSE; } 933 934 $timetick_array = explode(':', $timetick); 935 if (count($timetick_array) == 1 && is_numeric($timetick)) 936 { 937 $secs = $timetick; 938 $microsecs = 0; 939 } else { 940 //list($days, $hours, $mins, $secs) = $timetick_array; 941 $secs = array_pop($timetick_array); 942 $mins = array_pop($timetick_array); 943 $hours = array_pop($timetick_array); 944 $days = array_pop($timetick_array); 945 list($secs, $microsecs) = explode('.', $secs); 946 947 $hours += $days * 24; 948 $mins += $hours * 60; 949 $secs += $mins * 60; 950 951 // Sometime used non standard years counter 952 if (count($timetick_array)) 953 { 954 $years = array_pop($timetick_array); 955 $secs += $years * 31536000; // 365 * 24 * 60 * 60; 956 } 957 //print_vars($timetick_array); 958 } 959 $time = ($float ? (float)$secs + $microsecs/100 : (int)$secs); 960 961 return $time; 962} 963 964/** 965 * Convert SNMP DateAndTime string into unixtime 966 * 967 * field octets contents range 968 * ----- ------ -------- ----- 969 * 1 1-2 year 0..65536 970 * 2 3 month 1..12 971 * 3 4 day 1..31 972 * 4 5 hour 0..23 973 * 5 6 minutes 0..59 974 * 6 7 seconds 0..60 975 * (use 60 for leap-second) 976 * 7 8 deci-seconds 0..9 977 * 8 9 direction from UTC '+' / '-' 978 * 9 10 hours from UTC 0..11 979 * 10 11 minutes from UTC 0..59 980 * 981 * For example, Tuesday May 26, 1992 at 1:30:15 PM EDT would be displayed as: 982 * 1992-5-26,13:30:15.0,-4:0 983 * 984 * Note that if only local time is known, then timezone information (fields 8-10) is not present. 985 * 986 * @param string $datetime DateAndTime string 987 * @param boolean $use_gmt Return unixtime converted to GMT or Local (by default) 988 * 989 * @return integer Unixtime 990 */ 991function datetime_to_unixtime($datetime, $use_gmt = FALSE) 992{ 993 $timezone = get_timezone(); 994 995 $datetime = trim($datetime); 996 if (preg_match('/(?<year>\d+)-(?<mon>\d+)-(?<day>\d+)(?:,(?<hour>\d+):(?<min>\d+):(?<sec>\d+)(?<millisec>\.\d+)?(?:,(?<tzs>[+\-]?)(?<tzh>\d+):(?<tzm>\d+))?)/', $datetime, $matches)) 997 { 998 if (isset($matches['tzh'])) 999 { 1000 // Use TZ offset from datetime string 1001 $offset = $matches['tzs'] . ($matches['tzh'] * 3600 + $matches['tzm'] * 60); // Offset from GMT in seconds 1002 } else { 1003 // Or use system offset 1004 $offset = $timezone['php_offset']; 1005 } 1006 $time_tmp = mktime($matches['hour'], $matches['min'], $matches['sec'], $matches['mon'], $matches['day'], $matches['year']); // Generate unixtime 1007 1008 $time_gmt = $time_tmp + ($offset * -1); // Unixtime from string in GMT 1009 $time_local = $time_gmt + $timezone['php_offset']; // Unixtime from string in local timezone 1010 } else { 1011 $time_local = time(); // Current unixtime with local timezone 1012 $time_gmt = $time_local - $timezone['php_offset']; // Current unixtime in GMT 1013 } 1014 1015 if (OBS_DEBUG > 1) 1016 { 1017 $debug_msg = 'UNIXTIME from DATETIME "' . ($time_tmp ? $datetime : 'time()') . '": '; 1018 $debug_msg .= 'LOCAL (' . format_unixtime($time_local) . '), GMT (' . format_unixtime($time_gmt) . '), TZ (' . $timezone['php'] . ')'; 1019 print_message($debug_msg); 1020 } 1021 1022 if ($use_gmt) 1023 { 1024 return ($time_gmt); 1025 } else { 1026 return ($time_local); 1027 } 1028} 1029 1030// DOCME needs phpdoc block 1031# If a device is up, return its uptime, otherwise return the 1032# time since the last time we were able to poll it. This 1033# is not very accurate, but better than reporting what the 1034# uptime was at some time before it went down. 1035// TESTME needs unit testing 1036function deviceUptime($device, $format = "long") 1037{ 1038 if ($device['status'] == 0) { 1039 if ($device['last_polled'] == 0) { 1040 return "Never polled"; 1041 } 1042 $since = time() - strtotime($device['last_polled']); 1043 //$reason = isset($device['status_type']) && $format == 'long' ? '('.strtoupper($device['status_type']).') ' : ''; 1044 $reason = isset($device['status_type']) ? '('.strtoupper($device['status_type']).') ' : ''; 1045 1046 return "Down $reason" . format_uptime($since, $format); 1047 } else { 1048 return format_uptime($device['uptime'], $format); 1049 } 1050} 1051 1052/** 1053 * Format seconds to requested time format. 1054 * 1055 * Default format is "long". 1056 * 1057 * Supported formats: 1058 * long => '1 year, 1 day, 1h 1m 1s' 1059 * longest => '1 year, 1 day, 1 hour 1 minute 1 second' 1060 * short-3 => '1y 1d 1h' 1061 * short-2 => '1y 1d' 1062 * shorter => *same as short-2 above 1063 * (else) => '1y 1d 1h 1m 1s' 1064 * 1065 * @param int|string $uptime Time is seconds 1066 * @param string $format Optional format 1067 * 1068 * @return string 1069 */ 1070function format_uptime($uptime, $format = "long") 1071{ 1072 $uptime = intval(round($uptime, 0)); 1073 if ($uptime <= 0) { return '0s'; } 1074 1075 $up['y'] = floor($uptime / 31536000); 1076 $up['d'] = floor($uptime % 31536000 / 86400); 1077 $up['h'] = floor($uptime % 86400 / 3600); 1078 $up['m'] = floor($uptime % 3600 / 60); 1079 $up['s'] = floor($uptime % 60); 1080 1081 $result = ''; 1082 1083 if ($format == 'long' || $format == 'longest') 1084 { 1085 if ($up['y'] > 0) { 1086 $result .= $up['y'] . ' year'. ($up['y'] != 1 ? 's' : ''); 1087 if ($up['d'] > 0 || $up['h'] > 0 || $up['m'] > 0 || $up['s'] > 0) { $result .= ', '; } 1088 } 1089 1090 if ($up['d'] > 0) { 1091 $result .= $up['d'] . ' day' . ($up['d'] != 1 ? 's' : ''); 1092 if ($up['h'] > 0 || $up['m'] > 0 || $up['s'] > 0) { $result .= ', '; } 1093 } 1094 1095 if ($format == 'longest') 1096 { 1097 if ($up['h'] > 0) { $result .= $up['h'] . ' hour' . ($up['h'] != 1 ? 's ' : ' '); } 1098 if ($up['m'] > 0) { $result .= $up['m'] . ' minute' . ($up['m'] != 1 ? 's ' : ' '); } 1099 if ($up['s'] > 0) { $result .= $up['s'] . ' second' . ($up['s'] != 1 ? 's ' : ' '); } 1100 } else { 1101 if ($up['h'] > 0) { $result .= $up['h'] . 'h '; } 1102 if ($up['m'] > 0) { $result .= $up['m'] . 'm '; } 1103 if ($up['s'] > 0) { $result .= $up['s'] . 's '; } 1104 } 1105 } else { 1106 $count = 6; 1107 if ($format == 'short-3') { $count = 3; } 1108 elseif ($format == 'short-2' || $format == 'shorter') { $count = 2; } 1109 1110 foreach ($up as $period => $value) 1111 { 1112 if ($value == 0) { continue; } 1113 $result .= $value.$period.' '; 1114 $count--; 1115 if ($count == 0) { break; } 1116 } 1117 } 1118 1119 return trim($result); 1120} 1121 1122/** 1123 * This function convert human written Uptime to seconds. 1124 * Opposite function for format_uptime(). 1125 * 1126 * Also applicable for some uptime formats in MIB, like EigrpUpTimeString: 1127 * 'hh:mm:ss', reflecting hours, minutes, and seconds 1128 * If the up time is greater than 24 hours, is less precise and 1129 * the minutes and seconds are not reflected. Instead only the days 1130 * and hours are shown and the string will be formatted like this: 'xxxdxxh' 1131 * 1132 * @param string $uptime Uptime in human readable string or timetick 1133 * @return int Uptime in seconds 1134 */ 1135function uptime_to_seconds($uptime) 1136{ 1137 if (str_contains($uptime, ':')) { 1138 $seconds = timeticks_to_sec($uptime); 1139 } else { 1140 $seconds = age_to_seconds($uptime); 1141 } 1142 1143 return $seconds; 1144} 1145 1146/** 1147 * Get current timezones for mysql and php. 1148 * Use this function when need display timedate from mysql 1149 * for fix diffs betwen this timezones 1150 * 1151 * Example: 1152 * Array 1153 * ( 1154 * [mysql] => +03:00 1155 * [php] => +03:00 1156 * [php_abbr] => MSK 1157 * [php_offset] => +10800 1158 * [mysql_offset] => +10800 1159 * [diff] => 0 1160 * ) 1161 * 1162 * @return array Timezones info 1163 */ 1164// MOVEME to includes/functions.inc.php 1165function get_timezone() 1166{ 1167 global $cache; 1168 1169 if (!isset($cache['timezone'])) 1170 { 1171 $timezone = array(); 1172 $timezone['system'] = external_exec('date "+%:z"'); // return '+03:00' 1173 if (!OBS_DB_SKIP) 1174 { 1175 $timezone['mysql'] = dbFetchCell('SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP);'); // return '03:00:00' 1176 if ($timezone['mysql'][0] != '-') 1177 { 1178 $timezone['mysql'] = '+'.$timezone['mysql']; 1179 } 1180 $timezone['mysql'] = preg_replace('/:00$/', '', $timezone['mysql']); // convert to '+03:00' 1181 } 1182 $timezone['php'] = date('P'); // return '+03:00' 1183 $timezone['php_abbr'] = date('T'); // return 'MSK' 1184 $timezone['php_name'] = date('e'); // return 'Europe/Moscow' 1185 $timezone['php_daylight'] = date('I'); // return '0' 1186 1187 foreach (array('php', 'mysql') as $entry) 1188 { 1189 if (!isset($timezone[$entry])) { continue; } // skip mysql if OBS_DB_SKIP 1190 1191 $sign = $timezone[$entry][0]; 1192 list($hours, $minutes) = explode(':', $timezone[$entry]); 1193 $timezone[$entry . '_offset'] = $sign . (abs($hours) * 3600 + $minutes * 60); // Offset from GMT in seconds 1194 } 1195 1196 if (OBS_DB_SKIP) 1197 { 1198 // If mysql skipped, just return system/php timezones without caching 1199 return $timezone; 1200 } 1201 1202 // Get get the difference in sec between mysql and php timezones 1203 $timezone['diff'] = (int)$timezone['mysql_offset'] - (int)$timezone['php_offset']; 1204 $cache['timezone'] = $timezone; 1205 } 1206 1207 return $cache['timezone']; 1208} 1209 1210// DOCME needs phpdoc block 1211function humanspeed($speed) 1212{ 1213 if ($speed == '') 1214 { 1215 return '-'; 1216 } else { 1217 return formatRates($speed); 1218 } 1219} 1220 1221/** 1222 * Convert common MAC strings to fixed 12 char string 1223 * @param string $mac MAC string (ie: 66:c:9b:1b:62:7e, 00 02 99 09 E9 84) 1224 * @return string Cleaned MAC string (ie: 00029909e984) 1225 */ 1226function mac_zeropad($mac) 1227{ 1228 $mac = strtolower(trim($mac)); 1229 if (strpos($mac, ':') !== FALSE) 1230 { 1231 // STRING: 66:c:9b:1b:62:7e 1232 $mac_parts = explode(':', $mac); 1233 if (count($mac_parts) == 6) 1234 { 1235 $mac = ''; 1236 foreach ($mac_parts as $part) 1237 { 1238 $mac .= zeropad($part); 1239 } 1240 } 1241 } else { 1242 // Hex-STRING: 00 02 99 09 E9 84 1243 // Cisco MAC: 1234.5678.9abc 1244 // Some other: 0x123456789ABC 1245 $mac = str_replace(array(' ', '.', '0x'), '', $mac); 1246 } 1247 1248 if (strlen($mac) == 12 && ctype_xdigit($mac)) 1249 { 1250 $mac_clean = $mac; 1251 } else { 1252 $mac_clean = NULL; 1253 } 1254 1255 return $mac_clean; 1256} 1257 1258// DOCME needs phpdoc block 1259// TESTME needs unit testing 1260function format_mac($mac) 1261{ 1262 // Strip out non-hex digits 1263 $mac = preg_replace('/[[:^xdigit:]]/', '', strtolower($mac)); 1264 // Add colons 1265 $mac = preg_replace('/([[:xdigit:]]{2})(?!$)/', '$1:', $mac); 1266 // Convert fake MACs to IP 1267 //if (preg_match('/ff:fe:([[:xdigit:]]+):([[:xdigit:]]+):([[:xdigit:]]+):([[:xdigit:]]{1,2})/', $mac, $matches)) 1268 if (preg_match('/ff:fe:([[:xdigit:]]{2}):([[:xdigit:]]{2}):([[:xdigit:]]{2}):([[:xdigit:]]{2})/', $mac, $matches)) 1269 { 1270 if ($matches[1] == '00' && $matches[2] == '00') 1271 { 1272 $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) 1273 } else { 1274 $mac = hexdec($matches[1]).'.'.hexdec($matches[2]).'.'.hexdec($matches[3]).'.'.hexdec($matches[4]); 1275 } 1276 } 1277 1278 return $mac; 1279} 1280 1281// DOCME needs phpdoc block 1282// TESTME needs unit testing 1283function format_number_short($number, $sf) 1284{ 1285 // This formats a number so that we only send back three digits plus an optional decimal point. 1286 // Example: 723.42 -> 723 72.34 -> 72.3 2.23 -> 2.23 1287 1288 list($whole, $decimal) = explode('.', $number); 1289 $whole_len = strlen($whole); 1290 1291 if ($whole_len >= $sf || !is_numeric($decimal)) 1292 { 1293 $number = $whole; 1294 } 1295 else if ($whole_len < $sf) 1296 { 1297 $number = $whole; 1298 $diff = $sf - $whole_len; 1299 $decimal = rtrim(substr($decimal, 0, $diff), '0'); 1300 if (strlen($decimal)) 1301 { 1302 $number .= '.' . $decimal; 1303 } 1304 } 1305 return $number; 1306} 1307 1308/** 1309 * Detect if required exec functions available 1310 * 1311 * @return boolean TRUE if proc_open/proc_get_status available and not in safe mode. 1312 */ 1313function is_exec_available() 1314{ 1315 // Detect that function ini_get() not disabled too 1316 if (!function_exists('ini_get')) 1317 { 1318 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.'); 1319 1320 return TRUE; // NOTE, this is not a critical function for functionally 1321 } 1322 1323 $required_functions = array('proc_open', 'proc_get_status'); 1324 $disabled_functions = explode(',', ini_get('disable_functions')); 1325 foreach ($required_functions as $func) 1326 { 1327 if (in_array($func, $disabled_functions)) 1328 { 1329 print_error('ERROR: Function ' . $func . '() is disabled via the `disable_functions` option in php.ini configuration file. Please clean this function from this list.'); 1330 return FALSE; 1331 } 1332 } 1333 1334 /* 1335 // Detect safe mode 1336 $safe_mode = ini_get('safe_mode'); 1337 if (strtolower($safe_mode) != 'off') 1338 { 1339 return FALSE; 1340 } 1341 */ 1342 1343 return TRUE; 1344} 1345 1346// DOCME needs phpdoc block 1347function external_exec($command, $timeout = NULL) 1348{ 1349 global $exec_status; 1350 1351 $command = trim($command); 1352 1353 // Debug the command *before* we run it! 1354 if (OBS_DEBUG > 0) 1355 { 1356 $debug_command = ($command === '' && isset($GLOBALS['snmp_command'])) ? $GLOBALS['snmp_command'] : $command; 1357 if (OBS_DEBUG < 2 && $GLOBALS['config']['snmp']['hide_auth'] && 1358 preg_match("/snmp(bulk)?(get|getnext|walk)(\s+-(t|r|Cr)['\d\s]+){0,3}(\s+-Cc)?\s+-v[123]c?\s+/", $debug_command)) 1359 { 1360 // Hide snmp auth params from debug cmd out, 1361 // for help users who want send debug output to developers 1362 $pattern = "/\s+(?:(-[uxXaA])\s*(?:'.*?')|(-c)\s*(?:'.*?(@\S+)?'))/"; // do not hide contexts, only community and v3 auth 1363 $debug_command = preg_replace($pattern, ' \1\2 ***\3', $debug_command); 1364 } 1365 print_message(PHP_EOL . 'CMD[%y' . $debug_command . '%n]' . PHP_EOL, 'console'); 1366 } 1367 1368 $exec_status = array('command' => $command, 1369 'exitdelay' => 0); 1370 if ($command === '') 1371 { 1372 // Hardcode exec_status if empty command passed 1373 if (isset($GLOBALS['snmp_command'])) 1374 { 1375 $exec_status['command'] = $GLOBALS['snmp_command']; 1376 unset($GLOBALS['snmp_command']); // Now clean this global var 1377 } 1378 $exec_status['exitcode'] = -1; 1379 $exec_status['endtime'] = microtime(TRUE); // store end unixtime with microseconds 1380 $exec_status['runtime'] = 0; 1381 $exec_status['stderr'] = 'Empty command passed'; 1382 $exec_status['stdout'] = ''; 1383 1384 if (OBS_DEBUG > 0) 1385 { 1386 print_message('CMD EXITCODE['.($exec_status['exitcode'] !== 0 ? '%r' : '%g').$exec_status['exitcode'].'%n]'.PHP_EOL. 1387 'CMD RUNTIME['.($exec_status['runtime'] > 7 ? '%r' : '%g').round($exec_status['runtime'], 4).'s%n]', 'console'); 1388 print_message("STDOUT[\n\n]", 'console', FALSE); 1389 if ($exec_status['exitcode'] && $exec_status['stderr']) 1390 { 1391 // Show stderr if exitcode not 0 1392 print_message("STDERR[\n".$exec_status['stderr']."\n]", 'console', FALSE); 1393 } 1394 } 1395 return ''; 1396 } 1397 1398 if (is_numeric($timeout) && $timeout > 0) 1399 { 1400 $timeout_usec = $timeout * 1000000; 1401 $timeout = 0; 1402 } else { 1403 // set timeout to null (not to 0!), see stream_select() description 1404 $timeout_usec = NULL; 1405 $timeout = NULL; 1406 } 1407 1408 $descriptorspec = array( 1409 //0 => array('pipe', 'r'), // stdin 1410 1 => array('pipe', 'w'), // stdout 1411 2 => array('pipe', 'w') // stderr 1412 ); 1413 1414 //$process = proc_open($command, $descriptorspec, $pipes); 1415 $process = proc_open('exec ' . $command, $descriptorspec, $pipes); // exec prevent to use shell 1416 //stream_set_blocking($pipes[0], 0); // Make stdin/stdout/stderr non-blocking 1417 stream_set_blocking($pipes[1], 0); 1418 stream_set_blocking($pipes[2], 0); 1419 1420 $stdout = $stderr = ''; 1421 $runtime = 0; 1422 if (is_resource($process)) 1423 { 1424 $start = microtime(TRUE); 1425 //while ($status['running'] !== FALSE) 1426 //while (feof($pipes[1]) === FALSE || feof($pipes[2]) === FALSE) 1427 while (TRUE) 1428 { 1429 $read = array(); 1430 if (!feof($pipes[1])) { $read[] = $pipes[1]; } 1431 if (!feof($pipes[2])) { $read[] = $pipes[2]; } 1432 if (empty($read)) { break; } 1433 $write = NULL; 1434 $except = NULL; 1435 stream_select($read, $write, $except, $timeout, $timeout_usec); 1436 1437 // Read the contents from the buffers 1438 foreach ($read as $pipe) 1439 { 1440 if ($pipe === $pipes[1]) 1441 { 1442 $stdout .= fread($pipe, 8192); 1443 } 1444 else if ($pipe === $pipes[2]) 1445 { 1446 $stderr .= fread($pipe, 8192); 1447 } 1448 } 1449 $runtime = microtime(TRUE) - $start; 1450 1451 // Get the status of the process 1452 $status = proc_get_status($process); 1453 1454 // Break from this loop if the process exited before timeout 1455 if (!$status['running']) 1456 { 1457 if (feof($pipes[1]) === FALSE) 1458 { 1459 // Very rare situation, seems as next proc_get_status() bug 1460 if (!isset($status_fix)) { $status_fix = $status; } 1461 if (OBS_DEBUG > 1) { print_debug("Wrong process status! Issue in proc_get_status(), see: https://bugs.php.net/bug.php?id=69014"); } 1462 } else { 1463 //var_dump($status); 1464 break; 1465 } 1466 } 1467 // Break from this loop if the process exited by timeout 1468 if ($timeout !== NULL) 1469 { 1470 $timeout_usec -= $runtime * 1000000; 1471 if ($timeout_usec < 0) 1472 { 1473 $status['running'] = FALSE; 1474 $status['exitcode'] = -1; 1475 break; 1476 } 1477 } 1478 } 1479 if ($status['running']) 1480 { 1481 // Fix sometimes wrong status, wait for 10 milliseconds 1482 $delay = 0; 1483 $delay_step = 10000; // 10ms 1484 $delay_max = 300000; // 300ms 1485 while ($status['running'] && $delay < $delay_max) 1486 { 1487 usleep($delay_step); 1488 $status = proc_get_status($process); 1489 $delay += $delay_step; 1490 } 1491 $exec_status['exitdelay'] = intval($delay / 1000); // Convert to ms 1492 } 1493 else if (isset($status_fix)) 1494 { 1495 // See fixed proc_get_status() above 1496 $status = $status_fix; 1497 } 1498 $exec_status['exitcode'] = (int)$status['exitcode']; 1499 $exec_status['stderr'] = rtrim($stderr); 1500 $stdout = preg_replace('/(?:\n|\r\n|\r)$/D', '', $stdout); // remove last (only) eol 1501 } else { 1502 $stdout = FALSE; 1503 $exec_status['stderr'] = ''; 1504 $exec_status['exitcode'] = -1; 1505 } 1506 proc_terminate($process, 9); 1507 //fclose($pipes[0]); 1508 fclose($pipes[1]); 1509 fclose($pipes[2]); 1510 1511 $exec_status['endtime'] = $start + $runtime; // store end unixtime with microseconds 1512 $exec_status['runtime'] = $runtime; 1513 $exec_status['stdout'] = $stdout; 1514 1515 if (OBS_DEBUG > 0) 1516 { 1517 print_message('CMD EXITCODE['.($exec_status['exitcode'] !== 0 ? '%r' : '%g').$exec_status['exitcode'].'%n]'.PHP_EOL. 1518 'CMD RUNTIME['.($runtime > 7 ? '%r' : '%g').round($runtime, 4).'s%n]', 'console'); 1519 if ($exec_status['exitdelay'] > 0) 1520 { 1521 print_message("CMD EXITDELAY[%r".$exec_status['exitdelay']."ms%n]", 'console', FALSE); 1522 } 1523 print_message("STDOUT[\n".$stdout."\n]", 'console', FALSE); 1524 if ($exec_status['exitcode'] && $exec_status['stderr']) 1525 { 1526 // Show stderr if exitcode not 0 1527 print_message("STDERR[\n".$exec_status['stderr']."\n]", 'console', FALSE); 1528 } 1529 } 1530 1531 return $stdout; 1532} 1533 1534/** 1535 * Get information about process by it identifier (pid) 1536 * 1537 * @param int $pid The process identifier. 1538 * @param boolean $stats If true, additionally show cpu/memory stats 1539 * @return array Array with information about process, If process not found, return FALSE 1540 */ 1541function get_pid_info($pid, $stats = FALSE) 1542{ 1543 $pid = intval($pid); 1544 if ($pid < 1) 1545 { 1546 print_debug("Incorrect PID passed"); 1547 //trigger_error("PID ".$pid." doesn't exists", E_USER_WARNING); 1548 return FALSE; 1549 } 1550 1551 if (!$stats && stripos(PHP_OS, 'Linux') === 0) 1552 { 1553 // Do not use call to ps on Linux and extended stat not required 1554 // FIXME. Need something same on BSD and other Unix platforms 1555 1556 if ($pid_stat = lstat("/proc/$pid")) 1557 { 1558 $pid_info = array('PID' => "$pid"); 1559 $ps_stat = explode(" ", file_get_contents("/proc/$pid/stat")); 1560 //echo PHP_EOL; print_vars($ps_stat); echo PHP_EOL; 1561 //echo PHP_EOL; print_vars($pid_stat); echo PHP_EOL; 1562 $pid_info['PPID'] = $ps_stat[3]; 1563 $pid_info['UID'] = $pid_stat['uid'].''; 1564 $pid_info['GID'] = $pid_stat['gid'].''; 1565 $pid_info['STAT'] = $ps_stat[2]; 1566 $pid_info['COMMAND'] = trim(str_replace("\0", " ", file_get_contents("/proc/$pid/cmdline"))); 1567 $pid_info['STARTED'] = date("r", $pid_stat['mtime']); 1568 $pid_info['STARTED_UNIX'] = $pid_stat['mtime']; 1569 } else { 1570 $pid_info = FALSE; 1571 } 1572 1573 } else { 1574 // Use ps call, have troubles on high load systems! 1575 1576 if ($stats) 1577 { 1578 // Add CPU/Mem stats 1579 $options = 'pid,ppid,uid,gid,pcpu,pmem,vsz,rss,tty,stat,time,lstart,args'; 1580 } else { 1581 $options = 'pid,ppid,uid,gid,tty,stat,time,lstart,args'; 1582 } 1583 1584 $timezone = get_timezone(); // Get system timezone info, for correct started time conversion 1585 1586 $ps = external_exec('/bin/ps -ww -o '.$options.' -p '.$pid, 1); // Set timeout 1sec for exec 1587 $ps = explode("\n", rtrim($ps)); 1588 1589 if ($GLOBALS['exec_status']['exitcode'] === 127) 1590 { 1591 print_debug("/bin/ps command not found, not possible to get process info."); 1592 return NULL; 1593 } 1594 else if ($GLOBALS['exec_status']['exitcode'] !== 0 || count($ps) < 2) 1595 { 1596 print_debug("PID ".$pid." doesn't exists"); 1597 //trigger_error("PID ".$pid." doesn't exists", E_USER_WARNING); 1598 return FALSE; 1599 } 1600 1601 // " PID PPID UID GID %CPU %MEM VSZ RSS TT STAT TIME STARTED COMMAND" 1602 // "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" 1603 // 1604 // " PID PPID UID GID TT STAT TIME STARTED COMMAND" 1605 // "14675 10250 1000 1000 pts/4 S+ 00:00:00 Mon Mar 21 14:48:08 2016 php ./test_pid.php" 1606 //print_vars($ps); 1607 1608 // Parse output 1609 $keys = preg_split("/\s+/", $ps[0], -1, PREG_SPLIT_NO_EMPTY); 1610 $entries = preg_split("/\s+/", $ps[1], count($keys) - 1, PREG_SPLIT_NO_EMPTY); 1611 $started = preg_split("/\s+/", array_pop($entries), 6, PREG_SPLIT_NO_EMPTY); 1612 $command = array_pop($started); 1613 1614 $started[] = str_replace(':', '', $timezone['system']); // Add system TZ to started time 1615 $started_rfc = array_shift($started) . ','; // Sun 1616 // Reimplode and convert to RFC2822 started date 'Sun, 20 Mar 2016 18:01:53 +0300' 1617 $started_rfc .= ' ' . $started[1]; // 20 1618 $started_rfc .= ' ' . $started[0]; // Mar 1619 $started_rfc .= ' ' . $started[3]; // 2016 1620 $started_rfc .= ' ' . $started[2]; // 18:01:53 1621 $started_rfc .= ' ' . $started[4]; // +0300 1622 //$started_rfc .= implode(' ', $started); 1623 $entries[] = $started_rfc; 1624 1625 $entries[] = $command; // Re-add command 1626 //print_vars($entries); 1627 //print_vars($started); 1628 1629 $pid_info = array(); 1630 foreach ($keys as $i => $key) 1631 { 1632 $pid_info[$key] = $entries[$i]; 1633 } 1634 $pid_info['STARTED_UNIX'] = strtotime($pid_info['STARTED']); 1635 //print_vars($pid_info); 1636 1637 } 1638 1639 return $pid_info; 1640} 1641 1642/** 1643 * Add information about process into DB 1644 * 1645 * @param string $process_name Process identicator 1646 * @param array $device Device array 1647 * @param int $pid PID for process. If empty used current PHP process ID 1648 * @return int DB id for inserted row 1649 */ 1650function add_process_info($device, $pid = NULL) 1651{ 1652 global $argv; 1653 1654 // Check if device_id passed instead array 1655 if (is_numeric($device)) 1656 { 1657 $device = array('device_id' => $device); 1658 } 1659 if (!is_numeric($pid)) 1660 { 1661 $pid = getmypid(); 1662 } 1663 $pid_info = get_pid_info($pid); 1664 1665 if (is_array($pid_info)) 1666 { 1667 $process_name = basename($argv[0]); 1668 if ($process_name == 'poller.php' || $process_name == 'alerter.php') 1669 { 1670 // Try detect parent poller wrapper 1671 $parent_info = $pid_info; 1672 do 1673 { 1674 $found = FALSE; 1675 $parent_info = get_pid_info($parent_info['PPID']); 1676 if (strpos($parent_info['COMMAND'], $process_name) !== FALSE) 1677 { 1678 $found = TRUE; 1679 } 1680 else if (strpos($parent_info['COMMAND'], 'poller-wrapper.py') !== FALSE) 1681 { 1682 $pid_info['PPID'] = $parent_info['PID']; 1683 } 1684 } while ($found); 1685 } 1686 $update_array = array( 1687 'process_pid' => $pid, 1688 'process_name' => $process_name, 1689 'process_ppid' => $pid_info['PPID'], 1690 'process_uid' => $pid_info['UID'], 1691 'process_command' => $pid_info['COMMAND'], 1692 'process_start' => $pid_info['STARTED_UNIX'], 1693 'device_id' => $device['device_id'] 1694 ); 1695 return dbInsert($update_array, 'observium_processes'); 1696 } 1697 print_debug("Process info not added for PID: $pid"); 1698} 1699 1700function del_process_info($device, $pid = NULL) 1701{ 1702 global $argv; 1703 1704 // Check if device_id passed instead array 1705 if (is_numeric($device)) 1706 { 1707 $device = array('device_id' => $device); 1708 } 1709 if (!is_numeric($pid)) 1710 { 1711 $pid = getmypid(); 1712 } 1713 1714 return dbDelete('observium_processes', '`process_pid` = ? AND `process_name` = ? AND `device_id` = ?', array($pid, basename($argv[0]), $device['device_id'])); 1715} 1716 1717function check_process_run($device, $pid = NULL) 1718{ 1719 global $argv; 1720 1721 $process_name = basename($argv[0]); 1722 $check = FALSE; 1723 1724 // Check if device_id passed instead array 1725 if (is_numeric($device)) 1726 { 1727 $device = array('device_id' => $device); 1728 } 1729 1730 $query = 'SELECT * FROM `observium_processes` WHERE `process_name` = ? AND `device_id` = ?'; 1731 $params = array($process_name, $device['device_id']); 1732 if (is_numeric($pid)) 1733 { 1734 $query .= ' AND `process_pid` = ?'; 1735 $params[] = intval($pid); 1736 } 1737 1738 foreach (dbFetchRows($query, $params) as $process) 1739 { 1740 // We found processes in DB, check if it exist on system 1741 $pid_info = get_pid_info($process['process_pid']); 1742 if (is_array($pid_info) && strpos($pid_info['COMMAND'], $process_name) !== FALSE) 1743 { 1744 // Process still running 1745 $check = array_merge($pid_info, $process); 1746 } else { 1747 // Remove stalled DB entries 1748 dbDelete('observium_processes', '`process_id` = ?', array($process['process_id'])); 1749 } 1750 } 1751 1752 return $check; 1753} 1754 1755/** 1756 * Determine array is associative or sequential? 1757 * 1758 * @param array 1759 * @return boolean 1760 */ 1761function is_array_assoc($array) 1762{ 1763 return (is_array($array) && $array !== array_values($array)); 1764} 1765 1766function array_get_nested($array, $string, $delimiter = '->') 1767{ 1768 foreach (explode($delimiter, $string) as $key) 1769 { 1770 if (!array_key_exists($key, $array)) 1771 { 1772 return NULL; 1773 } 1774 $array = $array[$key]; 1775 } 1776 1777 return $array; 1778} 1779 1780/** 1781 * Fast string compare function, checks if string contain $needle 1782 * 1783 * @param string $string The string to search in 1784 * @param mixed $needle If needle is not a string, it is converted to an string 1785 * @param mixed $encoding For use "slow" multibyte compare, pass required encoding here (ie: UTF-8) 1786 * @param bool $case_insensitivity If case_insensitivity is TRUE, comparison is case insensitive 1787 * @return bool Returns TRUE if $string starts with $needle or FALSE otherwise 1788 */ 1789function str_contains($string, $needle, $encoding = FALSE, $case_insensitivity = FALSE) 1790{ 1791 // If needle is array, use recursive compare 1792 if (is_array($needle)) 1793 { 1794 foreach ($needle as $findme) 1795 { 1796 if (str_contains($string, (string)$findme, $encoding, $case_insensitivity)) 1797 { 1798 return TRUE; 1799 } 1800 } 1801 return FALSE; 1802 } 1803 1804 $needle = (string)$needle; 1805 $compare = $string === $needle; 1806 if ($case_insensitivity) 1807 { 1808 // Case-INsensitive 1809 1810 // NOTE, multibyte compare required mb_* functions and slower than general functions 1811 if ($encoding && check_extension_exists('mbstring') && mb_strlen($string, $encoding) != strlen($string)) 1812 { 1813 //$encoding = 'UTF-8'; 1814 //return mb_strripos($string, $needle, -mb_strlen($string, $encoding), $encoding) !== FALSE; 1815 return $compare || mb_stripos($string, $needle) !== FALSE; 1816 } 1817 1818 return $compare || stripos($string, $needle) !== FALSE; 1819 } else { 1820 // Case-sensitive 1821 return $compare || strpos($string, $needle) !== FALSE; 1822 } 1823} 1824 1825function str_icontains($string, $needle, $encoding = FALSE) 1826{ 1827 return str_contains($string, $needle, $encoding, TRUE); 1828} 1829 1830/** 1831 * Fast string compare function, checks if string begin with $needle 1832 * 1833 * @param string $string The string to search in 1834 * @param mixed $needle If needle is not a string, it is converted to an string 1835 * @param mixed $encoding For use "slow" multibyte compare, pass required encoding here (ie: UTF-8) 1836 * @param binary $case_insensitivity If case_insensitivity is TRUE, comparison is case insensitive 1837 * @return binary Returns TRUE if $string starts with $needle or FALSE otherwise 1838 */ 1839function str_starts($string, $needle, $encoding = FALSE, $case_insensitivity = FALSE) 1840{ 1841 // If needle is array, use recursive compare 1842 if (is_array($needle)) 1843 { 1844 foreach ($needle as $findme) 1845 { 1846 if (str_starts($string, (string)$findme, $encoding, $case_insensitivity)) 1847 { 1848 return TRUE; 1849 } 1850 } 1851 return FALSE; 1852 } 1853 1854 $needle = (string)$needle; 1855 if ($case_insensitivity) 1856 { 1857 // Case-INsensitive 1858 1859 // NOTE, multibyte compare required mb_* functions and slower than general functions 1860 if ($encoding && check_extension_exists('mbstring') && mb_strlen($string, $encoding) != strlen($string)) 1861 { 1862 //$encoding = 'UTF-8'; 1863 return mb_strripos($string, $needle, -mb_strlen($string, $encoding), $encoding) !== FALSE; 1864 } 1865 1866 return $needle !== '' 1867 ? strncasecmp($string, $needle, strlen($needle)) === 0 1868 : $string === ''; 1869 } else { 1870 // Case-sensitive 1871 return $string[0] === $needle[0] 1872 ? strncmp($string, $needle, strlen($needle)) === 0 1873 : FALSE; 1874 } 1875} 1876 1877function str_istarts($string, $needle, $encoding = FALSE) 1878{ 1879 return str_starts($string, $needle, $encoding, TRUE); 1880} 1881 1882/** 1883 * Fast string compare function, checks if string end with $needle 1884 * 1885 * @param string $string The string to search in 1886 * @param mixed $needle If needle is not a string, it is converted to an string 1887 * @param mixed $encoding For use "slow" multibyte compare, pass required encoding here (ie: UTF-8) 1888 * @param binary $case_insensitivity If case_insensitivity is TRUE, comparison is case insensitive 1889 * @return binary Returns TRUE if $string ends with $needle or FALSE otherwise 1890 */ 1891function str_ends($string, $needle, $encoding = FALSE, $case_insensitivity = FALSE) 1892{ 1893 // If needle is array, use recursive compare 1894 if (is_array($needle)) 1895 { 1896 foreach ($needle as $findme) 1897 { 1898 if (str_ends($string, (string)$findme, $encoding, $case_insensitivity)) 1899 { 1900 return TRUE; 1901 } 1902 } 1903 return FALSE; 1904 } 1905 1906 $needle = (string)$needle; 1907 $nlen = strlen($needle); 1908 $compare = $needle !== ''; 1909 1910 // NOTE, multibyte compare required mb_* functions and slower than general functions 1911 if ($encoding && $compare && check_extension_exists('mbstring') && mb_strlen($string, $encoding) != strlen($string)) 1912 { 1913 //$encoding = 'UTF-8'; 1914 $diff = mb_strlen($string, $encoding) - mb_strlen($needle, $encoding); 1915 if ($case_insensitivity) 1916 { 1917 return $diff >= 0 && mb_stripos($string, $needle, $diff, $encoding) !== FALSE; 1918 } else { 1919 return $diff >= 0 && mb_strpos($string, $needle, $diff, $encoding) !== FALSE; 1920 } 1921 } 1922 1923 return $compare 1924 ? substr_compare($string, $needle, -$nlen, $nlen, $case_insensitivity) === 0 1925 : $string === ''; 1926} 1927 1928function str_iends($string, $needle, $encoding = FALSE) 1929{ 1930 return str_ends($string, $needle, $encoding, TRUE); 1931} 1932 1933// DOCME needs phpdoc block 1934// TESTME needs unit testing 1935function is_cli() 1936{ 1937 if (defined('__PHPUNIT_PHAR__') && isset($GLOBALS['cache']['is_cli'])) 1938 { 1939 // Allow override is_cli() in PHPUNIT 1940 return $GLOBALS['cache']['is_cli']; 1941 } 1942 else if (!defined('OBS_CLI')) 1943 { 1944 define('OBS_CLI', php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])); 1945 } 1946 1947 return OBS_CLI; 1948} 1949 1950function cli_is_piped() 1951{ 1952 if (!defined('OBS_CLI_PIPED')) 1953 { 1954 define('OBS_CLI_PIPED', check_extension_exists('posix') && !posix_isatty(STDOUT)); 1955 } 1956 1957 return OBS_CLI_PIPED; 1958} 1959 1960// Detect if script runned from crontab 1961// DOCME needs phpdoc block 1962// TESTME needs unit testing 1963function is_cron() 1964{ 1965 if (!defined('OBS_CRON')) 1966 { 1967 $cron = is_cli() && !isset($_SERVER['TERM']); 1968 // For more accurate check if STDOUT exist (but this requires posix extension) 1969 if ($cron) 1970 { 1971 $cron = $cron && cli_is_piped(); 1972 } 1973 define('OBS_CRON', $cron); 1974 } 1975 1976 return OBS_CRON; 1977} 1978 1979// DOCME needs phpdoc block 1980// TESTME needs unit testing 1981function print_prompt($text, $default_yes = FALSE) 1982{ 1983 if (is_cli()) 1984 { 1985 if (cli_is_piped()) 1986 { 1987 // If now not have interactive TTY skip any prompts, return default 1988 $return = TRUE && $default_yes; 1989 } 1990 1991 $question = ($default_yes ? 'Y/n' : 'y/N'); 1992 echo trim($text), " [$question]: "; 1993 $handle = fopen ('php://stdin', 'r'); 1994 $line = strtolower(trim(fgets($handle, 3))); 1995 fclose($handle); 1996 if ($default_yes) 1997 { 1998 $return = ($line === 'no' || $line === 'n'); 1999 } else { 2000 $return = ($line === 'yes' || $line === 'y'); 2001 } 2002 } else { 2003 // Here placeholder for web prompt 2004 $return = TRUE && $default_yes; 2005 } 2006 2007 return $return; 2008} 2009 2010/** 2011 * This function echoes text with style 'debug', see print_message(). 2012 * Here checked constant OBS_DEBUG, if OBS_DEBUG not set output - empty. 2013 * 2014 * @param string $text 2015 * @param boolean $strip Stripe special characters (for web) or html tags (for cli) 2016 */ 2017function print_debug($text, $strip = FALSE) 2018{ 2019 if (defined('OBS_DEBUG') && OBS_DEBUG > 0) 2020 { 2021 print_message($text, 'debug', $strip); 2022 } 2023} 2024 2025/** 2026 * This function echoes text with style 'error', see print_message(). 2027 * 2028 * @param string $text 2029 * @param boolean $strip Stripe special characters (for web) or html tags (for cli) 2030 */ 2031function print_error($text, $strip = TRUE) 2032{ 2033 print_message($text, 'error', $strip); 2034} 2035 2036/** 2037 * This function echoes text with style 'warning', see print_message(). 2038 * 2039 * @param string $text 2040 * @param boolean $strip Stripe special characters (for web) or html tags (for cli) 2041 */ 2042function print_warning($text, $strip = TRUE) 2043{ 2044 print_message($text, 'warning', $strip); 2045} 2046 2047/** 2048 * This function echoes text with style 'success', see print_message(). 2049 * 2050 * @param string $text 2051 * @param boolean $strip Stripe special characters (for web) or html tags (for cli) 2052 */ 2053function print_success($text, $strip = TRUE) 2054{ 2055 print_message($text, 'success', $strip); 2056} 2057 2058/** 2059 * This function echoes text with specific styles (different for cli and web output). 2060 * 2061 * @param string $text 2062 * @param string $type Supported types: default, success, warning, error, debug 2063 * @param boolean $strip Stripe special characters (for web) or html tags (for cli) 2064 */ 2065function print_message($text, $type='', $strip = TRUE) 2066{ 2067 global $config; 2068 2069 // Do nothing if input text not any string (like NULL, array or other). (Empty string '' still printed). 2070 if (!is_string($text) && !is_numeric($text)) { return NULL; } 2071 2072 $type = trim(strtolower($type)); 2073 switch ($type) 2074 { 2075 case 'success': 2076 $color = array('cli' => '%g', // green 2077 'cli_color' => FALSE, // by default cli coloring disabled 2078 'class' => 'alert alert-success'); // green 2079 $icon = 'oicon-tick-circle'; 2080 break; 2081 case 'warning': 2082 $color = array('cli' => '%b', // blue 2083 'cli_color' => FALSE, // by default cli coloring disabled 2084 'class' => 'alert alert-warning'); // yellow 2085 $icon = 'oicon-bell'; 2086 break; 2087 case 'error': 2088 $color = array('cli' => '%r', // red 2089 'cli_color' => FALSE, // by default cli coloring disabled 2090 'class' => 'alert alert-danger'); // red 2091 $icon = 'oicon-exclamation-red'; 2092 break; 2093 case 'debug': 2094 $color = array('cli' => '%r', // red 2095 'cli_color' => FALSE, // by default cli coloring disabled 2096 'class' => 'alert alert-danger'); // red 2097 $icon = 'oicon-exclamation-red'; 2098 break; 2099 case 'color': 2100 $color = array('cli' => '', // none 2101 'cli_color' => TRUE, // allow using coloring 2102 'class' => 'alert alert-info'); // blue 2103 $icon = 'oicon-information'; 2104 break; 2105 case 'console': 2106 // This is special type used nl2br conversion for display console messages on WUI with correct line breaks 2107 $color = array('cli' => '', // none 2108 'cli_color' => TRUE, // allow using coloring 2109 'class' => 'alert alert-suppressed'); // purple 2110 $icon = 'oicon-information'; 2111 break; 2112 default: 2113 $color = array('cli' => '%W', // bold 2114 'cli_color' => FALSE, // by default cli coloring disabled 2115 'class' => 'alert alert-info'); // blue 2116 $icon = 'oicon-information'; 2117 break; 2118 } 2119 2120 if (is_cli()) 2121 { 2122 if ($strip) 2123 { 2124 $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); // Convert special HTML entities back to characters 2125 $text = str_ireplace(array('<br />', '<br>', '<br/>'), PHP_EOL, $text); // Convert html <br> to into newline 2126 $text = strip_tags($text); 2127 } 2128 if ($type == 'debug' && !$color['cli_color']) 2129 { 2130 // For debug just echo message. 2131 echo($text . PHP_EOL); 2132 } else { 2133 2134 print_cli($color['cli'].$text.'%n'.PHP_EOL, $color['cli_color']); 2135 2136 } 2137 } else { 2138 if ($text === '') { return NULL; } // Do not web output if the string is empty 2139 if ($strip) 2140 { 2141 if ($text == strip_tags($text)) 2142 { 2143 // Convert special characters to HTML entities only if text not have html tags 2144 $text = escape_html($text); 2145 } 2146 if ($color['cli_color']) 2147 { 2148 // Replace some Pear::Console_Color2 color codes with html styles 2149 $replace = array('%', // '%%' 2150 '</span>', // '%n' 2151 '<span class="label label-warning">', // '%y' 2152 '<span class="label label-success">', // '%g' 2153 '<span class="label label-danger">', // '%r' 2154 '<span class="label label-primary">', // '%b' 2155 '<span class="label label-info">', // '%c' 2156 '<span class="label label-default">', // '%W' 2157 '<span class="label label-default" style="color:black;">', // '%k' 2158 '<span style="font-weight: bold;">', // '%_' 2159 '<span style="text-decoration: underline;">', // '%U' 2160 ); 2161 } else { 2162 $replace = array('%', ''); 2163 } 2164 $text = str_replace(array('%%', '%n', '%y', '%g', '%r', '%b', '%c', '%W', '%k', '%_', '%U'), $replace, $text); 2165 } 2166 2167 $msg = PHP_EOL.' <div class="'.$color['class'].'">'; 2168 if ($type != 'warning' && $type != 'error') 2169 { 2170 $msg .= '<button type="button" class="close" data-dismiss="alert">×</button>'; 2171 } 2172 if ($type == 'console') 2173 { 2174 $text = nl2br(trim($text)); // Convert newline to <br /> for console messages with line breaks 2175 } 2176 2177 $msg .= ' 2178 <div>'.$text.'</div> 2179 </div>'.PHP_EOL; 2180 2181 echo($msg); 2182 } 2183} 2184 2185function print_cli($text, $colour = TRUE) 2186{ 2187 //include_once("Console/Color2.php"); 2188 2189 $msg = new Console_Color2(); 2190 2191 print $msg->convert($text, $colour); 2192} 2193 2194// TESTME needs unit testing 2195/** 2196 * Print an discovery/poller module stats 2197 * 2198 * @global array $GLOBALS['module_stats'] 2199 * @param array $device Device array 2200 * @param string $module Module name 2201 */ 2202function print_module_stats($device, $module) 2203{ 2204 $log_event = FALSE; 2205 $stats_msg = array(); 2206 foreach (array('added', 'updated', 'deleted', 'unchanged') as $key) 2207 { 2208 if ($GLOBALS['module_stats'][$module][$key]) 2209 { 2210 $stats_msg[] = (int)$GLOBALS['module_stats'][$module][$key].' '.$key; 2211 if ($key != 'unchanged') { $log_event = TRUE; } 2212 } 2213 } 2214 if (count($GLOBALS['module_stats'][$module])) { echo(PHP_EOL); } 2215 if (count($stats_msg)) { print_cli_data("Changes", implode(', ', $stats_msg)); } 2216 if ($GLOBALS['module_stats'][$module]['time']) 2217 { 2218 print_cli_data("Duration", $GLOBALS['module_stats'][$module]['time']."s"); 2219 } 2220 if ($log_event) { log_event(nicecase($module).': '.implode(', ', $stats_msg).'.', $device, 'device', $device['device_id']); } 2221} 2222 2223// DOCME needs phpdoc block 2224// TESTME needs unit testing 2225function print_obsolete_config($filter = '') 2226{ 2227 global $config; 2228 2229 $list = array(); 2230 foreach ($config['obsolete_config'] as $entry) 2231 { 2232 if ($filter && strpos($entry['old'], $filter) === FALSE) { continue; } 2233 $old = explode('->', $entry['old']); 2234 switch (count($old)) 2235 { 2236 case 1: 2237 $entry['isset'] = isset($config[$old[0]]); 2238 break; 2239 case 2: 2240 $entry['isset'] = isset($config[$old[0]][$old[1]]); 2241 break; 2242 case 3: 2243 $entry['isset'] = isset($config[$old[0]][$old[1]][$old[2]]); 2244 break; 2245 case 4: 2246 $entry['isset'] = isset($config[$old[0]][$old[1]][$old[2]][$old[3]]); 2247 break; 2248 } 2249 if ($entry['isset']) 2250 { 2251 $new = explode('->', $entry['new']); 2252 $info = (isset($entry['info']) ? ' ('.$entry['info'].')' : ''); 2253 $list[] = " %r\$config['".implode("']['", $old)."']%n --> %g\$config['".implode("']['", $new)."']%n".$info; 2254 } 2255 } 2256 2257 if ($list) 2258 { 2259 $msg = "%WWARNING.%n Found obsolete configurations in config.php, please rename respectively:\n".implode(PHP_EOL, $list); 2260 print_message($msg, 'color'); 2261 return TRUE; 2262 } else { 2263 return FALSE; 2264 } 2265} 2266 2267// Check if php extension exist, than warn or fail 2268// DOCME needs phpdoc block 2269// TESTME needs unit testing 2270function check_extension_exists($extension, $text = FALSE, $fatal = FALSE) 2271{ 2272 $exist = FALSE; 2273 $extension = strtolower($extension); 2274 $extension_functions = array( 2275 'ldap' => 'ldap_connect', 2276 'mysql' => 'mysql_connect', 2277 'mysqli' => 'mysqli_connect', 2278 'mbstring' => 'mb_detect_encoding', 2279 'mcrypt' => 'mcrypt_encrypt', // CLEANME, mcrypt not used anymore (deprecated since php 7.1, removed since php 7.2) 2280 'posix' => 'posix_isatty', 2281 'session' => 'session_name', 2282 'svn' => 'svn_log' 2283 ); 2284 2285 if (isset($extension_functions[$extension])) 2286 { 2287 $exist = @function_exists($extension_functions[$extension]); 2288 } else { 2289 $exist = @extension_loaded($extension); 2290 } 2291 2292 if (!$exist) 2293 { 2294 // Print error (only if $text not equals to FALSE) 2295 if ($text === '' || $text === TRUE) 2296 { 2297 // Generic message 2298 print_error("The extension '$extension' is missing. Please check your PHP configuration."); 2299 } 2300 elseif ($text !== FALSE) 2301 { 2302 // Custom message 2303 print_error("The extension '$extension' is missing. $text"); 2304 } else { 2305 // Debug message 2306 print_debug("The extension '$extension' is missing. Please check your PHP configuration."); 2307 } 2308 2309 // Exit if $fatal set to TRUE 2310 if ($fatal) { exit(2); } 2311 } 2312 2313 return $exist; 2314} 2315 2316// TESTME needs unit testing 2317/** 2318 * Sign function 2319 * 2320 * This function extracts the sign of the number. 2321 * Returns -1 (negative), 0 (zero), 1 (positive) 2322 * 2323 * @param integer $int 2324 * @return integer 2325 */ 2326function sgn($int) 2327{ 2328 if ($int < 0) 2329 { 2330 return -1; 2331 } elseif ($int == 0) { 2332 return 0; 2333 } else { 2334 return 1; 2335 } 2336} 2337 2338// Get port array by ID (using cache) 2339// DOCME needs phpdoc block 2340// TESTME needs unit testing 2341// MOVEME to includes/functions.inc.php 2342function get_port_by_id_cache($port_id) 2343{ 2344 return get_entity_by_id_cache('port', $port_id); 2345} 2346 2347// Get port array by ID (with port state) 2348// NOTE get_port_by_id(ID) != get_port_by_id_cache(ID) 2349// DOCME needs phpdoc block 2350// TESTME needs unit testing 2351// MOVEME to includes/functions.inc.php 2352function get_port_by_id($port_id) 2353{ 2354 if (is_numeric($port_id)) 2355 { 2356 //$port = dbFetchRow("SELECT * FROM `ports` LEFT JOIN `ports-state` ON `ports`.`port_id` = `ports-state`.`port_id` WHERE `ports`.`port_id` = ?", array($port_id)); 2357 $port = dbFetchRow("SELECT * FROM `ports` WHERE `ports`.`port_id` = ?", array($port_id)); 2358 } 2359 2360 if (is_array($port)) 2361 { 2362 //$port['port_id'] = $port_id; // It corrects the situation, when `ports-state` is empty 2363 humanize_port($port); 2364 return $port; 2365 } 2366 2367 return FALSE; 2368} 2369 2370// DOCME needs phpdoc block 2371// TESTME needs unit testing 2372// MOVEME to includes/functions.inc.php 2373function get_bill_by_id($bill_id) 2374{ 2375 $bill = dbFetchRow("SELECT * FROM `bills` WHERE `bill_id` = ?", array($bill_id)); 2376 2377 if (is_array($bill)) 2378 { 2379 return $bill; 2380 } else { 2381 return FALSE; 2382 } 2383 2384} 2385 2386// DOCME needs phpdoc block 2387// TESTME needs unit testing 2388// MOVEME to includes/functions.inc.php 2389function get_all_devices() 2390{ 2391 global $cache; 2392 2393 // FIXME needs access control checks! 2394 // FIXME respect $type (server, network, etc) -- needs an array fill in topnav. 2395 2396 $devices = array(); 2397 if (isset($cache['devices']['hostname'])) 2398 { 2399 foreach ($cache['devices']['hostname'] as $hostname => $device_id) 2400 { 2401 $devices[$device_id] = $hostname; 2402 } 2403 //$devices = array_keys($cache['devices']['hostname']); 2404 } 2405 else 2406 { 2407 foreach (dbFetchRows("SELECT `device_id`, `hostname` FROM `devices` ORDER BY `hostname`") as $data) 2408 { 2409 $devices[$data['device_id']] = $data['hostname']; 2410 } 2411 } 2412 //r($devices); 2413 //asort($devices); 2414 //r($devices); 2415 2416 return $devices; 2417} 2418 2419// DOCME needs phpdoc block 2420// TESTME needs unit testing 2421// MOVEME to includes/functions.inc.php 2422function get_application_by_id($application_id) 2423{ 2424 if (is_numeric($application_id)) 2425 { 2426 $application = dbFetchRow("SELECT * FROM `applications` WHERE `app_id` = ?", array($application_id)); 2427 } 2428 if (is_array($application)) 2429 { 2430 return $application; 2431 } else { 2432 return FALSE; 2433 } 2434} 2435 2436// DOCME needs phpdoc block 2437// TESTME needs unit testing 2438// MOVEME to includes/functions.inc.php 2439function get_device_id_by_port_id($port_id) 2440{ 2441 if (is_numeric($port_id)) 2442 { 2443 $device_id = dbFetchCell("SELECT `device_id` FROM `ports` WHERE `port_id` = ?", array($port_id)); 2444 } 2445 if (is_numeric($device_id)) 2446 { 2447 return $device_id; 2448 } else { 2449 return FALSE; 2450 } 2451} 2452 2453// DOCME needs phpdoc block 2454// TESTME needs unit testing 2455// MOVEME to includes/functions.inc.php 2456function get_device_id_by_app_id($app_id) 2457{ 2458 if (is_numeric($app_id)) 2459 { 2460 $device_id = dbFetchCell("SELECT `device_id` FROM `applications` WHERE `app_id` = ?", array($app_id)); 2461 } 2462 if (is_numeric($device_id)) 2463 { 2464 return $device_id; 2465 } else { 2466 return FALSE; 2467 } 2468} 2469 2470// DOCME needs phpdoc block 2471// TESTME needs unit testing 2472// MOVEME html/includes/functions.inc.php BUT this used in includes/rewrites.inc.php 2473function port_html_class($ifOperStatus, $ifAdminStatus, $encrypted = FALSE) 2474{ 2475 $ifclass = "interface-upup"; 2476 if ($ifAdminStatus == "down") { $ifclass = "gray"; } 2477 else if ($ifAdminStatus == "up") 2478 { 2479 if ($ifOperStatus == "down") { $ifclass = "red"; } 2480 else if ($ifOperStatus == "lowerLayerDown") { $ifclass = "orange"; } 2481 else if ($ifOperStatus == "monitoring") { $ifclass = "green"; } 2482 //else if ($encrypted === '1') { $ifclass = "olive"; } 2483 else if ($encrypted) { $ifclass = "olive"; } 2484 else if ($ifOperStatus == "up") { $ifclass = ""; } 2485 else { $ifclass = "purple"; } 2486 } 2487 2488 return $ifclass; 2489} 2490 2491// DOCME needs phpdoc block 2492// TESTME needs unit testing 2493// MOVEME to includes/functions.inc.php 2494function device_by_name($name, $refresh = 0) 2495{ 2496 // FIXME - cache name > id too. 2497 return device_by_id_cache(get_device_id_by_hostname($name), $refresh); 2498} 2499 2500// DOCME needs phpdoc block 2501// TESTME needs unit testing 2502// MOVEME to includes/functions.inc.php 2503function accesspoint_by_id($ap_id, $refresh = '0') 2504{ 2505 $ap = dbFetchRow("SELECT * FROM `accesspoints` WHERE `accesspoint_id` = ?", array($ap_id)); 2506 2507 return $ap; 2508} 2509 2510// DOCME needs phpdoc block 2511// TESTME needs unit testing 2512// MOVEME to includes/functions.inc.php 2513function device_by_id_cache($device_id, $refresh = '0') 2514{ 2515 global $cache; 2516 2517 if (!$refresh && isset($cache['devices']['id'][$device_id]) && is_array($cache['devices']['id'][$device_id])) 2518 { 2519 $device = $cache['devices']['id'][$device_id]; 2520 } else { 2521 $device = dbFetchRow("SELECT * FROM `devices` WHERE `device_id` = ?", array($device_id)); 2522 } 2523 2524 if (!empty($device)) 2525 { 2526 humanize_device($device); 2527 if ($refresh || !isset($device['graphs'])) 2528 { 2529 // Fetch device graphs 2530 $device['graphs'] = dbFetchRows("SELECT * FROM `device_graphs` WHERE `device_id` = ?", array($device_id)); 2531 } 2532 $cache['devices']['id'][$device_id] = $device; 2533 2534 return $device; 2535 } else { 2536 return FALSE; 2537 } 2538} 2539 2540// DOCME needs phpdoc block 2541// TESTME needs unit testing 2542function truncate($substring, $max = 50, $rep = '...') 2543{ 2544 if (strlen($substring) < 1) { $string = $rep; } else { $string = $substring; } 2545 $leave = $max - strlen ($rep); 2546 if (strlen($string) > $max) { return substr_replace($string, $rep, $leave); } else { return $string; } 2547} 2548 2549/** 2550 * Wrapper to htmlspecialchars() 2551 * 2552 * @param string $string 2553 */ 2554// TESTME needs unit testing 2555function escape_html($string, $flags = ENT_QUOTES) 2556{ 2557 2558 $string = htmlspecialchars($string, $flags, 'UTF-8'); 2559 2560 // Un-escape allowed tags 2561 foreach($GLOBALS['config']['escape_html']['tags'] as $tag) 2562 { 2563 $string = str_ireplace('<' .$tag.'>', '<' .$tag.'>', $string); 2564 $string = str_ireplace('</'.$tag.'>', '</'.$tag.'>', $string); 2565 } 2566 // Un-escape allowed entities 2567 foreach($GLOBALS['config']['escape_html']['entities'] as $tag) 2568 { 2569 $string = str_ireplace('&'.$tag.';', '&'.$tag.';', $string); 2570 } 2571 2572 return $string; 2573} 2574 2575// DOCME needs phpdoc block 2576// TESTME needs unit testing 2577// MOVEME to includes/functions.inc.php 2578function get_device_by_device_id($id) 2579{ 2580 global $cache; 2581 2582 if (isset($cache['devices']['id'][$id]['hostname'])) 2583 { 2584 $hostname = $cache['devices']['id'][$id]['hostname']; 2585 } 2586 else 2587 { 2588 $hostname = dbFetchCell("SELECT `hostname` FROM `devices` WHERE `device_id` = ?", array($id)); 2589 } 2590 2591 return $hostname; 2592} 2593 2594// Return random string with optional character list 2595// DOCME needs phpdoc block 2596// TESTME needs unit testing 2597function generate_random_string($max = 16, $characters = NULL) 2598{ 2599 if (!$characters || !is_string($characters)) 2600 { 2601 $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 2602 } 2603 2604 $randstring = ''; 2605 $length = strlen($characters) - 1; 2606 2607 for ($i = 0; $i < $max; $i++) 2608 { 2609 $randstring .= $characters[random_int(0, $length)]; // Require PHP 7.x or random_compat 2610 } 2611 2612 return $randstring; 2613} 2614 2615// Backward compatible random string generator 2616// DOCME needs phpdoc block 2617// TESTME needs unit testing 2618function strgen($length = 16) 2619{ 2620 return generate_random_string($length); 2621} 2622 2623// DOCME needs phpdoc block 2624// TESTME needs unit testing 2625// MOVEME to includes/functions.inc.php 2626function getpeerhost($id) 2627{ 2628 return dbFetchCell("SELECT `device_id` from `bgpPeers` WHERE `bgpPeer_id` = ?", array($id)); 2629} 2630 2631// DOCME needs phpdoc block 2632// TESTME needs unit testing 2633// MOVEME to includes/functions.inc.php 2634function get_device_id_by_hostname($hostname) 2635{ 2636 global $cache; 2637 2638 if (isset($cache['devices']['hostname'][$hostname])) 2639 { 2640 $id = $cache['devices']['hostname'][$hostname]; 2641 } 2642 else 2643 { 2644 $id = dbFetchCell("SELECT `device_id` FROM `devices` WHERE `hostname` = ?", array($hostname)); 2645 } 2646 2647 if (is_numeric($id)) 2648 { 2649 return $id; 2650 } else { 2651 return FALSE; 2652 } 2653} 2654 2655// DOCME needs phpdoc block 2656// TESTME needs unit testing 2657// MOVEME to includes/functions.inc.php 2658function gethostosbyid($id) 2659{ 2660 global $cache; 2661 2662 if (isset($cache['devices']['id'][$id]['os'])) 2663 { 2664 $os = $cache['devices']['id'][$id]['os']; 2665 } 2666 else 2667 { 2668 $os = dbFetchCell("SELECT `os` FROM `devices` WHERE `device_id` = ?", array($id)); 2669 } 2670 2671 return $os; 2672} 2673 2674// DOCME needs phpdoc block 2675// TESTME needs unit testing 2676function safename($filename) 2677{ 2678 return preg_replace('/[^a-zA-Z0-9._\-]/', '_', $filename); 2679} 2680 2681 2682// DOCME needs phpdoc block 2683// TESTME needs unit testing 2684function zeropad($num, $length = 2) 2685{ 2686 return str_pad($num, $length, '0', STR_PAD_LEFT); 2687} 2688 2689// DOCME needs phpdoc block 2690// TESTME needs unit testing 2691// RENAME to get_device_entphysical_state 2692// MOVEME to includes/functions.inc.php 2693function get_dev_entity_state($device) 2694{ 2695 $state = array(); 2696 foreach (dbFetchRows("SELECT * FROM `entPhysical-state` WHERE `device_id` = ?", array($device)) as $entity) 2697 { 2698 $state['group'][$entity['group']][$entity['entPhysicalIndex']][$entity['subindex']][$entity['key']] = $entity['value']; 2699 $state['index'][$entity['entPhysicalIndex']][$entity['subindex']][$entity['group']][$entity['key']] = $entity['value']; 2700 } 2701 2702 return $state; 2703} 2704 2705// OBSOLETE, remove when all function calls will be deleted 2706function get_dev_attrib($device, $attrib_type) 2707{ 2708 // Call to new function 2709 return get_entity_attrib('device', $device, $attrib_type); 2710} 2711 2712// OBSOLETE, remove when all function calls will be deleted 2713function get_dev_attribs($device_id) 2714{ 2715 // Call to new function 2716 return get_entity_attribs('device', $device_id); 2717} 2718 2719// OBSOLETE, remove when all function calls will be deleted 2720function set_dev_attrib($device, $attrib_type, $attrib_value) 2721{ 2722 // Call to new function 2723 return set_entity_attrib('device', $device, $attrib_type, $attrib_value); 2724} 2725 2726// OBSOLETE, remove when all function calls will be deleted 2727function del_dev_attrib($device, $attrib_type) 2728{ 2729 // Call to new function 2730 return del_entity_attrib('device', $device, $attrib_type); 2731} 2732 2733/** 2734 * Return model array from definitions, based on device sysObjectID 2735 * 2736 * @param array $device Device array required keys -> os, sysObjectID 2737 * @param string $sysObjectID_new If passed, than use "new" sysObjectID instead from device array 2738 * @return array|FALSE Model array or FALSE if no model specific definitions 2739 */ 2740function get_model_array($device, $sysObjectID_new = NULL) 2741{ 2742 global $config, $cache; 2743 2744 if (isset($config['os'][$device['os']]['model'])) 2745 { 2746 $model = $config['os'][$device['os']]['model']; 2747 $models = $config['model'][$model]; 2748 $set_cache = FALSE; 2749 if ($sysObjectID_new && preg_match('/^\.\d[\d\.]+$/', $sysObjectID_new)) 2750 { 2751 // Use passed as param sysObjectID 2752 $sysObjectID = $sysObjectID_new; 2753 } 2754 elseif (isset($cache['devices']['model'][$device['device_id']])) 2755 { 2756 // Return already cached array if no passed param sysObjectID 2757 return $cache['devices']['model'][$device['device_id']]; 2758 } 2759 elseif (preg_match('/^\.\d[\d\.]+$/', $device['sysObjectID'])) 2760 { 2761 // Use sysObjectID from device array 2762 $sysObjectID = $device['sysObjectID']; 2763 $set_cache = TRUE; 2764 } else { 2765 // Just random non empty string 2766 $sysObjectID = 'empty_sysObjectID_3948ffakc'; 2767 $set_cache = TRUE; 2768 } 2769 if ($set_cache && (!is_numeric($device['device_id']) || defined('__PHPUNIT_PHAR__'))) 2770 { 2771 // Do not set cache for unknown device_id (not added device) or phpunit 2772 $set_cache = FALSE; 2773 } 2774 if (isset($models[$sysObjectID])) 2775 { 2776 // Exactly match 2777 if ($set_cache) 2778 { 2779 $cache['devices']['model'][$device['device_id']] = $models[$sysObjectID]; 2780 } 2781 return $models[$sysObjectID]; 2782 } 2783 // Resort sysObjectID array by oids with from high to low order! 2784 //krsort($config['model'][$model]); 2785 uksort($config['model'][$model], 'compare_numeric_oids_reverse'); 2786 foreach ($config['model'][$model] as $key => $entry) 2787 { 2788 if (strpos($sysObjectID, $key) === 0) 2789 { 2790 if ($set_cache) 2791 { 2792 $cache['devices']['model'][$device['device_id']] = $entry; 2793 } 2794 return $entry; 2795 break; 2796 } 2797 } 2798 // If model array not found, set cache entry to FALSE, 2799 // for do not search again 2800 if ($set_cache) 2801 { 2802 $cache['devices']['model'][$device['device_id']] = FALSE; 2803 } 2804 } 2805 return FALSE; 2806} 2807 2808// DOCME needs phpdoc block 2809// TESTME needs unit testing 2810function formatRates($value, $round = 2, $sf = 3) 2811{ 2812 $value = format_si($value, $round, $sf) . "bps"; 2813 return $value; 2814} 2815 2816// DOCME needs phpdoc block 2817// TESTME needs unit testing 2818function formatStorage($value, $round = 2, $sf = 3) 2819{ 2820 $value = format_bi($value, $round, $sf) . 'B'; 2821 return $value; 2822} 2823 2824// DOCME needs phpdoc block 2825// TESTME needs unit testing 2826function format_si($value, $round = 2, $sf = 3) 2827{ 2828 if ($value < "0") 2829 { 2830 $neg = 1; 2831 $value = $value * -1; 2832 } 2833 2834 if ($value >= "0.1") 2835 { 2836 $sizes = Array('', 'k', 'M', 'G', 'T', 'P', 'E'); 2837 $ext = $sizes[0]; 2838 for ($i = 1; (($i < count($sizes)) && ($value >= 1000)); $i++) { $value = $value / 1000; $ext = $sizes[$i]; } 2839 } 2840 else 2841 { 2842 $sizes = Array('', 'm', 'u', 'n'); 2843 $ext = $sizes[0]; 2844 for ($i = 1; (($i < count($sizes)) && ($value != 0) && ($value <= 0.1)); $i++) { $value = $value * 1000; $ext = $sizes[$i]; } 2845 } 2846 2847 if ($neg) { $value = $value * -1; } 2848 2849 return format_number_short(round($value, $round), $sf).$ext; 2850} 2851 2852// DOCME needs phpdoc block 2853// TESTME needs unit testing 2854function format_bi($value, $round = 2, $sf = 3) 2855{ 2856 if ($value < "0") 2857 { 2858 $neg = 1; 2859 $value = $value * -1; 2860 } 2861 $sizes = Array('', 'k', 'M', 'G', 'T', 'P', 'E'); 2862 $ext = $sizes[0]; 2863 for ($i = 1; (($i < count($sizes)) && ($value >= 1024)); $i++) { $value = $value / 1024; $ext = $sizes[$i]; } 2864 2865 if ($neg) { $value = $value * -1; } 2866 2867 return format_number_short(round($value, $round), $sf).$ext; 2868} 2869 2870// DOCME needs phpdoc block 2871// TESTME needs unit testing 2872function format_number($value, $base = '1000', $round = 2, $sf = 3) 2873{ 2874 if ($base == '1000') 2875 { 2876 return format_si($value, $round, $sf); 2877 } else { 2878 return format_bi($value, $round, $sf); 2879 } 2880} 2881 2882// DOCME needs phpdoc block 2883// TESTME needs unit testing 2884function format_value($value, $format = '', $round = 2, $sf = 3) 2885{ 2886 2887 switch (strtolower($format)) 2888 { 2889 case 'si': 2890 case '1000': 2891 $value = format_si($value, $round, $sf); 2892 break; 2893 case 'bi': 2894 case '1024': 2895 $value = format_bi($value, $round, $sf); 2896 break; 2897 2898 case 'shorttime': 2899 $value = format_uptime($value, 'short'); 2900 break; 2901 2902 case 'uptime': 2903 case 'time': 2904 $value = format_uptime($value); 2905 break; 2906 2907 default: 2908 if (is_numeric($value)) 2909 { 2910 $value = sprintf("%01.{$round}f", $value); 2911 $value = preg_replace(array('/\.0+$/', '/(\.\d)0+$/'), '\1', $value); 2912 } 2913 } 2914 2915 return $value; 2916} 2917 2918/** 2919 * Is Valid Hostname 2920 * 2921 * See: http://stackoverflow.com/a/4694816 2922 * http://stackoverflow.com/a/2183140 2923 * 2924 * The Internet standards (Request for Comments) for protocols mandate that 2925 * component hostname labels may contain only the ASCII letters 'a' through 'z' 2926 * (in a case-insensitive manner), the digits '0' through '9', and the hyphen 2927 * ('-'). The original specification of hostnames in RFC 952, mandated that 2928 * labels could not start with a digit or with a hyphen, and must not end with 2929 * a hyphen. However, a subsequent specification (RFC 1123) permitted hostname 2930 * labels to start with digits. No other symbols, punctuation characters, or 2931 * white space are permitted. While a hostname may not contain other characters, 2932 * such as the underscore character (_), other DNS names may contain the underscore 2933 * 2934 * @param string $hostname 2935 * @return bool 2936 */ 2937function is_valid_hostname($hostname) 2938{ 2939 return (preg_match("/^(_?[a-z\d](-*[_a-z\d])*)(\.(_?[a-z\d](-*[_a-z\d])*))*$/i", $hostname) // valid chars check 2940 && preg_match("/^.{1,253}$/", $hostname) // overall length check 2941 && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $hostname)); // length of each label 2942 /* check for invalid starting characters 2943 if (preg_match('/^[_.-]/', $hostname)) 2944 { 2945 return FALSE; 2946 } else { 2947 return ctype_alnum(str_replace('_','',str_replace('-','',str_replace('.','',$hostname)))); 2948 } 2949 */ 2950} 2951 2952// get $host record from /etc/hosts 2953// FIXME Maybe replace the below thing with exec'ing getent? this makes hosts from LDAP and other NSS sources work as well. 2954// 2955// tom@magic:~$ getent ahostsv4 magic.powersource.cx 2956// 195.160.166.161 STREAM magic.powersource.cx 2957// tom@magic:~$ getent hosts magic.powersource.cx 2958// 2001:67c:5c:100::c3a0:a6a1 magic.powersource.cx 2959// 2960// Possibly, as above, not ideal for v4/v6 things though... but I'm not sure what the below code does for a v4 or v6 host (or both) 2961// 2962// DOCME needs phpdoc block 2963// TESTME needs unit testing 2964function ipFromEtcHosts($host) 2965{ 2966 $host = strtolower($host); 2967 try { 2968 foreach (new SplFileObject('/etc/hosts') as $line) 2969 { 2970 $d = preg_split('/\s/', $line, -1, PREG_SPLIT_NO_EMPTY); 2971 if (empty($d) || substr(reset($d), 0, 1) == '#') { continue; } 2972 //print_vars($d); 2973 $ip = array_shift($d); 2974 $hosts = array_map('strtolower', $d); 2975 if (in_array($host, $hosts)) 2976 { 2977 print_debug("Host '$host' found in hosts"); 2978 return $ip; 2979 } 2980 } 2981 } 2982 catch (Exception $e) 2983 { 2984 print_warning("Could not open the file /etc/hosts! This file should be world readable, also check that SELinux is not in enforcing mode."); 2985 } 2986 2987 return FALSE; 2988} 2989 2990// Same as gethostbyname(), but work with both IPv4 and IPv6 2991// Get the IPv4 or IPv6 address corresponding to a given Internet hostname 2992// By default return IPv4 address (A record) if exist, 2993// else IPv6 address (AAAA record) if exist. 2994// For get only IPv6 record use gethostbyname6($hostname, OBS_DNS_AAAA) 2995// DOCME needs phpdoc block 2996// TESTME needs unit testing 2997function gethostbyname6($host, $flags = OBS_DNS_ALL) 2998{ 2999 // get AAAA record for $host 3000 // if flag OBS_DNS_A is set, if AAAA fails, it tries for A 3001 // the first match found is returned 3002 // otherwise returns FALSE 3003 3004 $dns = gethostbynamel6($host, $flags); 3005 if ($dns == FALSE) 3006 { 3007 return FALSE; 3008 } else { 3009 return $dns[0]; 3010 } 3011} 3012 3013// Same as gethostbynamel(), but work with both IPv4 and IPv6 3014// By default returns both IPv4/6 addresses (A and AAAA records), 3015// for get only IPv6 addresses use gethostbynamel6($hostname, OBS_DNS_AAAA) 3016// DOCME needs phpdoc block 3017// TESTME needs unit testing 3018function gethostbynamel6($host, $flags = OBS_DNS_ALL) 3019{ 3020 // get AAAA records for $host, 3021 // if $try_a is true, if AAAA fails, it tries for A 3022 // results are returned in an array of ips found matching type 3023 // otherwise returns FALSE 3024 3025 $ip6 = array(); 3026 $ip4 = array(); 3027 3028 // First try /etc/hosts 3029 $etc = ipFromEtcHosts($host); 3030 3031 $try_a = is_flag_set(OBS_DNS_A, $flags); 3032 if ($try_a === TRUE) 3033 { 3034 if ($etc) 3035 { 3036 if (str_contains($etc, '.')) { $ip4[] = $etc; } 3037 else if (str_contains($etc, ':')) { $ip6[] = $etc; } 3038 } 3039 // Separate A and AAAA queries, see: https://www.mail-archive.com/observium@observium.org/msg09239.html 3040 $dns = dns_get_record($host, DNS_A); 3041 if (!is_array($dns)) { $dns = array(); } 3042 $dns6 = dns_get_record($host, DNS_AAAA); 3043 if (is_array($dns6)) 3044 { 3045 $dns = array_merge($dns, $dns6); 3046 } 3047 } else { 3048 if ($etc && str_contains($etc, ':')) { $ip6[] = $etc; } 3049 $dns = dns_get_record($host, DNS_AAAA); 3050 } 3051 3052 foreach ($dns as $record) 3053 { 3054 switch ($record['type']) 3055 { 3056 case 'A': 3057 $ip4[] = $record['ip']; 3058 break; 3059 case 'AAAA': 3060 $ip6[] = $record['ipv6']; 3061 break; 3062 } 3063 } 3064 3065 if ($try_a && count($ip4)) 3066 { 3067 // Merge ipv4 & ipv6 3068 $ip6 = array_merge($ip4, $ip6); 3069 } 3070 3071 if (count($ip6)) 3072 { 3073 return $ip6; 3074 } 3075 3076 return FALSE; 3077} 3078 3079// Get hostname by IP (both IPv4/IPv6) 3080// Return PTR or FALSE 3081// DOCME needs phpdoc block 3082// TESTME needs unit testing 3083function gethostbyaddr6($ip) 3084{ 3085 //include_once('Net/DNS2.php'); 3086 //include_once('Net/DNS2/RR/PTR.php'); 3087 3088 $ptr = FALSE; 3089 $resolver = new Net_DNS2_Resolver(); 3090 try 3091 { 3092 $response = $resolver->query($ip, 'PTR'); 3093 if ($response) 3094 { 3095 $ptr = $response->answer[0]->ptrdname; 3096 } 3097 } catch (Net_DNS2_Exception $e) {} 3098 3099 return $ptr; 3100} 3101 3102// DOCME needs phpdoc block 3103// TESTME needs unit testing 3104// CLEANME DEPRECATED 3105function add_service($device, $service, $descr) 3106{ 3107 $insert = array('device_id' => $device['device_id'], 'service_ip' => $device['hostname'], 'service_type' => $service, 3108 'service_changed' => array('UNIX_TIMESTAMP(NOW())'), 'service_desc' => $descr, 'service_param' => "", 'service_ignore' => "0"); 3109 3110 echo dbInsert($insert, 'services'); 3111} 3112 3113/** 3114 * Request an http(s) url. 3115 * Note. If first runtime request exit with timeout, 3116 * than will be set constant OBS_HTTP_REQUEST as FALSE 3117 * and all other requests will skipped with FALSE response! 3118 * 3119 * @param string $request Requested URL 3120 * @param array $context Set additional HTTP context options, see http://php.net/manual/en/context.http.php 3121 * @param int|boolean $rate_limit Rate limit per day for specified domain (in url). If FALSE no limits 3122 * @global array $config 3123 * @global array $GLOBALS['response_headers'] Response headers with keys: 3124 * code (HTTP code status), status (HTTP status description) and all other 3125 * @global boolean $GLOBALS['request_status'] TRUE if response code is 2xx or 3xx 3126 * 3127 * @return string|boolean Return response content or FALSE 3128 */ 3129function get_http_request($request, $context = array(), $rate_limit = FALSE) 3130{ 3131 global $config; 3132 3133 $ok = TRUE; 3134 if (defined('OBS_HTTP_REQUEST') && OBS_HTTP_REQUEST === FALSE) 3135 { 3136 print_debug("HTTP requests skipped since previous request exit with timeout"); 3137 $ok = FALSE; 3138 $GLOBALS['response_headers'] = array('code' => 408, 'descr' => 'Request Timeout'); 3139 } 3140 else if (!ini_get('allow_url_fopen')) 3141 { 3142 print_debug('HTTP requests disabled, since PHP config option "allow_url_fopen" set to off. Please enable this option in your PHP config.'); 3143 $ok = FALSE; 3144 $GLOBALS['response_headers'] = array('code' => 400, 'descr' => 'HTTP Method Disabled'); 3145 } 3146 else if (preg_match('/^https/i', $request) && !check_extension_exists('openssl')) 3147 { 3148 // Check if Secure requests allowed, but ssl extensin not exist 3149 print_debug(__FUNCTION__.'() wants to connect with https but https is not enabled on this server. Please check your PHP settings, the openssl extension must exist and be enabled.'); 3150 logfile(__FUNCTION__.'() wants to connect with https but https is not enabled on this server. Please check your PHP settings, the openssl extension must exist and be enabled.'); 3151 $ok = FALSE; 3152 $GLOBALS['response_headers'] = array('code' => 400, 'descr' => 'HTTPS Method Disabled'); 3153 } 3154 3155 if ($ok && $rate_limit && is_numeric($rate_limit) && $rate_limit >= 0) 3156 { 3157 // Check limit rates to this domain (per/day) 3158 if (preg_match('/^https?:\/\/([\w\.]+[\w\-\.]*(:\d+)?)/i', $request, $matches)) 3159 { 3160 $date = format_unixtime($config['time']['now'], 'Y-m-d'); 3161 $domain = $matches[0]; // base domain (with http(s)): https://test-me.com/ -> https://test-me.com 3162 $rate_db = json_decode(get_obs_attrib('http_rate_' . $domain), TRUE); 3163 //print_vars($date); print_vars($rate_db); 3164 if (is_array($rate_db) && isset($rate_db[$date])) 3165 { 3166 $rate_count = $rate_db[$date]; 3167 } else { 3168 $rate_count = 0; 3169 } 3170 $rate_count++; 3171 set_obs_attrib('http_rate_' . $domain, json_encode(array($date => $rate_count))); 3172 if ($rate_count > $rate_limit) 3173 { 3174 print_debug("HTTP requests skipped because the rate limit $rate_limit/day for domain '$domain' is exceeded (count: $rate_count)"); 3175 $GLOBALS['response_headers'] = array('code' => 429, 'descr' => 'Too Many Requests'); 3176 $ok = FALSE; 3177 } 3178 else if (OBS_DEBUG > 1) 3179 { 3180 print_debug("HTTP rate count for domain '$domain': $rate_count ($rate_limit/day)"); 3181 } 3182 } else { 3183 $rate_limit = FALSE; 3184 } 3185 } 3186 3187 if (OBS_DEBUG > 0) 3188 { 3189 $debug_request = $request; 3190 if (OBS_DEBUG < 2 && strpos($request, 'update.observium.org')) { $debug_request = preg_replace('/&stats=.+/', '&stats=***', $debug_request); } 3191 $debug_msg = PHP_EOL . 'REQUEST[%y' . $debug_request . '%n]'; 3192 } 3193 3194 if (!$ok) 3195 { 3196 if (OBS_DEBUG > 0) 3197 { 3198 print_message($debug_msg . PHP_EOL . 3199 'REQUEST STATUS[%rFALSE%n]' . PHP_EOL . 3200 'RESPONSE CODE[' . $GLOBALS['response_headers']['code'] . ' ' . $GLOBALS['response_headers']['descr'] . ']', 'console'); 3201 } 3202 3203 // Set GLOBAL var $request_status for use as validate status of last responce 3204 $GLOBALS['request_status'] = FALSE; 3205 return FALSE; 3206 } 3207 3208 $response = ''; 3209 3210 // Add common http context 3211 $opts = array('http' => generate_http_context_defaults($context)); 3212 3213 // Process http request and calculate runtime 3214 $start = utime(); 3215 $context = stream_context_create($opts); 3216 $response = file_get_contents($request, FALSE, $context); 3217 $runtime = utime() - $start; 3218 3219 // Parse response headers 3220 // Note: $http_response_header - see: http://php.net/manual/en/reserved.variables.httpresponseheader.php 3221 $head = array(); 3222 foreach ($http_response_header as $k => $v) 3223 { 3224 $t = explode(':', $v, 2); 3225 if (isset($t[1])) 3226 { 3227 // Date: Sat, 12 Apr 2008 17:30:38 GMT 3228 $head[trim($t[0])] = trim($t[1]); 3229 } else { 3230 // HTTP/1.1 200 OK 3231 if (preg_match("!HTTP/([\d\.]+)\s+(\d+)(.*)!", $v, $matches)) 3232 { 3233 $head['http'] = $matches[1]; 3234 $head['code'] = intval($matches[2]); 3235 $head['descr'] = trim($matches[3]); 3236 } else { 3237 $head[] = $v; 3238 } 3239 } 3240 } 3241 $GLOBALS['response_headers'] = $head; 3242 3243 // Set GLOBAL var $request_status for use as validate status of last responce 3244 if (isset($head['code']) && ($head['code'] < 200 || $head['code'] >= 400)) 3245 { 3246 $GLOBALS['request_status'] = FALSE; 3247 } 3248 elseif ($response === FALSE) 3249 { 3250 // An error in get response 3251 $GLOBALS['response_headers'] = array('code' => 408, 'descr' => 'Request Timeout'); 3252 $GLOBALS['request_status'] = FALSE; 3253 } else { 3254 // Valid statuses: 2xx Success, 3xx Redirection or head code not set (ie response not correctly parsed) 3255 $GLOBALS['request_status'] = TRUE; 3256 } 3257 3258 // Set OBS_HTTP_REQUEST for skip all other requests (FALSE for skip all other requests) 3259 if (!defined('OBS_HTTP_REQUEST')) 3260 { 3261 if ($response === FALSE && empty($http_response_header)) 3262 { 3263 $GLOBALS['response_headers'] = array('code' => 408, 'descr' => 'Request Timeout'); 3264 $GLOBALS['request_status'] = FALSE; 3265 3266 // Validate host from request and check if it timeout request 3267 if (gethostbyname6(parse_url($request, PHP_URL_HOST))) 3268 { 3269 // Timeout error, only if not received response headers 3270 define('OBS_HTTP_REQUEST', FALSE); 3271 print_debug(__FUNCTION__.'() exit with timeout. Access to outside localnet is blocked by firewall or network problems. Check proxy settings.'); 3272 logfile(__FUNCTION__.'() exit with timeout. Access to outside localnet is blocked by firewall or network problems. Check proxy settings.'); 3273 } 3274 } else { 3275 define('OBS_HTTP_REQUEST', TRUE); 3276 } 3277 } 3278 // FIXME. what if first request fine, but second broken? 3279 //else if ($response === FALSE) 3280 //{ 3281 // if (function_exists('runkit_constant_redefine')) { runkit_constant_redefine('OBS_HTTP_REQUEST', FALSE); } 3282 //} 3283 3284 if (OBS_DEBUG > 0) 3285 { 3286 // Hide extended stats in normal debug level = 1 3287 if (OBS_DEBUG < 2 && strpos($request, 'update.observium.org')) { $request = preg_replace('/&stats=.+/', '&stats=***', $request); } 3288 // Show debug info 3289 print_message($debug_msg . PHP_EOL . 3290 'REQUEST STATUS[' . ($GLOBALS['request_status'] ? '%gTRUE' : '%rFALSE') . '%n]' . PHP_EOL . 3291 'REQUEST RUNTIME['.($runtime > 3 ? '%r' : '%g').round($runtime, 4).'s%n]' . PHP_EOL . 3292 'RESPONSE CODE[' . $GLOBALS['response_headers']['code'] . ' ' . $GLOBALS['response_headers']['descr'] . ']', 'console'); 3293 if (OBS_DEBUG > 1) 3294 { 3295 print_message("RESPONSE[\n".$response."\n]", 'console', FALSE); 3296 print_vars($http_response_header); 3297 print_vars($opts); 3298 } 3299 } 3300 3301 return $response; 3302} 3303 3304/** 3305 * Process HTTP request by definition array and process it for valid status. 3306 * Used definition params in response key. 3307 * 3308 * @param string $def Definition array or alert transport key (see transports definitions) 3309 * @param string $response Response from get_http_request() 3310 * @return boolean Return TRUE if request processed with valid HTTP code (2xx, 3xx) and API response return valid param 3311 */ 3312function test_http_request($def, $response) 3313{ 3314 $response = trim($response); 3315 3316 if (is_string($def)) 3317 { 3318 // Get transport definition for responses 3319 $def = $GLOBALS['config']['transports'][$def]['notification']; 3320 } 3321 3322 // Set status by response status 3323 $success = get_http_last_status(); 3324 3325 // If response return valid code and content, additional parse for specific defined tests 3326 if ($success) 3327 { 3328 // Decode if request OK 3329 $is_response_array = FALSE; 3330 if (strtolower($def['response_format']) == 'json') 3331 { 3332 $response = json_decode($response, TRUE); 3333 $is_response_array = TRUE; 3334 } 3335 // else additional formats? 3336 3337 // Check if call succeeded 3338 if (isset($def['response_test'])) 3339 { 3340 // Convert single test condition to multi-level condition 3341 if (isset($def['response_test']['operator'])) 3342 { 3343 $def['response_test'] = array($def['response_test']); 3344 } 3345 3346 // Compare all definition fields with response, 3347 // if response param not equals to expected, set not success 3348 // multilevel keys should written with '->' separator, ie: $a[key][some][0] - key->some->0 3349 foreach ($def['response_test'] as $test) 3350 { 3351 if ($is_response_array) 3352 { 3353 $field = array_get_nested($response, $test['field']); 3354 } else { 3355 // RAW response 3356 $field = $response; 3357 } 3358 if (test_condition($field, $test['operator'], $test['value']) === FALSE) 3359 { 3360 print_debug("Response test not success: [" . $test['field'] . "] " . $test['operator'] . " [" . implode(', ', (array)$test['value']) . "]"); 3361 3362 $success = FALSE; 3363 break; 3364 } else { 3365 print_debug("Response test success: [" . $test['field'] . "] " . $test['operator'] . " [" . implode(', ', (array)$test['value']) . "]"); 3366 } 3367 } 3368 } 3369 3370 print_debug_vars($response); 3371 } 3372 3373 return $success; 3374} 3375 3376/** 3377 * Return HTTP return code for last request by get_http_request() 3378 * 3379 * @return integer HTTP code 3380 */ 3381function get_http_last_code() 3382{ 3383 return $GLOBALS['response_headers']['code']; 3384} 3385 3386/** 3387 * Return HTTP return code for last request by get_http_request() 3388 * 3389 * @return boolean HTTP status TRUE if response code 2xx or 3xx 3390 */ 3391function get_http_last_status() 3392{ 3393 return $GLOBALS['request_status']; 3394} 3395 3396/** 3397 * Generate HTTP specific context with some defaults for proxy, timeout, user-agent. 3398 * Used in get_http_request(). 3399 * 3400 * @param array $context HTTP specified context, see http://php.net/manual/ru/function.stream-context-create.php 3401 * @return array HTTP context array 3402 */ 3403function generate_http_context_defaults($context = array()) 3404{ 3405 global $config; 3406 3407 if (!is_array($context)) { $context = array(); } // Fix context if not array passed 3408 3409 // Defaults 3410 $context['timeout'] = '15'; 3411 3412 // User agent (required for some type of queries, ie geocoding) 3413 if (!isset($context['header'])) 3414 { 3415 $context['header'] = ''; // Avoid 'undefined index' when concatting below 3416 } 3417 $context['header'] .= 'User-Agent: ' . OBSERVIUM_PRODUCT . '/' . OBSERVIUM_VERSION . "\r\n"; 3418 3419 if (isset($config['http_proxy']) && $config['http_proxy']) 3420 { 3421 $context['proxy'] = 'tcp://' . $config['http_proxy']; 3422 $context['request_fulluri'] = TRUE; 3423 } 3424 3425 // Basic proxy auth 3426 if (isset($config['proxy_user']) && $config['proxy_user'] && isset($config['proxy_password'])) 3427 { 3428 $auth = base64_encode($config['proxy_user'].':'.$config['proxy_password']); 3429 $context['header'] .= 'Proxy-Authorization: Basic ' . $auth . "\r\n"; 3430 } 3431 3432 print_debug_vars($context); 3433 3434 return $context; 3435} 3436 3437 3438/** 3439 * Generate HTTP context based on passed params, tags and definition. 3440 * This context will used in get_http_request_test() (or get_http_request()) 3441 * 3442 * @global array $config 3443 * @param string $def Definition array or alert transport key (see transports definitions) 3444 * @param array $tags (optional) Contact array and other tags 3445 * @param array $params (optional) Array of requested params with key => value entries (used with request method POST) 3446 * @return array HTTP Context which can used in get_http_request_test() or get_http_request() 3447 */ 3448function generate_http_context($def, $tags = array(), $params = array()) 3449{ 3450 global $config; 3451 3452 if (is_string($def)) 3453 { 3454 // Get transport definition for requests 3455 $def = $config['transports'][$def]['notification']; 3456 } 3457 3458 $context = array(); // Init 3459 3460 // Request method POST/GET 3461 if ($def['method']) 3462 { 3463 $context['method'] = strtoupper($def['method']); 3464 } 3465 3466 // Content and headers 3467 $header = "Connection: close\r\n"; 3468 3469 // Add encode $params for POST request inside http headers 3470 if ($context['method'] == 'POST') 3471 { 3472 // Generate request params 3473 foreach ($def['request_params'] as $param => $entry) 3474 { 3475 // Try to find all keys in header like %bot_hash% matched with same key in $endpoint array 3476 if (is_array($entry)) 3477 { 3478 // ie teams and pagerduty 3479 $params[$param] = array_merge((array)$params[$param], array_tag_replace($tags, $entry)); 3480 } 3481 elseif (!isset($params[$param]) || $params[$param] === '') 3482 { 3483 $params[$param] = array_tag_replace($tags, $entry); 3484 } 3485 // Clean empty params 3486 if ($params[$param] === '' || $params[$param] === []) { unset($params[$param]); } 3487 } 3488 3489 if (strtolower($def['request_format']) == 'json') 3490 { 3491 // Encode params as json string 3492 $data = json_encode($params); 3493 $header .= "Content-Type: application/json; charset=utf-8\r\n"; 3494 } else { 3495 // Encode params as url encoded string 3496 $data = http_build_query($params); 3497 // https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data 3498 //$header .= "Content-Type: multipart/form-data\r\n"; 3499 $header .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n"; 3500 } 3501 $header .= "Content-Length: ".strlen($data)."\r\n"; 3502 3503 // Encoded content data 3504 $context['content'] = $data; 3505 } 3506 3507 // Additional headers with contact params 3508 foreach ($def['request_header'] as $entry) 3509 { 3510 // Try to find all keys in header like %bot_hash% matched with same key in $endpoint array 3511 $header .= array_tag_replace($tags, $entry) . "\r\n"; 3512 } 3513 3514 $context['header'] = $header; 3515 3516 return $context; 3517} 3518 3519/** 3520 * Generate URL based on passed params, tags and definition. 3521 * This context will used in get_http_request_test() (or get_http_request()) 3522 * 3523 * @global array $config 3524 * @param string $def Definition array or alert transport key (see transports definitions) 3525 * @param array $tags (optional) Contact array, used only if transport required additional headers (ie hipchat) 3526 * @param array $params (optional) Array of requested params with key => value entries (used with request method GET) 3527 * @return string URL which can used in get_http_request_test() or get_http_request() 3528 */ 3529function generate_http_url($def, $tags = array(), $params = array()) 3530{ 3531 global $config; 3532 3533 if (is_string($def)) 3534 { 3535 // Get definition for transport API 3536 $def = $config['transports'][$def]['notification']; 3537 } 3538 3539 $url = ''; // Init 3540 3541 // Append (if set $def['url_param']) or set hardcoded url for transport 3542 if (isset($def['url'])) 3543 { 3544 // Try to find all keys in URL like %bot_hash% matched with same key in $endpoint array 3545 $url .= array_tag_replace($tags, $def['url']); 3546 } 3547 3548 // Add GET params to url 3549 if ($def['method'] == 'GET') 3550 { 3551 // Generate request params 3552 foreach ($def['request_params'] as $param => $entry) 3553 { 3554 // Try to find all keys in header like %bot_hash% matched with same key in $endpoint array 3555 if (is_array($entry)) 3556 { 3557 // ie teams and pagerduty 3558 $params[$param] = array_merge((array)$params[$param], array_tag_replace($tags, $entry)); 3559 } 3560 elseif (!isset($params[$param]) || $params[$param] === '') 3561 { 3562 $params[$param] = array_tag_replace($tags, $entry); 3563 } 3564 // Clean empty params 3565 if ($params[$param] === '' || $params[$param] === []) { unset($params[$param]); } 3566 } 3567 3568 // Append params to url 3569 if (count($params)) 3570 { 3571 $data = http_build_query($params); 3572 if (str_contains($url, '?')) 3573 { 3574 // Append additional params to url string 3575 $url .= '&' . $data; 3576 } else { 3577 // Add get params as first time 3578 $url .= '?' . $data; 3579 } 3580 } 3581 } 3582 3583 return $url; 3584} 3585 3586/** 3587 * Format date string. 3588 * 3589 * This function convert date/time string to format from 3590 * config option $config['timestamp_format']. 3591 * If date/time not detected in string, function return original string. 3592 * Example conversions to format 'd-m-Y H:i': 3593 * '2012-04-18 14:25:01' -> '18-04-2012 14:25' 3594 * 'Star wars' -> 'Star wars' 3595 * 3596 * @param string $str 3597 * @return string 3598 */ 3599// TESTME needs unit testing 3600function format_timestamp($str) 3601{ 3602 global $config; 3603 3604 if (($timestamp = strtotime($str)) === FALSE) 3605 { 3606 return $str; 3607 } else { 3608 return date($config['timestamp_format'], $timestamp); 3609 } 3610} 3611 3612/** 3613 * Format unixtime. 3614 * 3615 * This function convert unixtime string to format from 3616 * config option $config['timestamp_format']. 3617 * Can take an optional format parameter, which is passed to date(); 3618 * 3619 * @param string $time Unixtime in seconds since the Unix Epoch (also allowed microseconds) 3620 * @param string $format Common date format 3621 * @return string 3622 */ 3623// TESTME needs unit testing 3624function format_unixtime($time, $format = NULL) 3625{ 3626 global $config; 3627 3628 list($sec, $usec) = explode('.', strval($time)); 3629 if (strlen($usec)) { 3630 $date = date_create_from_format('U.u', number_format($time, 6, '.', '')); 3631 } else { 3632 $date = date_create_from_format('U', $sec); 3633 } 3634 3635 // If something wrong with create data object, just return empty string (and yes, we never use zero unixtime) 3636 if (!$date || $time == 0) { return ''; } 3637 3638 // Set correct timezone 3639 $tz = get_timezone(); 3640 //r($tz); 3641 $date_timezone = new DateTimeZone($tz['php']); 3642 //$date_timezone = new DateTimeZone($tz['php_name']); 3643 $date->setTimeZone($date_timezone); 3644 //r($date); 3645 3646 if (strlen($format)) 3647 { 3648 return date_format($date, $format); 3649 } else { 3650 //return date_format($date, $config['timestamp_format'] . ' T'); 3651 return date_format($date, $config['timestamp_format']); 3652 } 3653} 3654 3655/** 3656 * Reformat US-based dates to display based on $config['date_format'] 3657 * 3658 * Supported input formats: 3659 * DD/MM/YYYY 3660 * DD/MM/YY 3661 * 3662 * Handling of YY -> YYYY years is passed on to PHP's strtotime, which 3663 * is currently cut off at 1970/2069. 3664 * 3665 * @param string $date Erroneous date format 3666 * @return string $date 3667 */ 3668function reformat_us_date($date) 3669{ 3670 global $config; 3671 3672 $date = trim($date); 3673 if (preg_match('!^\d{1,2}/\d{1,2}/(\d{2}|\d{4})$!', $date)) 3674 { 3675 // Only date 3676 $format = $config['date_format']; 3677 } 3678 elseif (preg_match('!^\d{1,2}/\d{1,2}/(\d{2}|\d{4})\s+\d{1,2}:\d{1,2}(:\d{1,2})?$!', $date)) 3679 { 3680 // Date + time 3681 $format = $config['timestamp_format']; 3682 } else { 3683 return $date; 3684 } 3685 3686 return date($format, strtotime($date)); 3687} 3688 3689/** 3690 * Convert age string to seconds. 3691 * 3692 * This function convert age string to seconds. 3693 * If age is numeric than it in seconds. 3694 * The supplied age accepts values such as 31d, 240h, 1.5d etc. 3695 * Accepted age scales are: 3696 * y (years), M (months), w (weeks), d (days), h (hours), m (minutes), s (seconds). 3697 * NOTE, for month use CAPITAL 'M' 3698 * With wrong and negative returns 0 3699 * 3700 * '3y 4M 6w 5d 3h 1m 3s' -> 109191663 3701 * '3y4M6w5d3h1m3s' -> 109191663 3702 * '1.5w' -> 907200 3703 * -886732 -> 0 3704 * 'Star wars' -> 0 3705 * 3706 * @param string $age 3707 * @return int 3708 */ 3709// TESTME needs unit testing 3710function age_to_seconds($age) 3711{ 3712 $age = trim($age); 3713 3714 if (is_numeric($age)) 3715 { 3716 $age = (int)$age; 3717 if ($age > 0) 3718 { 3719 return $age; 3720 } else { 3721 return 0; 3722 } 3723 } 3724 3725 $pattern = '/^'; 3726 $pattern .= '(?:(?<years>\d+(?:\.\d)*)\ ?(?:years?|y)[,\ ]*)*'; // y (years) 3727 $pattern .= '(?:(?<months>\d+(?:\.\d)*)\ ?(?:months?|M)[,\ ]*)*'; // M (months) 3728 $pattern .= '(?:(?<weeks>\d+(?:\.\d)*)\ ?(?:weeks?|w)[,\ ]*)*'; // w (weeks) 3729 $pattern .= '(?:(?<days>\d+(?:\.\d)*)\ ?(?:days?|d)[,\ ]*)*'; // d (days) 3730 $pattern .= '(?:(?<hours>\d+(?:\.\d)*)\ ?(?:hours?|h)[,\ ]*)*'; // h (hours) 3731 $pattern .= '(?:(?<minutes>\d+(?:\.\d)*)\ ?(?:minutes?|min|m)[,\ ]*)*'; // m (minutes) 3732 $pattern .= '(?:(?<seconds>\d+(?:\.\d)*)\ ?(?:seconds?|sec|s))*'; // s (seconds) 3733 $pattern .= '$/'; 3734 3735 if (!empty($age) && preg_match($pattern, $age, $matches)) 3736 { 3737 $seconds = $matches['seconds']; 3738 $seconds += $matches['years'] * 31536000; // year = 365 * 24 * 60 * 60 3739 $seconds += $matches['months'] * 2628000; // month = year / 12 3740 $seconds += $matches['weeks'] * 604800; // week = 7 days 3741 $seconds += $matches['days'] * 86400; // day = 24 * 60 * 60 3742 $seconds += $matches['hours'] * 3600; // hour = 60 * 60 3743 $seconds += $matches['minutes'] * 60; // minute = 60 3744 $age = (int)$seconds; 3745 3746 return $age; 3747 } 3748 3749 return 0; 3750} 3751 3752/** 3753 * Convert age string to unixtime. 3754 * 3755 * This function convert age string to unixtime. 3756 * 3757 * Description and notes same as for age_to_seconds() 3758 * 3759 * Additional check if $age more than minimal age in seconds 3760 * 3761 * '3y 4M 6w 5d 3h 1m 3s' -> time() - 109191663 3762 * '3y4M6w5d3h1m3s' -> time() - 109191663 3763 * '1.5w' -> time() - 907200 3764 * -886732 -> 0 3765 * 'Star wars' -> 0 3766 * 3767 * @param string $age 3768 * @return int 3769 */ 3770// TESTME needs unit testing 3771function age_to_unixtime($age, $min_age = 1) 3772{ 3773 $age = age_to_seconds($age); 3774 if ($age >= $min_age) 3775 { 3776 return time() - $age; 3777 } 3778 return 0; 3779} 3780 3781/** 3782 * Convert an variable to base64 encoded string 3783 * 3784 * This function converts any array or other variable to encoded string 3785 * which can be used in urls. 3786 * Can use serialize and json(default) methods. 3787 * 3788 * NOTE. In PHP < 5.4 json converts UTF-8 characters to Unicode escape sequences 3789 * also json rounds float numbers (98172397.1234567890 ==> 98172397.123457) 3790 * 3791 * @param mixed $var 3792 * @param string $method 3793 * @return string 3794 */ 3795function var_encode($var, $method = 'json') 3796{ 3797 switch ($method) 3798 { 3799 case 'serialize': 3800 $string = base64_encode(serialize($var)); 3801 break; 3802 default: 3803 //$tmp = json_encode($var, OBS_JSON_ENCODE); 3804 //echo PHP_EOL . 'precision = ' . ini_get('precision') . "\n"; 3805 //echo 'serialize_precision = ' . ini_get('serialize_precision'); 3806 //echo("\n---\n"); var_dump($var); echo("\n---\n"); var_dump($tmp); 3807 $string = base64_encode(json_encode($var, OBS_JSON_ENCODE)); 3808 break; 3809 } 3810 return $string; 3811} 3812 3813/** 3814 * Decode an previously encoded string by var_encode() to original variable 3815 * 3816 * This function converts base64 encoded string to original variable. 3817 * Can use serialize and json(default) methods. 3818 * If json/serialize not detected returns original var 3819 * 3820 * NOTE. In PHP < 5.4 json converts UTF-8 characters to Unicode escape sequences, 3821 * also json rounds float numbers (98172397.1234567890 ==> 98172397.123457) 3822 * 3823 * @param string $string 3824 * @return mixed 3825 */ 3826function var_decode($string, $method = 'json') 3827{ 3828 if ((strlen($string) % 4) > 0) 3829 { 3830 // BASE64 length must be multiple by 4 3831 return $string; 3832 } 3833 $value = base64_decode($string, TRUE); 3834 if ($value === FALSE) 3835 { 3836 // This is not base64 string, return original var 3837 return $string; 3838 } 3839 3840 switch ($method) 3841 { 3842 case 'serialize': 3843 case 'unserialize': 3844 if ($value === 'b:0;') { return FALSE; }; 3845 $decoded = @unserialize($value); 3846 if ($decoded !== FALSE) 3847 { 3848 // Serialized encoded string detected 3849 return $decoded; 3850 } 3851 break; 3852 default: 3853 if ($string === 'bnVsbA==') { return NULL; }; 3854 if (OBS_JSON_DECODE > 0) 3855 { 3856 $decoded = @json_decode($value, TRUE, 512, OBS_JSON_DECODE); 3857 } else { 3858 // Prevent to broke on old php (5.3), where supported only 3 params 3859 $decoded = @json_decode($value, TRUE, 512); 3860 } 3861 switch (json_last_error()) 3862 { 3863 case JSON_ERROR_STATE_MISMATCH: 3864 case JSON_ERROR_SYNTAX: 3865 // Critical json errors, return original string 3866 break; 3867 case JSON_ERROR_NONE: 3868 default: 3869 if ($decoded !== NULL) 3870 { 3871 // JSON encoded string detected 3872 return $decoded; 3873 } 3874 } 3875 break; 3876 } 3877 3878 // In all other cases return original var 3879 return $string; 3880} 3881 3882/** 3883 * Parse number with units to numeric. 3884 * 3885 * This function converts numbers with units (e.g. 100MB) to their value 3886 * in bytes (e.g. 104857600). 3887 * 3888 * @param string $str 3889 * @param int Use custom rigid unit base (1000 or 1024) 3890 * @return int 3891 */ 3892function unit_string_to_numeric($str, $unit_base = NULL) 3893{ 3894 // If it's already a number, return original string 3895 if (is_numeric($str)) { return (float)$str; } 3896 3897 preg_match('/(\d+\.?\d*)\ ?(\w+)/', $str, $matches); 3898 3899 // Error, return original string 3900 if (!is_numeric($matches[1])) { return $str; } 3901 3902 if (is_numeric($unit_base) && ($unit_base == 1000 || $unit_base == 1024)) 3903 { 3904 // Use rigid unit base, this interprets any units with hard multiplier base 3905 $base = $unit_base; 3906 } 3907 3908 switch ($matches[2]) 3909 { 3910 case '': 3911 case 'B': 3912 case 'b': 3913 case 'bit': 3914 case 'bps': 3915 case 'Bps': 3916 case 'byte': 3917 case 'Byte': 3918 $power = 0; 3919 $base = isset($base) ? $base : 1024; 3920 break; 3921 case 'K': 3922 case 'k': 3923 case 'kB': 3924 case 'kByte': 3925 case 'kbyte': 3926 $power = 1; 3927 $base = isset($base) ? $base : 1024; 3928 break; 3929 case 'kb': 3930 case 'kBps': 3931 case 'kbit': 3932 case 'kbps': 3933 $power = 1; 3934 $base = isset($base) ? $base : 1000; 3935 break; 3936 case 'M': 3937 case 'MB': 3938 case 'MByte': 3939 case 'Mbyte': 3940 $power = 2; 3941 $base = isset($base) ? $base : 1024; 3942 break; 3943 case 'Mb': 3944 case 'MBps': 3945 case 'Mbit': 3946 case 'Mbps': 3947 $power = 2; 3948 $base = isset($base) ? $base : 1000; 3949 break; 3950 case 'G': 3951 case 'GB': 3952 case 'GByte': 3953 case 'Gbyte': 3954 $power = 3; 3955 $base = isset($base) ? $base : 1024; 3956 break; 3957 case 'Gb': 3958 case 'GBps': 3959 case 'Gbit': 3960 case 'Gbps': 3961 $power = 3; 3962 $base = isset($base) ? $base : 1000; 3963 break; 3964 case 'T': 3965 case 'TB': 3966 case 'TByte': 3967 case 'Tbyte': 3968 $power = 4; 3969 $base = isset($base) ? $base : 1024; 3970 break; 3971 case 'Tb': 3972 case 'TBps': 3973 case 'Tbit': 3974 case 'Tbps': 3975 $power = 4; 3976 $base = isset($base) ? $base : 1000; 3977 break; 3978 default: 3979 $power = 0; 3980 $base = isset($base) ? $base : 1024; 3981 break; 3982 } 3983 $multiplier = pow($base, $power); 3984 3985 return (float)($matches[1] * $multiplier); 3986} 3987 3988/** 3989 * Generate Unique ID from string, based on crc32b hash. This ID unique for specific string and not changed over next call. 3990 * 3991 * @param string $string String 3992 * @return int Unique ID 3993 */ 3994function string_to_id($string) 3995{ 3996 return hexdec(hash("crc32b", $string)); 3997} 3998 3999/** 4000 * Convert value of sensor from known unit to defined SI unit (used in poller/discovery) 4001 * 4002 * @param float|string $value Value in non standard unit 4003 * @param string $unit Unit name/symbol 4004 * @param string $type Type of value (optional, if same unit can used for multiple types) 4005 * @return float|string Value converted to standard (SI) unit 4006 */ 4007function value_to_si($value, $unit, $type = NULL) 4008{ 4009 if (!is_numeric($value)) { return $value; } // Just return original value if not numeric 4010 4011 $unit_lower = strtolower($unit); 4012 switch ($unit_lower) 4013 { 4014 case 'f': 4015 case 'fahrenheit': 4016 case 'k': 4017 case 'kelvin': 4018 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Temperature($value, $unit); 4019 $si_value = $value_from->toUnit('C'); 4020 if ($si_value < -273.15) 4021 { 4022 // Physically incorrect value 4023 $si_value = FALSE; 4024 } 4025 4026 $type = 'temperature'; 4027 $from = $value . " $unit"; 4028 $to = $si_value . ' Celsius'; 4029 break; 4030 4031 case 'c': 4032 case 'celsius': 4033 // not convert, just keep correct value 4034 $type = 'temperature'; 4035 break; 4036 4037 case 'w': 4038 case 'watts': 4039 if ($type == 'dbm') 4040 { 4041 // Used when Power convert to dBm 4042 // https://en.wikipedia.org/wiki/DBm 4043 // https://www.everythingrf.com/rf-calculators/watt-to-dbm 4044 if ($value > 0) 4045 { 4046 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Power($value, $unit); 4047 $si_value = $value_from->toUnit('dBm'); 4048 4049 $from = $value . " $unit"; 4050 $to = $si_value . ' dBm'; 4051 } else { 4052 $si_value = FALSE; 4053 $from = $value . ' W'; 4054 $to = 'FALSE'; 4055 } 4056 } else { 4057 // not convert, just keep correct value 4058 $type = 'power'; 4059 } 4060 break; 4061 4062 case 'dbm': 4063 if ($type == 'power') 4064 { 4065 // Used when Power convert to dBm 4066 // https://en.wikipedia.org/wiki/DBm 4067 // https://www.everythingrf.com/rf-calculators/dbm-to-watts 4068 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Power($value, $unit); 4069 $si_value = $value_from->toUnit('W'); 4070 4071 $from = $value . " $unit"; 4072 $to = $si_value . ' W'; 4073 4074 } else { 4075 // not convert, just keep correct value 4076 $type = 'dbm'; 4077 } 4078 break; 4079 4080 case 'psi': 4081 case 'ksi': 4082 case 'Mpsi': 4083 // https://en.wikipedia.org/wiki/Pounds_per_square_inch 4084 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Pressure($value, $unit); 4085 $si_value = $value_from->toUnit('Pa'); 4086 4087 $type = 'pressure'; 4088 $from = $value . " $unit"; 4089 $to = $si_value . ' Pa'; 4090 break; 4091 4092 case 'ft/s': 4093 case 'fps': 4094 case 'ft/min': 4095 case 'fpm': 4096 case 'lfm': // linear feet per minute 4097 case 'mph': // Miles per hour 4098 case 'mps': // Miles per second 4099 case 'm/min': // Meter per minute 4100 case 'km/h': // Kilometer per hour 4101 // Any velocity units: 4102 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Velocity($value, $unit); 4103 $si_value = $value_from->toUnit('m/s'); 4104 4105 $type = 'velocity'; 4106 $from = $value . " $unit"; 4107 $to = $si_value . ' m/s'; 4108 break; 4109 4110 case 'ft3/s': 4111 case 'cfs': 4112 case 'ft3/min': 4113 case 'cfm': 4114 case 'gpd': // US (gallon per day) 4115 case 'gpm': // US (gallon per min) 4116 case 'l/min': 4117 case 'lpm': 4118 case 'cmh': 4119 case 'm3/h': 4120 case 'cmm': 4121 case 'm3/min': 4122 if ($type == 'waterflow') 4123 { 4124 // Waterflow default unit is L/s 4125 $si_unit = 'L/s'; 4126 } 4127 else if ($type == 'airflow') 4128 { 4129 // Use for Airflow imperial unit CFM (Cubic foot per minute) as more common industry standard 4130 $si_unit = 'CFM'; 4131 } else { 4132 // For future 4133 $si_unit = 'm^3/s'; 4134 } 4135 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\VolumeFlow($value, $unit); 4136 $si_value = $value_from->toUnit($si_unit); 4137 4138 $from = $value . " $unit"; 4139 $to = $si_value . " $si_unit"; 4140 break; 4141 4142 default: 4143 // Ability to use any custom function to convert value based on unit name 4144 $function_name = 'value_unit_'.$unit_lower; // ie: value_unit_ekinops_dbm1($value) or value_unit_accuenergy($value) 4145 if (function_exists($function_name)) 4146 { 4147 $si_value = call_user_func_array($function_name, array($value)); 4148 4149 //$type = $unit; 4150 $from = $value . " $unit"; 4151 $to = $si_value; 4152 } 4153 } 4154 4155 if (isset($si_value)) 4156 { 4157 print_debug('Converted '.strtoupper($type).' value: '.$from.' -> '.$to); 4158 return $si_value; 4159 } 4160 4161 return $value; // Fallback original value 4162} 4163 4164/** 4165 * Convert value of sensor from known unit to defined SI unit (used in poller/discovery) 4166 * 4167 * @param float|string $value Value 4168 * @param string $unit_from Unit name/symbol for value 4169 * @param string $class Type of value 4170 * @param string|array $unit_to Unit name/symbol for convert value (by default used sensor class default unit) 4171 * @return array Array with values converted to unit_from 4172 */ 4173function value_to_units($value, $unit_from, $class, $unit_to = []) 4174{ 4175 global $config; 4176 4177 // Convert symbols to supported by lib units 4178 $unit_from = str_replace(['<sup>', '</sup>'], ['^', ''], $unit_from); // I.e. mg/m<sup>3</sup> => mg/m^3 4179 $unit_from = html_entity_decode($unit_from); // I.e. °C => °C 4180 4181 // Non numeric values 4182 if (!is_numeric($value)) 4183 { 4184 return [$unit_from => $value]; 4185 } 4186 4187 switch ($class) 4188 { 4189 case 'temperature': 4190 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Temperature($value, $unit_from); 4191 break; 4192 4193 case 'pressure': 4194 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Pressure($value, $unit_from); 4195 break; 4196 4197 case 'power': 4198 case 'dbm': 4199 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Power($value, $unit_from); 4200 break; 4201 4202 case 'waterflow': 4203 case 'airflow': 4204 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\VolumeFlow($value, $unit_from); 4205 break; 4206 4207 case 'velocity': 4208 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Velocity($value, $unit_from); 4209 break; 4210 4211 case 'lifetime': 4212 case 'uptime': 4213 case 'time': 4214 if ($unit_from == '') { $unit_from = 's'; } 4215 $value_from = new PhpUnitsOfMeasure\PhysicalQuantity\Time($value, $unit_from); 4216 break; 4217 4218 default: 4219 // Unknown, return original value 4220 return [$unit_from => $value]; 4221 } 4222 4223 // Use our default unit (if not passed) 4224 if (empty($unit_to) && isset($config['sensor_types'][$class]['symbol'])) 4225 { 4226 $unit_to = $config['sensor_types'][$class]['symbol']; 4227 } 4228 4229 // Convert to units 4230 $units = []; 4231 foreach ((array)$unit_to as $to) 4232 { 4233 // Convert symbols to supported by lib units 4234 $tou = str_replace(['<sup>', '</sup>'], ['^', ''], $to); // I.e. mg/m<sup>3</sup> => mg/m^3 4235 $tou = html_entity_decode($tou); // I.e. °C => °C 4236 4237 $units[$to] = $value_from->toUnit($tou); 4238 } 4239 4240 return $units; 4241} 4242 4243/** 4244 * Replace all newlines in string to space char (except string begin and end) 4245 * 4246 * @param string $string Input string 4247 * @return string Output string without NL characters 4248 */ 4249function nl2space($string) 4250{ 4251 if (!is_string($string) || $string == '') 4252 { 4253 return $string; 4254 } 4255 4256 $string = trim($string, "\n\r"); 4257 return preg_replace('/ ?(\r\n|\r|\n) ?/', ' ', $string); 4258} 4259 4260/** 4261 * This noob function replace windows/mac newline character to unix newline 4262 * 4263 * @param string $string Input string 4264 * @return string Clean output string 4265 */ 4266function nl2nl($string) 4267{ 4268 if (!is_string($string) || $string == '') 4269 { 4270 return $string; 4271 } 4272 4273 return preg_replace('/\r\n|\r/', PHP_EOL, $string); 4274} 4275 4276/** 4277 * Microtime 4278 * 4279 * This function returns the current Unix timestamp seconds, accurate to the 4280 * nearest microsecond. 4281 * 4282 * @return float 4283 */ 4284function utime() 4285{ 4286 return microtime(TRUE); 4287} 4288 4289 4290/** 4291 * Bitwise checking if flags set 4292 * 4293 * Examples: 4294 * if (is_flag_set(FLAG_A, some_var)) // eg: some_var = 0b01100000000010 4295 * if (is_flag_set(FLAG_A | FLAG_F | FLAG_L, some_var)) // to check if at least one flag is set 4296 * if (is_flag_set(FLAG_A | FLAG_J | FLAG_M | FLAG_D, some_var, TRUE)) // to check if all flags are set 4297 * 4298 * @param int $flag Checked flags 4299 * @param int $param Parameter for checking 4300 * @param bool $all Check all flags 4301 * @return bool 4302 */ 4303function is_flag_set($flag, $param, $all = FALSE) 4304{ 4305 $set = $flag & $param; 4306 4307 if ($set and !$all) { return TRUE; } // at least one of the flags passed is set 4308 else if ($all and ($set == $flag)) { return TRUE; } // to check that all flags are set 4309 4310 return FALSE; 4311} 4312 4313// DOCME needs phpdoc block 4314// TESTME needs unit testing 4315function is_ssl() 4316{ 4317 if (isset($_SERVER['HTTPS'])) 4318 { 4319 if ('on' == strtolower($_SERVER['HTTPS'])) { return TRUE; } 4320 if ('1' == $_SERVER['HTTPS']) { return TRUE; } 4321 } 4322 else if (isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'])) 4323 { 4324 return TRUE; 4325 } 4326 else if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https') 4327 { 4328 return TRUE; 4329 } 4330 4331 return FALSE; 4332} 4333 4334/** 4335 * This function return object with recursive directory iterator. 4336 * 4337 * @param $dir 4338 * 4339 * @return RecursiveIteratorIterator 4340 */ 4341function get_recursive_directory_iterator($dir) 4342{ 4343 return new RecursiveIteratorIterator( 4344 new RecursiveDirectoryIterator($dir, FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS), 4345 RecursiveIteratorIterator::LEAVES_ONLY, 4346 RecursiveIteratorIterator::CATCH_GET_CHILD 4347 ); 4348} 4349 4350// Nice PHP (7.3) compat functions 4351 4352if (!function_exists('array_key_first')) 4353{ 4354 /** 4355 * Gets the first key of an array 4356 * 4357 * @param array $array 4358 * @return mixed 4359 */ 4360 function array_key_first($array) 4361 { 4362 return $array && is_array($array) ? array_keys($array)[0] : NULL; 4363 } 4364} 4365 4366if (!function_exists('array_key_last')) 4367{ 4368 /** 4369 * Gets the last key of an array 4370 * 4371 * @param array $array 4372 * @return mixed 4373 */ 4374 function array_key_last($array) 4375 { 4376 return $array && is_array($array) ? array_keys($array)[count($array) - 1] : NULL; 4377 } 4378} 4379 4380// EOF 4381