|
|
<?php
|
|
|
/*
|
|
|
** Zabbix
|
|
|
** Copyright (C) 2001-2023 Zabbix SIA
|
|
|
**
|
|
|
** This program is free software; you can redistribute it and/or modify
|
|
|
** it under the terms of the GNU General Public License as published by
|
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
|
** (at your option) any later version.
|
|
|
**
|
|
|
** This program is distributed in the hope that it will be useful,
|
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
** GNU General Public License for more details.
|
|
|
**
|
|
|
** You should have received a copy of the GNU General Public License
|
|
|
** along with this program; if not, write to the Free Software
|
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
**/
|
|
|
|
|
|
|
|
|
/**
|
|
|
* Verify that function exists and can be called as a function.
|
|
|
*
|
|
|
* @param array $names
|
|
|
*
|
|
|
* @return bool
|
|
|
*/
|
|
|
function zbx_is_callable(array $names) {
|
|
|
foreach ($names as $name) {
|
|
|
if (!is_callable($name)) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/************ REQUEST ************/
|
|
|
function redirect($url) {
|
|
|
$curl = (new CUrl($url))->removeArgument(CCsrfTokenHelper::CSRF_TOKEN_NAME);
|
|
|
header('Location: '.$curl->getUrl());
|
|
|
exit;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Check the HTTP request method.
|
|
|
*
|
|
|
* @param string $method HTTP request method
|
|
|
*
|
|
|
* @return bool true, if the request method matches
|
|
|
*/
|
|
|
function isRequestMethod($method) {
|
|
|
return (strtolower($method) === strtolower($_SERVER['REQUEST_METHOD']));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Check if request exist.
|
|
|
*
|
|
|
* @param string $name
|
|
|
*
|
|
|
* @return bool
|
|
|
*/
|
|
|
function hasRequest($name) {
|
|
|
return isset($_REQUEST[$name]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Check request, if exist request - return request value, else return default value.
|
|
|
*
|
|
|
* @param string $name
|
|
|
* @param mixed $def
|
|
|
*
|
|
|
* @return mixed
|
|
|
*/
|
|
|
function getRequest($name, $def = null) {
|
|
|
return isset($_REQUEST[$name]) ? $_REQUEST[$name] : $def;
|
|
|
}
|
|
|
|
|
|
function countRequest($str = null) {
|
|
|
if (!empty($str)) {
|
|
|
$count = 0;
|
|
|
|
|
|
foreach ($_REQUEST as $name => $value) {
|
|
|
if (strpos($name, $str) !== false) {
|
|
|
$count++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $count;
|
|
|
}
|
|
|
else {
|
|
|
return count($_REQUEST);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/************* DATE *************/
|
|
|
function getMonthCaption($num) {
|
|
|
switch ($num) {
|
|
|
case 1: return _('January');
|
|
|
case 2: return _('February');
|
|
|
case 3: return _('March');
|
|
|
case 4: return _('April');
|
|
|
case 5: return _('May');
|
|
|
case 6: return _('June');
|
|
|
case 7: return _('July');
|
|
|
case 8: return _('August');
|
|
|
case 9: return _('September');
|
|
|
case 10: return _('October');
|
|
|
case 11: return _('November');
|
|
|
case 12: return _('December');
|
|
|
}
|
|
|
|
|
|
return _s('[Wrong value for month: "%1$s" ]', $num);
|
|
|
}
|
|
|
|
|
|
function getDayOfWeekCaption($num) {
|
|
|
switch ($num) {
|
|
|
case 1: return _('Monday');
|
|
|
case 2: return _('Tuesday');
|
|
|
case 3: return _('Wednesday');
|
|
|
case 4: return _('Thursday');
|
|
|
case 5: return _('Friday');
|
|
|
case 6: return _('Saturday');
|
|
|
case 0:
|
|
|
case 7: return _('Sunday');
|
|
|
}
|
|
|
|
|
|
return _s('[Wrong value for day: "%1$s" ]', $num);
|
|
|
}
|
|
|
|
|
|
// Convert seconds (0..SEC_PER_WEEK) to string representation. For example, 212400 -> 'Tuesday 11:00'
|
|
|
function dowHrMinToStr($value, $display24Hours = false) {
|
|
|
$dow = $value - $value % SEC_PER_DAY;
|
|
|
$hr = $value - $dow;
|
|
|
$hr -= $hr % SEC_PER_HOUR;
|
|
|
$min = $value - $dow - $hr;
|
|
|
$min -= $min % SEC_PER_MIN;
|
|
|
|
|
|
$dow /= SEC_PER_DAY;
|
|
|
$hr /= SEC_PER_HOUR;
|
|
|
$min /= SEC_PER_MIN;
|
|
|
|
|
|
if ($display24Hours && $hr == 0 && $min == 0) {
|
|
|
$dow--;
|
|
|
$hr = 24;
|
|
|
}
|
|
|
|
|
|
return sprintf('%s %02d:%02d', getDayOfWeekCaption($dow), $hr, $min);
|
|
|
}
|
|
|
|
|
|
// Convert Day Of Week, Hours and Minutes to seconds representation. For example, 2 11:00 -> 212400. false if error occurred
|
|
|
function dowHrMinToSec($dow, $hr, $min) {
|
|
|
if (zbx_empty($dow) || zbx_empty($hr) || zbx_empty($min) || !zbx_ctype_digit($dow) || !zbx_ctype_digit($hr) || !zbx_ctype_digit($min)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if ($dow == 7) {
|
|
|
$dow = 0;
|
|
|
}
|
|
|
|
|
|
if ($dow < 0 || $dow > 6) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if ($hr < 0 || $hr > 24) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if ($min < 0 || $min > 59) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return $dow * SEC_PER_DAY + $hr * SEC_PER_HOUR + $min * SEC_PER_MIN;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert time to a string representation. Return 'Never' if timestamp is 0.
|
|
|
*
|
|
|
* @param $format
|
|
|
* @param null $time
|
|
|
* @param string|null $timezone
|
|
|
*
|
|
|
* @throws Exception
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function zbx_date2str($format, $time = null, string $timezone = null) {
|
|
|
static $weekdaynames, $weekdaynameslong, $months, $monthslong;
|
|
|
|
|
|
if ($time === null) {
|
|
|
$time = time();
|
|
|
}
|
|
|
|
|
|
if ($time == 0) {
|
|
|
return _('Never');
|
|
|
}
|
|
|
|
|
|
if ($time > ZBX_MAX_DATE) {
|
|
|
$prefix = '> ';
|
|
|
$datetime = new DateTime('@'.ZBX_MAX_DATE);
|
|
|
}
|
|
|
else {
|
|
|
$prefix = '';
|
|
|
$datetime = new DateTime('@'.(int) $time);
|
|
|
}
|
|
|
|
|
|
$datetime->setTimezone(new DateTimeZone($timezone ?? date_default_timezone_get()));
|
|
|
|
|
|
if ($weekdaynames === null) {
|
|
|
$weekdaynames = [
|
|
|
0 => _('Sun'),
|
|
|
1 => _('Mon'),
|
|
|
2 => _('Tue'),
|
|
|
3 => _('Wed'),
|
|
|
4 => _('Thu'),
|
|
|
5 => _('Fri'),
|
|
|
6 => _('Sat')
|
|
|
];
|
|
|
}
|
|
|
|
|
|
if ($weekdaynameslong === null) {
|
|
|
$weekdaynameslong = [
|
|
|
0 => _('Sunday'),
|
|
|
1 => _('Monday'),
|
|
|
2 => _('Tuesday'),
|
|
|
3 => _('Wednesday'),
|
|
|
4 => _('Thursday'),
|
|
|
5 => _('Friday'),
|
|
|
6 => _('Saturday')
|
|
|
];
|
|
|
}
|
|
|
|
|
|
if ($months === null) {
|
|
|
$months = [
|
|
|
1 => _('Jan'),
|
|
|
2 => _('Feb'),
|
|
|
3 => _('Mar'),
|
|
|
4 => _('Apr'),
|
|
|
5 => _x('May', 'May short'),
|
|
|
6 => _('Jun'),
|
|
|
7 => _('Jul'),
|
|
|
8 => _('Aug'),
|
|
|
9 => _('Sep'),
|
|
|
10 => _('Oct'),
|
|
|
11 => _('Nov'),
|
|
|
12 => _('Dec')
|
|
|
];
|
|
|
}
|
|
|
|
|
|
if ($monthslong === null) {
|
|
|
$monthslong = [
|
|
|
1 => _('January'),
|
|
|
2 => _('February'),
|
|
|
3 => _('March'),
|
|
|
4 => _('April'),
|
|
|
5 => _('May'),
|
|
|
6 => _('June'),
|
|
|
7 => _('July'),
|
|
|
8 => _('August'),
|
|
|
9 => _('September'),
|
|
|
10 => _('October'),
|
|
|
11 => _('November'),
|
|
|
12 => _('December')
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$replacements = [
|
|
|
'l' => $weekdaynameslong[$datetime->format('w')],
|
|
|
'F' => $monthslong[$datetime->format('n')],
|
|
|
'D' => $weekdaynames[$datetime->format('w')],
|
|
|
'M' => $months[$datetime->format('n')]
|
|
|
];
|
|
|
|
|
|
$output = '';
|
|
|
|
|
|
$length = strlen($format);
|
|
|
|
|
|
for ($i = 0; $i < $length; $i++) {
|
|
|
$char = $format[$i];
|
|
|
$char_escaped = $i > 0 && $format[$i - 1] === '\\';
|
|
|
|
|
|
if (!$char_escaped && array_key_exists($char, $replacements)) {
|
|
|
$output .= $replacements[$char];
|
|
|
}
|
|
|
else {
|
|
|
$output .= $datetime->format($char);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $prefix.$output;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Calculates and converts timestamp to string representation.
|
|
|
*
|
|
|
* @param int|string $start_date Start date timestamp.
|
|
|
* @param int|string $end_date End date timestamp.
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function zbx_date2age($start_date, $end_date = 0) {
|
|
|
$end_date = ($end_date != 0) ? $end_date : time();
|
|
|
|
|
|
return convertUnitsS($end_date - $start_date);
|
|
|
}
|
|
|
|
|
|
function zbxDateToTime($strdate) {
|
|
|
if (6 == sscanf($strdate, '%04d%02d%02d%02d%02d%02d', $year, $month, $date, $hours, $minutes, $seconds)) {
|
|
|
return mktime($hours, $minutes, $seconds, $month, $date, $year);
|
|
|
}
|
|
|
elseif (5 == sscanf($strdate, '%04d%02d%02d%02d%02d', $year, $month, $date, $hours, $minutes)) {
|
|
|
return mktime($hours, $minutes, 0, $month, $date, $year);
|
|
|
}
|
|
|
else {
|
|
|
return ($strdate && is_numeric($strdate)) ? $strdate : time();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/*************** CONVERTING ******************/
|
|
|
/**
|
|
|
* Convert the Windows new line (CR+LF) to Linux style line feed (LF).
|
|
|
*
|
|
|
* @param string $string Input string that will be converted.
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function CRLFtoLF($string) {
|
|
|
return str_replace("\r\n", "\n", $string);
|
|
|
}
|
|
|
|
|
|
function rgb2hex($color) {
|
|
|
$HEX = [
|
|
|
dechex($color[0]),
|
|
|
dechex($color[1]),
|
|
|
dechex($color[2])
|
|
|
];
|
|
|
foreach ($HEX as $id => $value) {
|
|
|
if (strlen($value) != 2) {
|
|
|
$HEX[$id] = '0'.$value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $HEX[0].$HEX[1].$HEX[2];
|
|
|
}
|
|
|
|
|
|
function hex2rgb($color) {
|
|
|
if ($color[0] == '#') {
|
|
|
$color = substr($color, 1);
|
|
|
}
|
|
|
|
|
|
if (strlen($color) == 6) {
|
|
|
list($r, $g, $b) = [$color[0].$color[1], $color[2].$color[3], $color[4].$color[5]];
|
|
|
}
|
|
|
elseif (strlen($color) == 3) {
|
|
|
list($r, $g, $b) = [$color[0].$color[0], $color[1].$color[1], $color[2].$color[2]];
|
|
|
}
|
|
|
else {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return [hexdec($r), hexdec($g), hexdec($b)];
|
|
|
}
|
|
|
|
|
|
function getColorVariations($color, $variations_requested = 1) {
|
|
|
if ($variations_requested <= 1) {
|
|
|
return [$color];
|
|
|
}
|
|
|
|
|
|
$change = hex2rgb('#ffffff'); // Color which is increased/decreased in variations.
|
|
|
$max = 50;
|
|
|
|
|
|
$color = hex2rgb($color);
|
|
|
$variations = [];
|
|
|
|
|
|
$range = range(-1 * $max, $max, $max * 2 / $variations_requested);
|
|
|
|
|
|
// Remove redundant values.
|
|
|
while (count($range) > $variations_requested) {
|
|
|
(count($range) % 2) ? array_shift($range) : array_pop($range);
|
|
|
}
|
|
|
|
|
|
// Calculate colors.
|
|
|
foreach ($range as $var) {
|
|
|
$r = $color[0] + ($change[0] / 100 * $var);
|
|
|
$g = $color[1] + ($change[1] / 100 * $var);
|
|
|
$b = $color[2] + ($change[2] / 100 * $var);
|
|
|
|
|
|
$variations[] = '#' . rgb2hex([
|
|
|
$r < 0 ? 0 : ($r > 255 ? 255 : (int) $r),
|
|
|
$g < 0 ? 0 : ($g > 255 ? 255 : (int) $g),
|
|
|
$b < 0 ? 0 : ($b > 255 ? 255 : (int) $b)
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
return $variations;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert suffixed string to decimal bytes ('10K' => 10240).
|
|
|
* Note: this function must not depend on optional PHP libraries, since it is used in Zabbix setup.
|
|
|
*
|
|
|
* @param string $value
|
|
|
*
|
|
|
* @return int
|
|
|
*/
|
|
|
function str2mem($value) {
|
|
|
$value = trim($value);
|
|
|
$suffix = strtoupper(substr($value, -1));
|
|
|
|
|
|
if (ctype_digit($suffix)) {
|
|
|
return (int) $value;
|
|
|
}
|
|
|
|
|
|
$value = (int) substr($value, 0, -1);
|
|
|
|
|
|
if ($suffix === 'G') {
|
|
|
$value *= ZBX_GIBIBYTE;
|
|
|
}
|
|
|
elseif ($suffix === 'M') {
|
|
|
$value *= ZBX_MEBIBYTE;
|
|
|
}
|
|
|
elseif ($suffix === 'K') {
|
|
|
$value *= ZBX_KIBIBYTE;
|
|
|
}
|
|
|
|
|
|
return $value;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert decimal bytes to suffixed string (10240 => '10K').
|
|
|
* Note: this function must not depend on optional PHP libraries, since it is used in Zabbix setup.
|
|
|
*
|
|
|
* @param int $bytes
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function mem2str($bytes) {
|
|
|
if ($bytes > ZBX_GIBIBYTE) {
|
|
|
return round($bytes / ZBX_GIBIBYTE, ZBX_UNITS_ROUNDOFF_SUFFIXED).'G';
|
|
|
}
|
|
|
elseif ($bytes > ZBX_MEBIBYTE) {
|
|
|
return round($bytes / ZBX_MEBIBYTE, ZBX_UNITS_ROUNDOFF_SUFFIXED).'M';
|
|
|
}
|
|
|
elseif ($bytes > ZBX_KIBIBYTE) {
|
|
|
return round($bytes / ZBX_KIBIBYTE, ZBX_UNITS_ROUNDOFF_SUFFIXED).'K';
|
|
|
}
|
|
|
else {
|
|
|
return round($bytes).'B';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function convertUnitsUptime($value) {
|
|
|
$value = round($value);
|
|
|
$value_abs = abs($value);
|
|
|
|
|
|
$result = $value < 0 ? '-' : '';
|
|
|
|
|
|
$days = floor($value_abs / SEC_PER_DAY);
|
|
|
|
|
|
if ($days != 0) {
|
|
|
$result .= _n('%1$d day', '%1$d days', formatFloat($days));
|
|
|
}
|
|
|
|
|
|
// Is original value precise enough for showing detailed data?
|
|
|
if (strlen($value_abs) <= ZBX_FLOAT_DIG) {
|
|
|
if ($days != 0) {
|
|
|
$result .= ', ';
|
|
|
}
|
|
|
|
|
|
$value_abs = $value_abs - $days * SEC_PER_DAY;
|
|
|
|
|
|
$hours = floor($value_abs / SEC_PER_HOUR);
|
|
|
$value_abs -= $hours * SEC_PER_HOUR;
|
|
|
|
|
|
$minutes = floor($value_abs / SEC_PER_MIN);
|
|
|
$seconds = $value_abs - $minutes * SEC_PER_MIN;
|
|
|
|
|
|
$result .= sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert time period to a human-readable format.
|
|
|
* The following units will be used: years, months, days, hours, minutes, seconds and milliseconds.
|
|
|
* Only the 3 most significant units will be displayed: #y #m #d, #m #d #h, #d #h #mm and so on, omitting empty ones.
|
|
|
*
|
|
|
* @param int $value Time period in seconds.
|
|
|
* @param bool $ignore_millisec Without ms (1s 200 ms = 1.2s).
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function convertUnitsS($value, $ignore_millisec = false) {
|
|
|
$value = (float) $value;
|
|
|
$value_abs = abs($value);
|
|
|
|
|
|
$parts = [];
|
|
|
$start = null;
|
|
|
|
|
|
$value_abs_int = floor($value_abs);
|
|
|
|
|
|
if (($v = floor($value_abs_int / SEC_PER_YEAR)) > 0) {
|
|
|
$parts['years'] = $v;
|
|
|
$value_abs_int -= $v * SEC_PER_YEAR;
|
|
|
$start = 0;
|
|
|
}
|
|
|
|
|
|
$v = floor($value_abs_int / SEC_PER_MONTH);
|
|
|
if ($v == 12) {
|
|
|
$parts['years'] = $start === null ? 1 : $parts['years'] + 1;
|
|
|
$start = 0;
|
|
|
}
|
|
|
elseif ($start === null || ceil(log10($parts['years'])) <= ZBX_FLOAT_DIG) {
|
|
|
if ($v > 0) {
|
|
|
$parts['months'] = $v;
|
|
|
$value_abs_int -= $v * SEC_PER_MONTH;
|
|
|
$start = $start === null ? 1 : $start;
|
|
|
}
|
|
|
|
|
|
$level = 2;
|
|
|
foreach ([
|
|
|
'days' => SEC_PER_DAY,
|
|
|
'hours' => SEC_PER_HOUR,
|
|
|
'minutes' => SEC_PER_MIN
|
|
|
] as $part => $sec_per_part) {
|
|
|
$v = floor($value_abs_int / $sec_per_part);
|
|
|
if ($v > 0) {
|
|
|
$parts[$part] = $v;
|
|
|
$value_abs_int -= $v * $sec_per_part;
|
|
|
$start = $start === null ? $level : $start;
|
|
|
}
|
|
|
|
|
|
if ($start !== null && $level - $start >= 2) {
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
$level++;
|
|
|
}
|
|
|
|
|
|
if ($start === null || $start >= 3) {
|
|
|
if ($ignore_millisec) {
|
|
|
$v = $value_abs_int + round(fmod($value_abs, 1), ZBX_UNITS_ROUNDOFF_SUFFIXED);
|
|
|
|
|
|
if ($v > 0) {
|
|
|
$parts['seconds'] = $v;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
$parts['seconds'] = $value_abs_int;
|
|
|
|
|
|
if ($start === null || $start >= 4) {
|
|
|
$v = fmod($value_abs, 1) * 1000;
|
|
|
|
|
|
if ($v > 0) {
|
|
|
$parts['milliseconds'] = formatFloat($v, ['decimals' => ZBX_UNITS_ROUNDOFF_SUFFIXED]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$units = [
|
|
|
'years' => _x('y', 'year short'),
|
|
|
'months' => _x('M', 'month short'),
|
|
|
'days' => _x('d', 'day short'),
|
|
|
'hours' => _x('h', 'hour short'),
|
|
|
'minutes' => _x('m', 'minute short'),
|
|
|
'seconds' => _x('s', 'second short'),
|
|
|
'milliseconds' => _x('ms', 'millisecond short')
|
|
|
];
|
|
|
|
|
|
$result = [];
|
|
|
|
|
|
foreach (array_filter($parts) as $part_unit => $part_value) {
|
|
|
$result[] = formatFloat($part_value, ['decimals' => ZBX_UNITS_ROUNDOFF_SUFFIXED]).$units[$part_unit];
|
|
|
}
|
|
|
|
|
|
return $result ? ($value < 0 ? '-' : '').implode(' ', $result) : '0';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert time period to a human-readable format.
|
|
|
* The following units will be used: weeks, days, hours, minutes and seconds.
|
|
|
* Only the 3 most significant units will be displayed: #w #d #h, #d #h #m or #h #m #s, omitting empty ones.
|
|
|
*
|
|
|
* @param int $value Time period in seconds.
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function convertSecondsToTimeUnits(int $value): string {
|
|
|
$parts = [];
|
|
|
$start = null;
|
|
|
|
|
|
if (($v = floor($value / SEC_PER_WEEK)) > 0) {
|
|
|
$parts['weeks'] = $v;
|
|
|
$value -= $v * SEC_PER_WEEK;
|
|
|
$start = 0;
|
|
|
}
|
|
|
|
|
|
$level = 1;
|
|
|
|
|
|
foreach ([
|
|
|
'days' => SEC_PER_DAY,
|
|
|
'hours' => SEC_PER_HOUR,
|
|
|
'minutes' => SEC_PER_MIN
|
|
|
] as $part => $sec_per_part) {
|
|
|
$v = floor($value / $sec_per_part);
|
|
|
|
|
|
if ($v > 0) {
|
|
|
$parts[$part] = $v;
|
|
|
$value -= $v * $sec_per_part;
|
|
|
$start = $start === null ? $level : $start;
|
|
|
}
|
|
|
|
|
|
if ($start !== null && $level - $start >= 2) {
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
$level++;
|
|
|
}
|
|
|
|
|
|
if ($start === null || $start >= 2) {
|
|
|
$v = $value + round(fmod($value, 1), ZBX_UNITS_ROUNDOFF_SUFFIXED);
|
|
|
|
|
|
if ($v > 0) {
|
|
|
$parts['seconds'] = $v;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$units = [
|
|
|
'weeks' => _x('w', 'week short'),
|
|
|
'days' => _x('d', 'day short'),
|
|
|
'hours' => _x('h', 'hour short'),
|
|
|
'minutes' => _x('m', 'minute short'),
|
|
|
'seconds' => _x('s', 'second short')
|
|
|
];
|
|
|
|
|
|
$result = [];
|
|
|
|
|
|
foreach ($parts as $part_unit => $part_value) {
|
|
|
$result[] = $part_value.$units[$part_unit];
|
|
|
}
|
|
|
|
|
|
return $result ? implode(' ', $result) : '0';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Converts a raw value to a user-friendly representation based on unit and other parameters.
|
|
|
* Example: 6442450944 B => 6 GB.
|
|
|
*
|
|
|
* @see convertUnitsRaw
|
|
|
*
|
|
|
* @param array $options
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function convertUnits(array $options): string {
|
|
|
[
|
|
|
'value' => $value,
|
|
|
'units' => $units
|
|
|
] = convertUnitsRaw($options);
|
|
|
|
|
|
$result = $value;
|
|
|
|
|
|
if ($units !== '') {
|
|
|
$result .= ' '.$units;
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Converts a raw value to a user-friendly representation based on unit and other parameters.
|
|
|
* Example: 6442450944 B => 6 GB.
|
|
|
*
|
|
|
* @param array $options
|
|
|
*
|
|
|
* $options = [
|
|
|
* 'value' => (string) Value to convert.
|
|
|
* 'units' => (string) Units to base the conversion on. Default: ''.
|
|
|
* 'convert' => (int) Default: ITEM_CONVERT_WITH_UNITS. Set to ITEM_CONVERT_NO_UNITS to
|
|
|
* force-convert a value with empty units.
|
|
|
* 'power' => (int) Convert to the specified power of "unit_base" (0 => '', 1 => K, 2 => M, ..).
|
|
|
* By default, the power will be calculated automatically.
|
|
|
* 'unit_base' => (string) 1000 or 1024. By default, will only use 1024 for "B" and "Bps" units.
|
|
|
* 'ignore_milliseconds' => (bool) Ignore milliseconds in time conversion ("s" units).
|
|
|
* 'precision' => (int) Max number of significant digits to take into account.
|
|
|
* Default: ZBX_FLOAT_DIG.
|
|
|
* 'decimals' => (int|null) Max number of first non-zero decimals to display. If null is specified,
|
|
|
* ZBX_UNITS_ROUNDOFF_SUFFIXED or ZBX_UNITS_ROUNDOFF_UNSUFFIXED will be used,
|
|
|
* depending on whether the units have been prefixed.
|
|
|
* 'decimals_exact' => (bool) Display exactly this number of decimals instead of first non-zeros.
|
|
|
* Default: false.
|
|
|
* 'small_scientific' => (bool) Allow scientific notation for small numbers. Default: true.
|
|
|
* 'zero_as_zero' => (bool) Return zero as '0', regardless of other options. Default: true.
|
|
|
* ]
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function convertUnitsRaw(array $options): array {
|
|
|
static $power_table = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
|
|
|
|
|
$options += [
|
|
|
'value' => '',
|
|
|
'units' => '',
|
|
|
'convert' => ITEM_CONVERT_WITH_UNITS,
|
|
|
'power' => null,
|
|
|
'unit_base' => null,
|
|
|
'ignore_milliseconds' => false,
|
|
|
'precision' => ZBX_FLOAT_DIG,
|
|
|
'decimals' => null,
|
|
|
'decimals_exact' => false,
|
|
|
'small_scientific' => true,
|
|
|
'zero_as_zero' => true
|
|
|
];
|
|
|
|
|
|
$value = $options['value'] !== null ? $options['value'] : '';
|
|
|
|
|
|
if (!is_numeric($value)) {
|
|
|
return [
|
|
|
'value' => $value,
|
|
|
'units' => '',
|
|
|
'is_numeric' => false
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$units = $options['units'] !== null ? $options['units'] : '';
|
|
|
|
|
|
if ($units === 'unixtime') {
|
|
|
return [
|
|
|
'value' => zbx_date2str(DATE_TIME_FORMAT_SECONDS, $value),
|
|
|
'units' => '',
|
|
|
'is_numeric' => false
|
|
|
];
|
|
|
}
|
|
|
|
|
|
if ($units === 'uptime') {
|
|
|
return [
|
|
|
'value' => convertUnitsUptime($value),
|
|
|
'units' => '',
|
|
|
'is_numeric' => false
|
|
|
];
|
|
|
}
|
|
|
|
|
|
if ($units === 's') {
|
|
|
return [
|
|
|
'value' => convertUnitsS($value, $options['ignore_milliseconds']),
|
|
|
'units' => '',
|
|
|
'is_numeric' => false
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$blacklist = ['%', 'ms', 'rpm', 'RPM'];
|
|
|
|
|
|
if ($units !== '' && $units[0] === '!') {
|
|
|
$units = substr($units, 1);
|
|
|
$blacklist[] = $units;
|
|
|
}
|
|
|
|
|
|
$value = (float) $value;
|
|
|
$value_abs = abs($value);
|
|
|
|
|
|
$do_convert = $units !== '' || $options['convert'] == ITEM_CONVERT_NO_UNITS;
|
|
|
|
|
|
if (in_array($units, $blacklist) || !$do_convert || $value_abs < 1) {
|
|
|
return [
|
|
|
'value' => formatFloat($value, [
|
|
|
'precision' => $options['precision'],
|
|
|
'decimals' => $options['decimals'] !== null ? $options['decimals'] : ZBX_UNITS_ROUNDOFF_UNSUFFIXED,
|
|
|
'decimals_exact' => $options['decimals_exact'],
|
|
|
'small_scientific' => $options['small_scientific'],
|
|
|
'zero_as_zero' => $options['zero_as_zero']
|
|
|
]),
|
|
|
'units' => $units,
|
|
|
'is_numeric' => true
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$unit_base = $options['unit_base'];
|
|
|
|
|
|
if ($unit_base != 1000 && $unit_base != ZBX_KIBIBYTE) {
|
|
|
$unit_base = isBinaryUnits($units) ? ZBX_KIBIBYTE : 1000;
|
|
|
}
|
|
|
|
|
|
if ($options['power'] === null) {
|
|
|
$result = null;
|
|
|
$unit_prefix = null;
|
|
|
|
|
|
foreach ($power_table as $power => $prefix) {
|
|
|
$result = formatFloat($value / pow($unit_base, $power), [
|
|
|
'precision' => $options['precision'],
|
|
|
'decimals' => $options['decimals'] !== null
|
|
|
? $options['decimals']
|
|
|
: ($prefix === '' ? ZBX_UNITS_ROUNDOFF_UNSUFFIXED : ZBX_UNITS_ROUNDOFF_SUFFIXED),
|
|
|
'decimals_exact' => $options['decimals_exact'],
|
|
|
'small_scientific' => $options['small_scientific'],
|
|
|
'zero_as_zero' => $options['zero_as_zero']
|
|
|
]);
|
|
|
|
|
|
$unit_prefix = $prefix;
|
|
|
|
|
|
if (abs($result) < $unit_base) {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
$unit_power = array_key_exists($options['power'], $power_table) ? $options['power'] : count($power_table) - 1;
|
|
|
$unit_prefix = $power_table[$unit_power];
|
|
|
|
|
|
$result = formatFloat($value / pow($unit_base, $unit_power), [
|
|
|
'precision' => $options['precision'],
|
|
|
'decimals' => $options['decimals'] !== null
|
|
|
? $options['decimals']
|
|
|
: ($unit_prefix === '' ? ZBX_UNITS_ROUNDOFF_UNSUFFIXED : ZBX_UNITS_ROUNDOFF_SUFFIXED),
|
|
|
'decimals_exact' => $options['decimals_exact'],
|
|
|
'small_scientific' => $options['small_scientific'],
|
|
|
'zero_as_zero' => $options['zero_as_zero']
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
$result_units = ($result == 0 ? '' : $unit_prefix).$units;
|
|
|
|
|
|
return [
|
|
|
'value' => $result,
|
|
|
'units' => $result_units,
|
|
|
'is_numeric' => true
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Validate and convert time to seconds.
|
|
|
* Examples: '100' => '100'; '10m' => '600'; '-10m' => '-600'; '3d' => '259200'.
|
|
|
*
|
|
|
* @param string $time Decimal integer with optional time suffix.
|
|
|
* @param bool $with_year Additionally parse year suffixes.
|
|
|
*
|
|
|
* @return int|null Decimal integer seconds or null on error.
|
|
|
*/
|
|
|
function timeUnitToSeconds($time, $with_year = false) {
|
|
|
$suffixes = $with_year ? ZBX_TIME_SUFFIXES_WITH_YEAR : ZBX_TIME_SUFFIXES;
|
|
|
|
|
|
if (!preg_match('/^'.ZBX_PREG_INT.'(?<suffix>['.$suffixes.'])?$/', $time, $matches)) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
$suffix = array_key_exists('suffix', $matches) ? $matches['suffix'] : 's';
|
|
|
|
|
|
return $matches['int'] * ZBX_TIME_SUFFIX_MULTIPLIERS[$suffix];
|
|
|
}
|
|
|
|
|
|
/************* ZBX MISC *************/
|
|
|
|
|
|
/**
|
|
|
* Check if every character in given string value is a decimal digit.
|
|
|
*
|
|
|
* @param string | int $x Value to check.
|
|
|
*
|
|
|
* @return boolean
|
|
|
*/
|
|
|
function zbx_ctype_digit($x) {
|
|
|
return ctype_digit(strval($x));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Returns true if the value is an empty string, empty array or null.
|
|
|
*
|
|
|
* @deprecated use strict comparison instead
|
|
|
*
|
|
|
* @param $value
|
|
|
*
|
|
|
* @return bool
|
|
|
*/
|
|
|
function zbx_empty($value) {
|
|
|
if ($value === null) {
|
|
|
return true;
|
|
|
}
|
|
|
if (is_array($value) && empty($value)) {
|
|
|
return true;
|
|
|
}
|
|
|
if (is_string($value) && $value === '') {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
function zbx_is_int($var) {
|
|
|
if (is_array($var)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (is_int($var)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
if (is_string($var)) {
|
|
|
if (function_exists('ctype_digit') && ctype_digit($var) || strcmp(intval($var), $var) == 0) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
if ($var > 0 && zbx_ctype_digit($var)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return preg_match("/^\-?\d{1,20}+$/", $var);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Look for two arrays field value and create 3 array lists, one with arrays where field value exists only in first array
|
|
|
* second with arrays where field values are only in second array and both where field values are in both arrays.
|
|
|
*
|
|
|
* @param array $primary
|
|
|
* @param array $secondary
|
|
|
* @param string $field field that is searched in arrays
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function zbx_array_diff(array $primary, array $secondary, $field) {
|
|
|
$fields1 = zbx_objectValues($primary, $field);
|
|
|
$fields2 = zbx_objectValues($secondary, $field);
|
|
|
|
|
|
$first = array_diff($fields1, $fields2);
|
|
|
$first = zbx_toHash($first);
|
|
|
|
|
|
$second = array_diff($fields2, $fields1);
|
|
|
$second = zbx_toHash($second);
|
|
|
|
|
|
$result = [
|
|
|
'first' => [],
|
|
|
'second' => [],
|
|
|
'both' => []
|
|
|
];
|
|
|
|
|
|
foreach ($primary as $array) {
|
|
|
if (!isset($array[$field])) {
|
|
|
$result['first'][] = $array;
|
|
|
}
|
|
|
elseif (isset($first[$array[$field]])) {
|
|
|
$result['first'][] = $array;
|
|
|
}
|
|
|
else {
|
|
|
$result['both'][$array[$field]] = $array;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
foreach ($secondary as $array) {
|
|
|
if (!isset($array[$field])) {
|
|
|
$result['second'][] = $array;
|
|
|
}
|
|
|
elseif (isset($second[$array[$field]])) {
|
|
|
$result['second'][] = $array;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
function zbx_array_push(&$array, $add) {
|
|
|
foreach ($array as $key => $value) {
|
|
|
foreach ($add as $newKey => $newValue) {
|
|
|
$array[$key][$newKey] = $newValue;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Find if array has any duplicate values and return an array with info about them.
|
|
|
* In case of no duplicates, empty array is returned.
|
|
|
* Example of usage:
|
|
|
* $result = zbx_arrayFindDuplicates(
|
|
|
* array('a', 'b', 'c', 'c', 'd', 'd', 'd', 'e')
|
|
|
* );
|
|
|
* array(
|
|
|
* 'd' => 3,
|
|
|
* 'c' => 2,
|
|
|
* )
|
|
|
*
|
|
|
* @param array $array
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function zbx_arrayFindDuplicates(array $array) {
|
|
|
$countValues = array_count_values($array); // counting occurrences of every value in array
|
|
|
foreach ($countValues as $value => $count) {
|
|
|
if ($count <= 1) {
|
|
|
unset($countValues[$value]);
|
|
|
}
|
|
|
}
|
|
|
arsort($countValues); // sorting, so that the most duplicates would be at the top
|
|
|
|
|
|
return $countValues;
|
|
|
}
|
|
|
|
|
|
/************* STRING *************/
|
|
|
function zbx_nl2br($str) {
|
|
|
$str_res = [];
|
|
|
foreach (explode("\n", $str) as $str_line) {
|
|
|
array_push($str_res, $str_line, BR());
|
|
|
}
|
|
|
array_pop($str_res);
|
|
|
|
|
|
return $str_res;
|
|
|
}
|
|
|
|
|
|
function zbx_formatDomId($value) {
|
|
|
return str_replace(['[', ']'], ['_', ''], $value);
|
|
|
}
|
|
|
|
|
|
/************* SORT *************/
|
|
|
function natksort(&$array) {
|
|
|
$keys = array_keys($array);
|
|
|
natcasesort($keys);
|
|
|
|
|
|
$new_array = [];
|
|
|
|
|
|
foreach ($keys as $k) {
|
|
|
$new_array[$k] = $array[$k];
|
|
|
}
|
|
|
|
|
|
$array = $new_array;
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// recursively sort an array by key
|
|
|
function zbx_rksort(&$array, $flags = null) {
|
|
|
if (is_array($array)) {
|
|
|
foreach ($array as $id => $data) {
|
|
|
zbx_rksort($array[$id]);
|
|
|
}
|
|
|
ksort($array, $flags);
|
|
|
}
|
|
|
|
|
|
return $array;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Sorts the data using a natural sort algorithm.
|
|
|
*
|
|
|
* Not suitable for sorting macros, use order_macros() instead.
|
|
|
*
|
|
|
* @param $data
|
|
|
* @param null $sortfield
|
|
|
* @param string $sortorder
|
|
|
*
|
|
|
* @return bool
|
|
|
*
|
|
|
* @see order_macros()
|
|
|
*/
|
|
|
function order_result(&$data, $sortfield = null, $sortorder = ZBX_SORT_UP) {
|
|
|
if (empty($data)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (is_null($sortfield)) {
|
|
|
natcasesort($data);
|
|
|
if ($sortorder != ZBX_SORT_UP) {
|
|
|
$data = array_reverse($data, true);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
$sort = [];
|
|
|
foreach ($data as $key => $arr) {
|
|
|
if (!isset($arr[$sortfield])) {
|
|
|
return false;
|
|
|
}
|
|
|
$sort[$key] = $arr[$sortfield];
|
|
|
}
|
|
|
natcasesort($sort);
|
|
|
|
|
|
if ($sortorder != ZBX_SORT_UP) {
|
|
|
$sort = array_reverse($sort, true);
|
|
|
}
|
|
|
|
|
|
$tmp = $data;
|
|
|
$data = [];
|
|
|
foreach ($sort as $key => $val) {
|
|
|
$data[$key] = $tmp[$key];
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Sorts the macros in the given order. Supports user and LLD macros.
|
|
|
*
|
|
|
* order_result() is not suitable for sorting macros, because it treats the "}" as a symbol with a lower priority
|
|
|
* then any alphanumeric character, and the result will be invalid.
|
|
|
*
|
|
|
* E.g: order_result() will sort array('{$DD}', '{$D}', '{$D1}') as
|
|
|
* array('{$D1}', '{$DD}', '{$D}') while the correct result is array('{$D}', '{$D1}', '{$DD}').
|
|
|
*
|
|
|
* @param array $macros
|
|
|
* @param string $sortfield
|
|
|
* @param string $order
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function order_macros(array $macros, $sortfield, $order = ZBX_SORT_UP) {
|
|
|
$temp = [];
|
|
|
foreach ($macros as $key => $macro) {
|
|
|
$temp[$key] = substr($macro[$sortfield], 2, strlen($macro[$sortfield]) - 3);
|
|
|
}
|
|
|
order_result($temp, null, $order);
|
|
|
|
|
|
$rs = [];
|
|
|
foreach ($temp as $key => $macroLabel) {
|
|
|
$rs[$key] = $macros[$key];
|
|
|
}
|
|
|
|
|
|
return $rs;
|
|
|
}
|
|
|
|
|
|
// preserve keys
|
|
|
function zbx_array_merge() {
|
|
|
$args = func_get_args();
|
|
|
$result = [];
|
|
|
foreach ($args as &$array) {
|
|
|
if (!is_array($array)) {
|
|
|
return false;
|
|
|
}
|
|
|
foreach ($array as $key => $value) {
|
|
|
$result[$key] = $value;
|
|
|
}
|
|
|
}
|
|
|
unset($array);
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
function uint_in_array($needle, $haystack) {
|
|
|
foreach ($haystack as $value) {
|
|
|
if (bccomp($needle, $value) == 0) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
function str_in_array($needle, $haystack, $strict = false) {
|
|
|
if (is_array($needle)) {
|
|
|
return in_array($needle, $haystack, $strict);
|
|
|
}
|
|
|
elseif ($strict) {
|
|
|
foreach ($haystack as $value) {
|
|
|
if ($needle === $value) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
foreach ($haystack as $value) {
|
|
|
if (strcmp($needle, $value) == 0) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
function zbx_value2array(&$values) {
|
|
|
if (!is_array($values) && !is_null($values)) {
|
|
|
$tmp = [];
|
|
|
if (is_object($values)) {
|
|
|
$tmp[] = $values;
|
|
|
}
|
|
|
else {
|
|
|
$tmp[$values] = $values;
|
|
|
}
|
|
|
$values = $tmp;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// creates chain of relation parent -> child, for all chain levels
|
|
|
function createParentToChildRelation(&$chain, $link, $parentField, $childField) {
|
|
|
if (!isset($chain[$link[$parentField]])) {
|
|
|
$chain[$link[$parentField]] = [];
|
|
|
}
|
|
|
|
|
|
$chain[$link[$parentField]][$link[$childField]] = $link[$childField];
|
|
|
if (isset($chain[$link[$childField]])) {
|
|
|
$chain[$link[$parentField]] = zbx_array_merge($chain[$link[$parentField]], $chain[$link[$childField]]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// object or array of objects to hash
|
|
|
function zbx_toHash($value, $field = null) {
|
|
|
if (is_null($value)) {
|
|
|
return $value;
|
|
|
}
|
|
|
$result = [];
|
|
|
|
|
|
if (!is_array($value)) {
|
|
|
$result = [$value => $value];
|
|
|
}
|
|
|
elseif (isset($value[$field])) {
|
|
|
$result[$value[$field]] = $value;
|
|
|
}
|
|
|
else {
|
|
|
foreach ($value as $val) {
|
|
|
if (!is_array($val)) {
|
|
|
$result[$val] = $val;
|
|
|
}
|
|
|
elseif (isset($val[$field])) {
|
|
|
$result[$val[$field]] = $val;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Transforms a single or an array of values to an array of objects, where the values are stored under the $field
|
|
|
* key.
|
|
|
*
|
|
|
* E.g:
|
|
|
* zbx_toObject(array(1, 2), 'hostid') // returns array(array('hostid' => 1), array('hostid' => 2))
|
|
|
* zbx_toObject(3, 'hostid') // returns array(array('hostid' => 3))
|
|
|
* zbx_toObject(array('a' => 1), 'hostid', true) // returns array('a' => array('hostid' => 1))
|
|
|
*
|
|
|
* @param $value
|
|
|
* @param $field
|
|
|
* @param $preserve_keys
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function zbx_toObject($value, $field, $preserve_keys = false) {
|
|
|
if (is_null($value)) {
|
|
|
return $value;
|
|
|
}
|
|
|
$result = [];
|
|
|
|
|
|
// Value or Array to Object or Array of objects
|
|
|
if (!is_array($value)) {
|
|
|
$result = [[$field => $value]];
|
|
|
}
|
|
|
elseif (!isset($value[$field])) {
|
|
|
foreach ($value as $key => $val) {
|
|
|
if (!is_array($val)) {
|
|
|
$result[$key] = [$field => $val];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!$preserve_keys) {
|
|
|
$result = array_values($result);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Converts the given value to a numeric array:
|
|
|
* - a scalar value will be converted to an array and added as the only element;
|
|
|
* - an array with first element key containing only numeric characters will be converted to plain zero-based numeric array.
|
|
|
* This is used for resetting nonsequential numeric arrays;
|
|
|
* - an associative array will be returned in an array as the only element, except if first element key contains only numeric characters.
|
|
|
*
|
|
|
* @param mixed $value
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function zbx_toArray($value) {
|
|
|
if ($value === null) {
|
|
|
return $value;
|
|
|
}
|
|
|
|
|
|
if (is_array($value)) {
|
|
|
// reset() is needed to move internal array pointer to the beginning of the array
|
|
|
reset($value);
|
|
|
|
|
|
if (zbx_ctype_digit(key($value))) {
|
|
|
$result = array_values($value);
|
|
|
}
|
|
|
elseif (!empty($value)) {
|
|
|
$result = [$value];
|
|
|
}
|
|
|
else {
|
|
|
$result = [];
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
$result = [$value];
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Converts value OR object OR array of objects TO an array.
|
|
|
*
|
|
|
* @deprecated Use array_column() instead.
|
|
|
*
|
|
|
* @param $value
|
|
|
* @param $field
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function zbx_objectValues($value, $field) {
|
|
|
if (is_null($value)) {
|
|
|
return $value;
|
|
|
}
|
|
|
|
|
|
if (!is_array($value)) {
|
|
|
$result = [$value];
|
|
|
}
|
|
|
elseif (isset($value[$field])) {
|
|
|
$result = [$value[$field]];
|
|
|
}
|
|
|
else {
|
|
|
$result = [];
|
|
|
|
|
|
foreach ($value as $val) {
|
|
|
if (!is_array($val)) {
|
|
|
$result[] = $val;
|
|
|
}
|
|
|
elseif (isset($val[$field])) {
|
|
|
$result[] = $val[$field];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
function zbx_cleanHashes(&$value) {
|
|
|
if (is_array($value)) {
|
|
|
// reset() is needed to move internal array pointer to the beginning of the array
|
|
|
reset($value);
|
|
|
if (zbx_ctype_digit(key($value))) {
|
|
|
$value = array_values($value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $value;
|
|
|
}
|
|
|
|
|
|
function zbx_toCSV($values) {
|
|
|
$csv = '';
|
|
|
$glue = '","';
|
|
|
foreach ($values as $row) {
|
|
|
if (!is_array($row)) {
|
|
|
$row = [$row];
|
|
|
}
|
|
|
foreach ($row as $num => $value) {
|
|
|
$row[$num] = str_replace('"', '""', $value);
|
|
|
}
|
|
|
$csv .= '"'.implode($glue, $row).'"'."\n";
|
|
|
}
|
|
|
|
|
|
return $csv;
|
|
|
}
|
|
|
|
|
|
function zbx_str2links($text) {
|
|
|
$result = [];
|
|
|
|
|
|
foreach (explode("\n", $text) as $line) {
|
|
|
$line = rtrim($line, "\r ");
|
|
|
|
|
|
preg_match_all('#https?://[^\n\t\r ]+#u', $line, $matches);
|
|
|
|
|
|
$start = 0;
|
|
|
|
|
|
foreach ($matches[0] as $match) {
|
|
|
if (($pos = mb_strpos($line, $match, $start)) !== false) {
|
|
|
if ($pos != $start) {
|
|
|
$result[] = mb_substr($line, $start, $pos - $start);
|
|
|
}
|
|
|
$result[] = new CLink($match, $match);
|
|
|
$start = $pos + mb_strlen($match);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (mb_strlen($line) != $start) {
|
|
|
$result[] = mb_substr($line, $start);
|
|
|
}
|
|
|
|
|
|
$result[] = BR();
|
|
|
}
|
|
|
|
|
|
array_pop($result);
|
|
|
|
|
|
return $result;
|
|
|
}
|
|
|
|
|
|
function zbx_subarray_push(&$mainArray, $sIndex, $element = null, $key = null) {
|
|
|
if (!isset($mainArray[$sIndex])) {
|
|
|
$mainArray[$sIndex] = [];
|
|
|
}
|
|
|
if ($key) {
|
|
|
$mainArray[$sIndex][$key] = is_null($element) ? $sIndex : $element;
|
|
|
}
|
|
|
else {
|
|
|
$mainArray[$sIndex][] = is_null($element) ? $sIndex : $element;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/*************** PAGE SORTING ******************/
|
|
|
|
|
|
/**
|
|
|
* Returns header with sorting options.
|
|
|
*
|
|
|
* @param string obj Header item.
|
|
|
* @param string $tabfield Table field.
|
|
|
* @param string $sortField Sorting field.
|
|
|
* @param string $sortOrder Sorting order.
|
|
|
* @param string $link Sorting link.
|
|
|
*
|
|
|
* @return CColHeader
|
|
|
*/
|
|
|
function make_sorting_header($obj, $tabfield, $sortField, $sortOrder, $link = null) {
|
|
|
$sortorder = ($sortField == $tabfield && $sortOrder == ZBX_SORT_UP) ? ZBX_SORT_DOWN : ZBX_SORT_UP;
|
|
|
|
|
|
$link = CUrlFactory::getContextUrl($link);
|
|
|
|
|
|
$link->setArgument('sort', $tabfield);
|
|
|
$link->setArgument('sortorder', $sortorder);
|
|
|
|
|
|
zbx_value2array($obj);
|
|
|
|
|
|
$arrow = null;
|
|
|
if ($tabfield == $sortField) {
|
|
|
if ($sortorder == ZBX_SORT_UP) {
|
|
|
$arrow = (new CSpan())->addClass(ZBX_STYLE_ARROW_DOWN);
|
|
|
}
|
|
|
else {
|
|
|
$arrow = (new CSpan())->addClass(ZBX_STYLE_ARROW_UP);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return new CColHeader(new CLink([$obj, $arrow], $link->getUrl()));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get decimal point and thousands separator for number formatting according to the current locale.
|
|
|
*
|
|
|
* @return array 'decimal_point' and 'thousands_sep' values.
|
|
|
*/
|
|
|
function getNumericFormatting(): array {
|
|
|
static $numeric_formatting = null;
|
|
|
|
|
|
if ($numeric_formatting === null) {
|
|
|
$numeric_formatting = array_intersect_key(localeconv(), array_flip(['decimal_point', 'thousands_sep']));
|
|
|
}
|
|
|
|
|
|
return $numeric_formatting;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Format floating-point number in the best possible way for displaying.
|
|
|
*
|
|
|
* @param float $number Valid number in decimal or scientific notation.
|
|
|
* @param array $options Formatting options.
|
|
|
*
|
|
|
* $options = [
|
|
|
* 'precision' => (int) Max number of significant digits to take into account. Default: ZBX_FLOAT_DIG.
|
|
|
* 'decimals' => (int) Max number of first non-zero decimals to display. Default: 0.
|
|
|
* 'decimals_exact' => (bool) Display exactly this number of decimals instead of first non-zeros. Default: false.
|
|
|
* 'small_scientific' => (bool) Allow scientific notation for small numbers. Default: true.
|
|
|
* 'zero_as_zero' => (bool) Return zero as '0', regardless of other options. Default: true.
|
|
|
* ]
|
|
|
*
|
|
|
* Note: $decimals must be less than $precision.
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function formatFloat(float $number, array $options = []): string {
|
|
|
if ($number == INF) {
|
|
|
return _('Infinity');
|
|
|
}
|
|
|
|
|
|
if ($number == -INF) {
|
|
|
return '-'._('Infinity');
|
|
|
}
|
|
|
|
|
|
$defaults = [
|
|
|
'precision' => ZBX_FLOAT_DIG,
|
|
|
'decimals' => 0,
|
|
|
'decimals_exact' => false,
|
|
|
'small_scientific' => true,
|
|
|
'zero_as_zero' => true
|
|
|
];
|
|
|
|
|
|
[
|
|
|
'precision' => $precision,
|
|
|
'decimals' => $decimals,
|
|
|
'decimals_exact' => $decimals_exact,
|
|
|
'small_scientific' => $small_scientific,
|
|
|
'zero_as_zero' => $zero_as_zero
|
|
|
] = $options + $defaults;
|
|
|
|
|
|
if ($zero_as_zero && $number == 0) {
|
|
|
return '0';
|
|
|
}
|
|
|
|
|
|
$number_original = $number;
|
|
|
|
|
|
$exponent = (int) explode('E', sprintf('%.'.($precision - 1).'E', $number))[1];
|
|
|
|
|
|
if ($exponent < 0) {
|
|
|
for ($i = 1; $i >= 0; $i--) {
|
|
|
$round_precision = $decimals - $exponent - $i;
|
|
|
|
|
|
// PHP rounding bug when precision is set more than 294.
|
|
|
if ($round_precision > 294) {
|
|
|
$decimal_shift = pow(10, $round_precision - 294);
|
|
|
$test = round($number * $decimal_shift, 294) / $decimal_shift;
|
|
|
}
|
|
|
else {
|
|
|
$test = round($number, $round_precision);
|
|
|
}
|
|
|
|
|
|
$test_number = sprintf('%.'.($precision - 1).'E', $test);
|
|
|
$test_digits = $precision == 1
|
|
|
? 1
|
|
|
: strlen(rtrim(explode('E', $test_number)[0], '0')) - ($test_number[0] === '-' ? 2 : 1);
|
|
|
|
|
|
if (!$small_scientific || $test_digits - $exponent < $precision) {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
$number = $test_number;
|
|
|
$digits = $test_digits;
|
|
|
}
|
|
|
else {
|
|
|
if ($exponent >= $precision) {
|
|
|
if ($exponent >= min(PHP_FLOAT_DIG, $precision + 3)
|
|
|
|| round($number, $precision - $exponent - 1) != $number) {
|
|
|
$number = round($number, $decimals - $exponent);
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
$number = round($number, min($decimals, $precision - $exponent - 1));
|
|
|
}
|
|
|
|
|
|
$number = sprintf('%.'.($precision - 1).'E', $number);
|
|
|
$digits = $precision == 1 ? 1 : strlen(rtrim(explode('E', $number)[0], '0')) - ($number[0] === '-' ? 2 : 1);
|
|
|
}
|
|
|
|
|
|
if ($zero_as_zero && $number == 0) {
|
|
|
return '0';
|
|
|
}
|
|
|
|
|
|
[
|
|
|
'decimal_point' => $decimal_point,
|
|
|
'thousands_sep' => $thousands_sep
|
|
|
] = getNumericFormatting();
|
|
|
|
|
|
$exponent = (int) explode('E', sprintf('%.'.($precision - 1).'E', $number))[1];
|
|
|
|
|
|
if ($exponent < 0) {
|
|
|
if (!$small_scientific
|
|
|
|| $digits - $exponent <= ($decimals_exact ? min($decimals + 1, $precision) : $precision)) {
|
|
|
return number_format($number, $decimals_exact ? $decimals : $digits - $exponent - 1, $decimal_point,
|
|
|
$thousands_sep
|
|
|
);
|
|
|
}
|
|
|
else {
|
|
|
return str_replace('.', $decimal_point,
|
|
|
sprintf('%.'.($decimals_exact ? $decimals : min($digits - 1, $decimals)).'E', $number)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
elseif ($exponent >= min(PHP_FLOAT_DIG, $precision + 3)
|
|
|
|| ($exponent >= $precision && $number != $number_original)) {
|
|
|
return str_replace('.', $decimal_point,
|
|
|
sprintf('%.'.($decimals_exact ? $decimals : min($digits - 1, $decimals)).'E', $number)
|
|
|
);
|
|
|
}
|
|
|
else {
|
|
|
return number_format($number, $decimals_exact ? $decimals : max(0, min($digits - $exponent - 1, $decimals)),
|
|
|
$decimal_point, $thousands_sep
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Truncate float to the amount of significant digits, to allow safe float comparison.
|
|
|
*
|
|
|
* @param float $number
|
|
|
*
|
|
|
* @return float
|
|
|
*/
|
|
|
function truncateFloat(float $number): float {
|
|
|
if (is_infinite($number)) {
|
|
|
return $number;
|
|
|
}
|
|
|
|
|
|
return (float) sprintf('%.'.(ZBX_FLOAT_DIG - 1).'E', $number);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get number of digits after the decimal dot.
|
|
|
*
|
|
|
* @param float $number Valid number in decimal or scientific notation.
|
|
|
*
|
|
|
* @return int
|
|
|
*/
|
|
|
function getNumDecimals(float $number): int {
|
|
|
[$mantissa, $exponent] = explode('E', sprintf('%.'.(ZBX_FLOAT_DIG - 1).'E', $number));
|
|
|
|
|
|
$significant_size = strlen(rtrim($mantissa, '0')) - ($number < 0 ? 2 : 1);
|
|
|
|
|
|
return max(0, $significant_size - 1 - $exponent);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Converts number to letter representation.
|
|
|
* From A to Z, then from AA to ZZ etc.
|
|
|
* Example: 0 => A, 25 => Z, 26 => AA, 27 => AB, 52 => BA, ...
|
|
|
*
|
|
|
* Keep in sync with JS num2letter().
|
|
|
*
|
|
|
* @param int $number
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function num2letter($number) {
|
|
|
$start = ord('A');
|
|
|
$base = 26;
|
|
|
$str = '';
|
|
|
$level = 0;
|
|
|
|
|
|
do {
|
|
|
if ($level++ > 0) {
|
|
|
$number--;
|
|
|
}
|
|
|
$remainder = $number % $base;
|
|
|
$number = ($number - $remainder) / $base;
|
|
|
$str = chr($start + $remainder).$str;
|
|
|
} while (0 != $number);
|
|
|
|
|
|
return $str;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Renders an "access denied" message and stops the execution of the script.
|
|
|
*
|
|
|
* The $mode parameters controls the layout of the message for logged in users:
|
|
|
* - ACCESS_DENY_OBJECT - render the message when denying access to a specific object
|
|
|
* - ACCESS_DENY_PAGE - render a complete access denied page
|
|
|
*
|
|
|
* If visitor is without any access permission then layout of the message is same as in ACCESS_DENY_PAGE mode.
|
|
|
*
|
|
|
* @param int $mode
|
|
|
*/
|
|
|
function access_deny($mode = ACCESS_DENY_OBJECT) {
|
|
|
// deny access to an object
|
|
|
if ($mode == ACCESS_DENY_OBJECT && CWebUser::isLoggedIn()) {
|
|
|
show_error_message(_('No permissions to referred object or it does not exist!'));
|
|
|
|
|
|
require_once dirname(__FILE__).'/page_header.php';
|
|
|
(new CHtmlPage())->show();
|
|
|
require_once dirname(__FILE__).'/page_footer.php';
|
|
|
}
|
|
|
// deny access to a page
|
|
|
else {
|
|
|
// url to redirect the user to after he logs in
|
|
|
$url = (new CUrl(!empty($_REQUEST['request']) ? $_REQUEST['request'] : ''))
|
|
|
->removeArgument(CCsrfTokenHelper::CSRF_TOKEN_NAME);
|
|
|
|
|
|
if (CAuthenticationHelper::get(CAuthenticationHelper::HTTP_LOGIN_FORM) == ZBX_AUTH_FORM_HTTP
|
|
|
&& CAuthenticationHelper::get(CAuthenticationHelper::HTTP_AUTH_ENABLED) == ZBX_AUTH_HTTP_ENABLED
|
|
|
&& (!CWebUser::isLoggedIn() || CWebUser::isGuest())) {
|
|
|
$redirect_to = (new CUrl('index_http.php'))->setArgument('request', $url->toString());
|
|
|
redirect($redirect_to->toString());
|
|
|
}
|
|
|
|
|
|
$url = urlencode($url->toString());
|
|
|
|
|
|
// if the user is logged in - render the access denied message
|
|
|
if (CWebUser::isLoggedIn()) {
|
|
|
$data = [
|
|
|
'header' => _('Access denied'),
|
|
|
'messages' => [
|
|
|
_s('You are logged in as "%1$s".',
|
|
|
CWebUser::$data['username']).' '._('You have no permissions to access this page.'
|
|
|
),
|
|
|
_('If you think this message is wrong, please consult your administrators about getting the necessary permissions.')
|
|
|
],
|
|
|
'buttons' => []
|
|
|
];
|
|
|
|
|
|
// display the login button only for guest users
|
|
|
if (CWebUser::isGuest()) {
|
|
|
$data['buttons'][] = (new CButton('login', _('Login')))
|
|
|
->setAttribute('data-url', $url)
|
|
|
->onClick('document.location = "index.php?request=" + this.dataset.url;');
|
|
|
}
|
|
|
|
|
|
$data['buttons'][] = (new CButton('back', _s('Go to "%1$s"', CMenuHelper::getFirstLabel())))
|
|
|
->setAttribute('data-url', CMenuHelper::getFirstUrl())
|
|
|
->onClick('document.location = this.dataset.url');
|
|
|
}
|
|
|
// if the user is not logged in - offer to login
|
|
|
else {
|
|
|
$data = [
|
|
|
'header' => _('You are not logged in'),
|
|
|
'messages' => [
|
|
|
_('You must login to view this page.'),
|
|
|
_('If you think this message is wrong, please consult your administrators about getting the necessary permissions.')
|
|
|
],
|
|
|
'buttons' => [
|
|
|
(new CButton('login', _('Login')))
|
|
|
->setAttribute('data-url', $url)
|
|
|
->onClick('document.location = "index.php?request=" + this.dataset.url;')
|
|
|
]
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$data['theme'] = getUserTheme(CWebUser::$data);
|
|
|
|
|
|
if (detect_page_type() == PAGE_TYPE_JS) {
|
|
|
echo (new CView('layout.json', ['main_block' => json_encode(['error' => $data['header']])]))->getOutput();
|
|
|
}
|
|
|
else {
|
|
|
echo (new CView('general.warning', $data))->getOutput();
|
|
|
}
|
|
|
session_write_close();
|
|
|
exit();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function detect_page_type($default = PAGE_TYPE_HTML) {
|
|
|
if (isset($_REQUEST['output'])) {
|
|
|
switch (strtolower($_REQUEST['output'])) {
|
|
|
case 'text':
|
|
|
return PAGE_TYPE_TEXT;
|
|
|
case 'ajax':
|
|
|
return PAGE_TYPE_JS;
|
|
|
case 'json':
|
|
|
return PAGE_TYPE_JSON;
|
|
|
case 'json-rpc':
|
|
|
return PAGE_TYPE_JSON_RPC;
|
|
|
case 'html':
|
|
|
return PAGE_TYPE_HTML_BLOCK;
|
|
|
case 'img':
|
|
|
return PAGE_TYPE_IMAGE;
|
|
|
case 'css':
|
|
|
return PAGE_TYPE_CSS;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $default;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Create a message box.
|
|
|
*
|
|
|
* @param string $class CSS class of the message box. Possible values:
|
|
|
* ZBX_STYLE_MSG_GOOD, ZBX_STYLE_MSG_BAD, ZBX_STYLE_MSG_WARNING.
|
|
|
* @param array $messages An array of messages.
|
|
|
* @param string $messages[]['message'] Message text.
|
|
|
* @param string|null $title (optional) Message box title.
|
|
|
* @param bool $show_close_box (optional) Show or hide close button in error message box.
|
|
|
* @param bool $show_details (optional) Show or hide message details.
|
|
|
*
|
|
|
* @return CTag
|
|
|
*/
|
|
|
function makeMessageBox(string $class, array $messages, string $title = null, bool $show_close_box = true,
|
|
|
bool $show_details = false): CTag {
|
|
|
$msg_details = null;
|
|
|
$link_details = null;
|
|
|
|
|
|
if ($messages) {
|
|
|
if ($title !== null) {
|
|
|
$link_details = (new CLinkAction())
|
|
|
->addItem(_('Details'))
|
|
|
->addItem(' ') // space
|
|
|
->addItem((new CSpan())
|
|
|
->setId('details-arrow')
|
|
|
->addClass($show_details ? ZBX_STYLE_ARROW_UP : ZBX_STYLE_ARROW_DOWN)
|
|
|
)
|
|
|
->setAttribute('aria-expanded', $show_details ? 'true' : 'false')
|
|
|
->onClick('
|
|
|
showHide(jQuery(this).siblings(\'.'.ZBX_STYLE_MSG_DETAILS.'\'));
|
|
|
jQuery("#details-arrow", $(this)).toggleClass("'.ZBX_STYLE_ARROW_UP.' '.ZBX_STYLE_ARROW_DOWN.'");
|
|
|
jQuery(this).attr(\'aria-expanded\', jQuery(this).find(\'.'.ZBX_STYLE_ARROW_DOWN.'\').length == 0);
|
|
|
');
|
|
|
}
|
|
|
|
|
|
$list = (new CList())->addClass(ZBX_STYLE_LIST_DASHED);
|
|
|
|
|
|
foreach ($messages as $message) {
|
|
|
$list->addItem($message['message']);
|
|
|
}
|
|
|
|
|
|
$msg_details = (new CDiv())
|
|
|
->addClass(ZBX_STYLE_MSG_DETAILS)
|
|
|
->addItem($list);
|
|
|
|
|
|
if ($title !== null && !$show_details) {
|
|
|
$msg_details->addStyle('display: none;');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$aria_labels = [
|
|
|
ZBX_STYLE_MSG_GOOD => _('Success message'),
|
|
|
ZBX_STYLE_MSG_BAD => _('Error message'),
|
|
|
ZBX_STYLE_MSG_WARNING => _('Warning message')
|
|
|
];
|
|
|
|
|
|
// Details link should be in front of title.
|
|
|
$msg_box = (new CTag('output', true, [$link_details, $title !== null ? new CSpan($title) : null, $msg_details]))
|
|
|
->addClass($class)
|
|
|
->setAttribute('role', 'contentinfo')
|
|
|
->setAttribute('aria-label', $aria_labels[$class]);
|
|
|
|
|
|
if ($show_close_box) {
|
|
|
$msg_box->addItem(
|
|
|
(new CSimpleButton())
|
|
|
->addClass(ZBX_STYLE_BTN_OVERLAY_CLOSE)
|
|
|
->onClick('jQuery(this).closest(\'.'.$class.'\').remove();')
|
|
|
->setTitle(_('Close'))
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return $msg_box;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Filters messages that can be displayed to user based on CSettingsHelper::SHOW_TECHNICAL_ERRORS and user settings.
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function filter_messages(): array {
|
|
|
if (!CSettingsHelper::getGlobal(CSettingsHelper::SHOW_TECHNICAL_ERRORS)
|
|
|
&& CWebUser::getType() != USER_TYPE_SUPER_ADMIN && !CWebUser::getDebugMode()) {
|
|
|
|
|
|
$type = CMessageHelper::getType();
|
|
|
$title = CMessageHelper::getTitle();
|
|
|
$messages = CMessageHelper::getMessages();
|
|
|
CMessageHelper::clear();
|
|
|
|
|
|
if ($title !== null) {
|
|
|
if ($type === CMessageHelper::MESSAGE_TYPE_SUCCESS) {
|
|
|
CMessageHelper::setSuccessTitle($title);
|
|
|
}
|
|
|
else {
|
|
|
CMessageHelper::setErrorTitle($title);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$generic_exists = false;
|
|
|
foreach ($messages as $message) {
|
|
|
if ($message['type'] === CMessageHelper::MESSAGE_TYPE_ERROR && $message['is_technical_error']) {
|
|
|
if (!$generic_exists) {
|
|
|
CMessageHelper::addError(_('System error occurred. Please contact Zabbix administrator.'));
|
|
|
$generic_exists = true;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
CMessageHelper::addMessage($message);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return CMessageHelper::getMessages();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Returns a message box if there are messages. Otherwise, null.
|
|
|
*
|
|
|
* @param bool $good Parameter passed to makeMessageBox to specify message box style.
|
|
|
* @param string $title Message box title.
|
|
|
* @param bool $show_close_box Show or hide close button in error message box.
|
|
|
*
|
|
|
* @return CTag|null
|
|
|
*/
|
|
|
function getMessages(bool $good = false, string $title = null, bool $show_close_box = true): ?CTag {
|
|
|
$messages = get_and_clear_messages();
|
|
|
|
|
|
$message_box = ($title || $messages)
|
|
|
? makeMessageBox($good ? ZBX_STYLE_MSG_GOOD : ZBX_STYLE_MSG_BAD, $messages, $title, $show_close_box, !$good)
|
|
|
: null;
|
|
|
|
|
|
return $message_box;
|
|
|
}
|
|
|
|
|
|
function show_messages($good = null, $okmsg = null, $errmsg = null) {
|
|
|
global $page, $ZBX_MESSAGES_PREPARED;
|
|
|
|
|
|
if (defined('ZBX_API_REQUEST')) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
$messages = get_and_clear_messages();
|
|
|
|
|
|
if ($good === null) {
|
|
|
$has_errors = false;
|
|
|
$has_warnings = false;
|
|
|
|
|
|
foreach ($messages as $message) {
|
|
|
$has_errors = ($has_errors || ($message['type'] === 'error'));
|
|
|
$has_warnings = ($has_warnings || ($message['type'] === 'warning'));
|
|
|
}
|
|
|
|
|
|
if ($has_errors) {
|
|
|
$class = ZBX_STYLE_MSG_BAD;
|
|
|
$good = false;
|
|
|
}
|
|
|
elseif ($has_warnings) {
|
|
|
$class = ZBX_STYLE_MSG_WARNING;
|
|
|
$good = true;
|
|
|
}
|
|
|
else {
|
|
|
$class = ZBX_STYLE_MSG_GOOD;
|
|
|
$good = true;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
$class = $good ? ZBX_STYLE_MSG_GOOD : ZBX_STYLE_MSG_BAD;
|
|
|
}
|
|
|
|
|
|
$title = $good ? $okmsg : $errmsg;
|
|
|
|
|
|
if ($title === null && !$messages) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
$page_type = (is_array($page) && array_key_exists('type', $page)) ? $page['type'] : PAGE_TYPE_HTML;
|
|
|
|
|
|
switch ($page_type) {
|
|
|
case PAGE_TYPE_IMAGE:
|
|
|
$image_messages = [];
|
|
|
|
|
|
if ($title !== null) {
|
|
|
$image_messages[] = [
|
|
|
'text' => $title,
|
|
|
'color' => (!$good) ? ['R' => 255, 'G' => 0, 'B' => 0] : ['R' => 34, 'G' => 51, 'B' => 68]
|
|
|
];
|
|
|
}
|
|
|
|
|
|
foreach ($messages as $message) {
|
|
|
$image_messages[] = [
|
|
|
'text' => $message['message'],
|
|
|
'color' => ($message['type'] === 'error')
|
|
|
? ['R' => 255, 'G' => 55, 'B' => 55]
|
|
|
: ['R' => 155, 'G' => 155, 'B' => 55]
|
|
|
];
|
|
|
}
|
|
|
|
|
|
// Draw an image with the messages.
|
|
|
$image_font_size = 8;
|
|
|
|
|
|
// Calculate the size of the text.
|
|
|
$width = 0;
|
|
|
$height = 0;
|
|
|
|
|
|
foreach ($image_messages as &$message) {
|
|
|
$size = imageTextSize($image_font_size, 0, $message['text']);
|
|
|
$message['height'] = $size['height'] - $size['baseline'];
|
|
|
|
|
|
// Calculate the total size of the image.
|
|
|
$width = max($width, $size['width']);
|
|
|
$height += $size['height'] + 1;
|
|
|
}
|
|
|
unset($message);
|
|
|
|
|
|
// Add padding.
|
|
|
$width += 2;
|
|
|
$height += 2;
|
|
|
|
|
|
// Create the image.
|
|
|
$canvas = imagecreate($width, $height);
|
|
|
imagefilledrectangle($canvas, 0, 0, $width, $height, imagecolorallocate($canvas, 255, 255, 255));
|
|
|
|
|
|
// Draw messages.
|
|
|
$y = 1;
|
|
|
foreach ($image_messages as $message) {
|
|
|
$y += $message['height'];
|
|
|
imageText($canvas, $image_font_size, 0, 1, $y,
|
|
|
imagecolorallocate($canvas, $message['color']['R'], $message['color']['G'], $message['color']['B']),
|
|
|
$message['text']
|
|
|
);
|
|
|
}
|
|
|
|
|
|
imageOut($canvas);
|
|
|
imagedestroy($canvas);
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
if (!is_array($ZBX_MESSAGES_PREPARED)) {
|
|
|
$ZBX_MESSAGES_PREPARED = [];
|
|
|
}
|
|
|
|
|
|
// Prepare messages for inclusion within the layout engine.
|
|
|
$ZBX_MESSAGES_PREPARED[] = [
|
|
|
'class' => $class,
|
|
|
'messages' => $messages,
|
|
|
'title' => $title,
|
|
|
'show_close_box' => true,
|
|
|
'show_details' => ($class === ZBX_STYLE_MSG_BAD)
|
|
|
];
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get prepared HTML messages generated by the current request and, optionally, passed by the previous request.
|
|
|
*
|
|
|
* @param array $options['with_auth_warning'] Include unsuccessful authentication warning message.
|
|
|
* @param array $options['with_session_messages'] Include messages passed by the previous request.
|
|
|
* @param array $options['with_current_messages'] Include messages generated by the current request.
|
|
|
*
|
|
|
* @return string|null One or several HTML message boxes.
|
|
|
*/
|
|
|
function get_prepared_messages(array $options = []): ?string {
|
|
|
global $ZBX_MESSAGES_PREPARED;
|
|
|
|
|
|
if (!is_array($ZBX_MESSAGES_PREPARED)) {
|
|
|
$ZBX_MESSAGES_PREPARED = [];
|
|
|
}
|
|
|
|
|
|
$options += [
|
|
|
'with_auth_warning' => false,
|
|
|
'with_session_messages' => false,
|
|
|
'with_current_messages' => false
|
|
|
];
|
|
|
|
|
|
// Process messages of the current request.
|
|
|
|
|
|
if ($options['with_current_messages']) {
|
|
|
show_messages(
|
|
|
null,
|
|
|
CMessageHelper::getTitle(),
|
|
|
CMessageHelper::getTitle()
|
|
|
);
|
|
|
|
|
|
$messages_current = $ZBX_MESSAGES_PREPARED;
|
|
|
$restore_messages = [];
|
|
|
$restore_messages_prepared = [];
|
|
|
}
|
|
|
else {
|
|
|
$messages_current = [];
|
|
|
$restore_messages = CMessageHelper::getMessages();
|
|
|
$restore_messages_prepared = $ZBX_MESSAGES_PREPARED;
|
|
|
CMessageHelper::clear();
|
|
|
}
|
|
|
|
|
|
$ZBX_MESSAGES_PREPARED = [];
|
|
|
|
|
|
// Process authentication warning if user had unsuccessful authentication attempts.
|
|
|
|
|
|
if ($options['with_auth_warning'] && ($failed_attempts = CProfile::get('web.login.attempt.failed', 0))) {
|
|
|
$attempt_ip = CProfile::get('web.login.attempt.ip', '');
|
|
|
$attempt_date = CProfile::get('web.login.attempt.clock', 0);
|
|
|
|
|
|
error(_n('%4$s failed login attempt logged. Last failed attempt was from %1$s on %2$s at %3$s.',
|
|
|
'%4$s failed login attempts logged. Last failed attempt was from %1$s on %2$s at %3$s.',
|
|
|
$attempt_ip,
|
|
|
zbx_date2str(DATE_FORMAT, $attempt_date),
|
|
|
zbx_date2str(TIME_FORMAT, $attempt_date),
|
|
|
$failed_attempts
|
|
|
));
|
|
|
|
|
|
show_messages(
|
|
|
false, // Failed login can be only error message.
|
|
|
CMessageHelper::getTitle(),
|
|
|
CMessageHelper::getTitle()
|
|
|
);
|
|
|
|
|
|
CProfile::update('web.login.attempt.failed', 0, PROFILE_TYPE_INT);
|
|
|
}
|
|
|
|
|
|
$messages_authentication = $ZBX_MESSAGES_PREPARED;
|
|
|
$ZBX_MESSAGES_PREPARED = [];
|
|
|
|
|
|
// Process messages passed by the previous request.
|
|
|
|
|
|
if ($options['with_session_messages']) {
|
|
|
CMessageHelper::restoreScheduleMessages($messages_current);
|
|
|
|
|
|
if (CMessageHelper::getTitle() !== null) {
|
|
|
show_messages(
|
|
|
CMessageHelper::getType() === CMessageHelper::MESSAGE_TYPE_SUCCESS,
|
|
|
CMessageHelper::getTitle(),
|
|
|
CMessageHelper::getTitle()
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
$messages_session = $ZBX_MESSAGES_PREPARED;
|
|
|
|
|
|
// Create message boxes for all requested messages types in the correct order.
|
|
|
|
|
|
$html = '';
|
|
|
foreach (array_merge($messages_authentication, $messages_session, $messages_current) as $box) {
|
|
|
$html .= makeMessageBox($box['class'], $box['messages'], $box['title'], $box['show_close_box'],
|
|
|
$box['show_details']
|
|
|
)->toString();
|
|
|
}
|
|
|
|
|
|
foreach ($restore_messages as $message) {
|
|
|
CMessageHelper::addMessage($message);
|
|
|
}
|
|
|
|
|
|
$ZBX_MESSAGES_PREPARED = $restore_messages_prepared;
|
|
|
|
|
|
return ($html === '') ? null : $html;
|
|
|
}
|
|
|
|
|
|
function show_message(string $msg): void {
|
|
|
show_messages(true, $msg, '');
|
|
|
}
|
|
|
|
|
|
function show_error_message(string $msg): void {
|
|
|
show_messages(false, '', $msg);
|
|
|
}
|
|
|
|
|
|
function info($msgs): void {
|
|
|
zbx_value2array($msgs);
|
|
|
|
|
|
foreach ($msgs as $msg) {
|
|
|
CMessageHelper::addSuccess($msg);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Add warning messages to the global message array.
|
|
|
*
|
|
|
* @param array|string $messages
|
|
|
*/
|
|
|
function warning($messages): void {
|
|
|
zbx_value2array($messages);
|
|
|
|
|
|
foreach ($messages as $message) {
|
|
|
CMessageHelper::addWarning($message);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Add an error to global message array.
|
|
|
*
|
|
|
* @param string|array $msgs Error message text.
|
|
|
* @param bool $is_technical_error
|
|
|
*/
|
|
|
function error($msgs, bool $is_technical_error = false): void {
|
|
|
$msgs = zbx_toArray($msgs);
|
|
|
|
|
|
foreach ($msgs as $msg) {
|
|
|
CMessageHelper::addError($msg, $is_technical_error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function get_and_clear_messages(): array {
|
|
|
$messages = filter_messages();
|
|
|
CMessageHelper::clear();
|
|
|
|
|
|
return $messages;
|
|
|
}
|
|
|
|
|
|
function fatal_error($msg) {
|
|
|
require_once dirname(__FILE__).'/page_header.php';
|
|
|
show_error_message($msg);
|
|
|
require_once dirname(__FILE__).'/page_footer.php';
|
|
|
}
|
|
|
|
|
|
function parse_period($str) {
|
|
|
$out = null;
|
|
|
$time_periods_parser = new CTimePeriodsParser();
|
|
|
|
|
|
if ($time_periods_parser->parse($str) != CParser::PARSE_SUCCESS) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
foreach ($time_periods_parser->getPeriods() as $period) {
|
|
|
if (!preg_match('/^([1-7])-([1-7]),([0-9]{1,2}):([0-9]{1,2})-([0-9]{1,2}):([0-9]{1,2})$/', $period, $matches)) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
for ($i = $matches[1]; $i <= $matches[2]; $i++) {
|
|
|
if (!isset($out[$i])) {
|
|
|
$out[$i] = [];
|
|
|
}
|
|
|
array_push($out[$i], [
|
|
|
'start_h' => $matches[3],
|
|
|
'start_m' => $matches[4],
|
|
|
'end_h' => $matches[5],
|
|
|
'end_m' => $matches[6]
|
|
|
]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $out;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Set image header.
|
|
|
*
|
|
|
* @param integer $format One of IMAGE_FORMAT_* constants. If not set global $IMAGE_FORMAT_DEFAULT will be used.
|
|
|
*/
|
|
|
function set_image_header($format = null) {
|
|
|
global $IMAGE_FORMAT_DEFAULT;
|
|
|
|
|
|
switch ($format !== null ? $format : $IMAGE_FORMAT_DEFAULT) {
|
|
|
case IMAGE_FORMAT_JPEG:
|
|
|
header('Content-type: image/jpeg');
|
|
|
break;
|
|
|
|
|
|
case IMAGE_FORMAT_GIF:
|
|
|
header('Content-type: image/gif');
|
|
|
break;
|
|
|
|
|
|
case IMAGE_FORMAT_TEXT:
|
|
|
header('Content-type: text/html');
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
header('Content-type: image/png');
|
|
|
}
|
|
|
|
|
|
header('Expires: Mon, 17 Aug 1998 12:51:50 GMT');
|
|
|
}
|
|
|
|
|
|
function imageOut(&$image, $format = null) {
|
|
|
global $page, $IMAGE_FORMAT_DEFAULT;
|
|
|
|
|
|
if (is_null($format)) {
|
|
|
$format = $IMAGE_FORMAT_DEFAULT;
|
|
|
}
|
|
|
|
|
|
ob_start();
|
|
|
|
|
|
if (IMAGE_FORMAT_JPEG == $format) {
|
|
|
imagejpeg($image);
|
|
|
}
|
|
|
else {
|
|
|
imagepng($image);
|
|
|
}
|
|
|
|
|
|
$imageSource = ob_get_contents();
|
|
|
ob_end_clean();
|
|
|
|
|
|
if ($page['type'] != PAGE_TYPE_IMAGE) {
|
|
|
$imageId = md5(strlen($imageSource));
|
|
|
CSessionHelper::set('image_id', [$imageId => $imageSource]);
|
|
|
}
|
|
|
|
|
|
switch ($page['type']) {
|
|
|
case PAGE_TYPE_IMAGE:
|
|
|
echo $imageSource;
|
|
|
break;
|
|
|
case PAGE_TYPE_JSON:
|
|
|
echo json_encode(['result' => $imageId]);
|
|
|
break;
|
|
|
case PAGE_TYPE_TEXT:
|
|
|
default:
|
|
|
echo $imageId;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Check if we have error messages to display.
|
|
|
*
|
|
|
* @return bool
|
|
|
*/
|
|
|
function hasErrorMessages() {
|
|
|
return CMessageHelper::getType() === CMessageHelper::MESSAGE_TYPE_ERROR;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Clears table rows selection's cookies.
|
|
|
*
|
|
|
* @param string $name entity name, used as sessionStorage suffix
|
|
|
* @param array $keepids checked rows ids
|
|
|
*/
|
|
|
function uncheckTableRows($name = null, $keepids = []) {
|
|
|
$key = 'cb_'.basename($_SERVER['SCRIPT_NAME'], '.php').($name !== null ? '_'.$name : '');
|
|
|
|
|
|
if ($keepids) {
|
|
|
$keepids = array_fill_keys($keepids, '');
|
|
|
|
|
|
insert_js('sessionStorage.setItem('.json_encode($key).', JSON.stringify('.json_encode($keepids).'));');
|
|
|
}
|
|
|
else {
|
|
|
insert_js('sessionStorage.removeItem('.json_encode($key).');');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Trim each element of the script path. For example, " a / b / c d " => "a/b/c d"
|
|
|
*
|
|
|
* @param string $name
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function trimPath($name) {
|
|
|
$path = splitPath($name);
|
|
|
$path = array_map('trim', $path);
|
|
|
$path = str_replace(['\\', '/'], ['\\\\', '\\/'], $path);
|
|
|
return implode('/', $path);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Splitting string using slashes with escape backslash support and non-pair backslash cleanup.
|
|
|
*
|
|
|
* @param string $path
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function splitPath($path) {
|
|
|
$path_items = [];
|
|
|
$path_item = '';
|
|
|
|
|
|
for ($i = 0; isset($path[$i]); $i++) {
|
|
|
switch ($path[$i]) {
|
|
|
case '/':
|
|
|
$path_items[] = $path_item;
|
|
|
$path_item = '';
|
|
|
break;
|
|
|
|
|
|
case '\\':
|
|
|
if (isset($path[++$i])) {
|
|
|
$path_item .= $path[$i];
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
$path_item .= $path[$i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
$path_items[] = $path_item;
|
|
|
|
|
|
return $path_items;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Allocate color for an image.
|
|
|
*
|
|
|
* @param resource $image
|
|
|
* @param string $color a hexadecimal color identifier like "1F2C33"
|
|
|
* @param int $alpha
|
|
|
*
|
|
|
* @return int
|
|
|
*/
|
|
|
function get_color($image, $color, $alpha = 0) {
|
|
|
$red = hexdec('0x'.substr($color, 0, 2));
|
|
|
$green = hexdec('0x'.substr($color, 2, 2));
|
|
|
$blue = hexdec('0x'.substr($color, 4, 2));
|
|
|
|
|
|
return imagecolorexactalpha($image, $red, $green, $blue, $alpha);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get graphic theme based on user configuration.
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function getUserGraphTheme() {
|
|
|
$themes = DB::find('graph_theme', [
|
|
|
'theme' => getUserTheme(CWebUser::$data)
|
|
|
]);
|
|
|
|
|
|
if ($themes) {
|
|
|
return $themes[0];
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
'theme' => 'blue-theme',
|
|
|
'textcolor' => '1F2C33',
|
|
|
'highlightcolor' => 'E33734',
|
|
|
'backgroundcolor' => 'FFFFFF',
|
|
|
'graphcolor' => 'FFFFFF',
|
|
|
'gridcolor' => 'CCD5D9',
|
|
|
'maingridcolor' => 'ACBBC2',
|
|
|
'gridbordercolor' => 'ACBBC2',
|
|
|
'nonworktimecolor' => 'EBEBEB',
|
|
|
'leftpercentilecolor' => '429E47',
|
|
|
'righttpercentilecolor' => 'E33734',
|
|
|
'colorpalette' => '1A7C11,F63100,2774A4,A54F10,FC6EA3,6C59DC,AC8C14,611F27,F230E0,5CCD18,BB2A02,5A2B57,'.
|
|
|
'89ABF8,7EC25C,274482,2B5429,8048B4,FD5434,790E1F,87AC4D,E89DF4'
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Custom error handler for PHP errors.
|
|
|
*
|
|
|
* @param int $errno Level of the error raised.
|
|
|
* @param string $errstr Error message.
|
|
|
* @param string $errfile Filename that the error was raised in.
|
|
|
* @param int $errline Line number the error was raised in.
|
|
|
*
|
|
|
* @return bool
|
|
|
*/
|
|
|
function zbx_err_handler($errno, $errstr, $errfile, $errline) {
|
|
|
// Suppress errors when calling with error control operator @function_name().
|
|
|
if ((error_reporting()
|
|
|
& ~(E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) == 0) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// Don't show the call to this handler function.
|
|
|
error($errstr.' ['.CProfiler::getInstance()->formatCallStack().']', true);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Creates an array with all possible variations of time units.
|
|
|
* For example: '14d' => ['1209600', '1209600s', '20160m', '336h', '14d', '2w']
|
|
|
*
|
|
|
* @param string|array $values
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function getTimeUnitFilters($values) {
|
|
|
if (is_array($values)) {
|
|
|
$res = [];
|
|
|
|
|
|
foreach ($values as $value) {
|
|
|
$res = array_merge($res, getTimeUnitFilters($value));
|
|
|
}
|
|
|
|
|
|
return array_unique($res, SORT_STRING);
|
|
|
}
|
|
|
|
|
|
$simple_interval_parser = new CSimpleIntervalParser();
|
|
|
|
|
|
if ($simple_interval_parser->parse($values) != CParser::PARSE_SUCCESS) {
|
|
|
return [$values];
|
|
|
}
|
|
|
|
|
|
$sec = timeUnitToSeconds($values);
|
|
|
|
|
|
$res = [$sec, $sec.'s'];
|
|
|
|
|
|
if ($sec % SEC_PER_MIN == 0) {
|
|
|
$res[] = floor($sec / SEC_PER_MIN).'m';
|
|
|
}
|
|
|
|
|
|
if ($sec % SEC_PER_HOUR == 0) {
|
|
|
$res[] = floor($sec / SEC_PER_HOUR).'h';
|
|
|
}
|
|
|
|
|
|
if ($sec % SEC_PER_DAY == 0) {
|
|
|
$res[] = floor($sec / SEC_PER_DAY).'d';
|
|
|
}
|
|
|
|
|
|
if ($sec % SEC_PER_WEEK == 0) {
|
|
|
$res[] = floor($sec / SEC_PER_WEEK).'w';
|
|
|
}
|
|
|
|
|
|
return $res;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Creates SQL filter to search all possible variations of time units.
|
|
|
*
|
|
|
* @param string $field_name
|
|
|
* @param string|array $values
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function makeUpdateIntervalFilter($field_name, $values) {
|
|
|
$filters = [];
|
|
|
|
|
|
foreach (getTimeUnitFilters($values) as $filter) {
|
|
|
$filter = str_replace("!", "!!", $filter);
|
|
|
$filter = str_replace("%", "!%", $filter);
|
|
|
$filter = str_replace("_", "!_", $filter);
|
|
|
|
|
|
$filters[] = $field_name.' LIKE '.zbx_dbstr($filter).' ESCAPE '.zbx_dbstr('!');
|
|
|
$filters[] = $field_name.' LIKE '.zbx_dbstr($filter.';%').' ESCAPE '.zbx_dbstr('!');
|
|
|
}
|
|
|
|
|
|
$res = $filters ? implode(' OR ', $filters) : '';
|
|
|
|
|
|
if (count($filters) > 1) {
|
|
|
$res = '('.$res.')';
|
|
|
}
|
|
|
|
|
|
return $res;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Update profile with new time selector range.
|
|
|
*
|
|
|
* @param array $options
|
|
|
* @param string $options['profileIdx']
|
|
|
* @param int $options['profileIdx2']
|
|
|
* @param string|null $options['from']
|
|
|
* @param string|null $options['to']
|
|
|
*/
|
|
|
function updateTimeSelectorPeriod(array $options) {
|
|
|
if ($options['from'] !== null && $options['to'] !== null) {
|
|
|
CProfile::update($options['profileIdx'].'.from', $options['from'], PROFILE_TYPE_STR, $options['profileIdx2']);
|
|
|
CProfile::update($options['profileIdx'].'.to', $options['to'], PROFILE_TYPE_STR, $options['profileIdx2']);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get profile stored 'from' and 'to'. If profileIdx is null then default values will be returned. If one of fields
|
|
|
* not exist in $options array 'from' and 'to' value will be read from user profile. Calculates from_ts, to_ts.
|
|
|
*
|
|
|
* @param array $options Array with period fields data: profileIdx, profileIdx2, from, to.
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function getTimeSelectorPeriod(array $options) {
|
|
|
$profileIdx = array_key_exists('profileIdx', $options) ? $options['profileIdx'] : null;
|
|
|
$profileIdx2 = array_key_exists('profileIdx2', $options) ? $options['profileIdx2'] : null;
|
|
|
|
|
|
if ($profileIdx === null) {
|
|
|
$options['from'] = 'now-'.CSettingsHelper::get(CSettingsHelper::PERIOD_DEFAULT);
|
|
|
$options['to'] = 'now';
|
|
|
}
|
|
|
elseif (!array_key_exists('from', $options) || !array_key_exists('to', $options)
|
|
|
|| $options['from'] === null || $options['to'] === null) {
|
|
|
$options['from'] = CProfile::get($profileIdx.'.from',
|
|
|
'now-'.CSettingsHelper::get(CSettingsHelper::PERIOD_DEFAULT),
|
|
|
$profileIdx2
|
|
|
);
|
|
|
$options['to'] = CProfile::get($profileIdx.'.to', 'now', $profileIdx2);
|
|
|
}
|
|
|
|
|
|
$range_time_parser = new CRangeTimeParser();
|
|
|
|
|
|
$range_time_parser->parse($options['from']);
|
|
|
$options['from_ts'] = $range_time_parser->getDateTime(true)->getTimestamp();
|
|
|
$range_time_parser->parse($options['to']);
|
|
|
$options['to_ts'] = $range_time_parser->getDateTime(false)->getTimestamp();
|
|
|
|
|
|
return $options;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get array of action statuses available for defined time range. For incorrect "from" or "to" all actions will be set
|
|
|
* to false.
|
|
|
*
|
|
|
* @param string $from Relative or absolute time, cannot be null.
|
|
|
* @param string $to Relative or absolute time, cannot be null.
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function getTimeselectorActions($from, $to): array {
|
|
|
$ts_now = time();
|
|
|
$parser = new CRangeTimeParser();
|
|
|
$ts_from = ($parser->parse($from) !== CParser::PARSE_FAIL) ? $parser->getDateTime(true)->getTimestamp() : null;
|
|
|
$ts_to = ($parser->parse($to) !== CParser::PARSE_FAIL) ? $parser->getDateTime(false)->getTimestamp() : null;
|
|
|
$valid = ($ts_from !== null && $ts_to !== null);
|
|
|
$parser->parse('now-'.CSettingsHelper::get(CSettingsHelper::MAX_PERIOD));
|
|
|
$max_period = 1 + $ts_now - $parser->getDateTime(true)->getTimestamp();
|
|
|
|
|
|
return [
|
|
|
'can_zoomout' => ($valid && ($ts_to - $ts_from + 1 < $max_period)),
|
|
|
'can_decrement' => ($valid && ($ts_from > 0)),
|
|
|
'can_increment' => ($valid && ($ts_to < $ts_now - ZBX_MIN_PERIOD))
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Convert relative date range string to translated string. Function does not check is passed date range correct.
|
|
|
*
|
|
|
* @param string $from Start date of date range.
|
|
|
* @param string $to End date of date range.
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function relativeDateToText($from, $to) {
|
|
|
$key = $from.':'.$to;
|
|
|
$ranges = [
|
|
|
'now-1d/d:now-1d/d' => _('Yesterday'),
|
|
|
'now-2d/d:now-2d/d' => _('Day before yesterday'),
|
|
|
'now-1w/d:now-1w/d' => _('This day last week'),
|
|
|
'now-1w/w:now-1w/w' => _('Previous week'),
|
|
|
'now-1M/M:now-1M/M' => _('Previous month'),
|
|
|
'now-1y/y:now-1y/y' => _('Previous year'),
|
|
|
'now/d:now/d' => _('Today'),
|
|
|
'now/d:now' => _('Today so far'),
|
|
|
'now/w:now/w' => _('This week'),
|
|
|
'now/w:now' => _('This week so far'),
|
|
|
'now/M:now/M' => _('This month'),
|
|
|
'now/M:now' => _('This month so far'),
|
|
|
'now/y:now/y' => _('This year'),
|
|
|
'now/y:now' => _('This year so far')
|
|
|
];
|
|
|
|
|
|
if (array_key_exists($key, $ranges)) {
|
|
|
return $ranges[$key];
|
|
|
}
|
|
|
|
|
|
if ($to === 'now') {
|
|
|
$relative_time_parser = new CRelativeTimeParser();
|
|
|
|
|
|
if ($relative_time_parser->parse($from) == CParser::PARSE_SUCCESS) {
|
|
|
$tokens = $relative_time_parser->getTokens();
|
|
|
|
|
|
if (count($tokens) == 1 && $tokens[0]['type'] == CRelativeTimeParser::ZBX_TOKEN_OFFSET
|
|
|
&& $tokens[0]['sign'] === '-') {
|
|
|
$suffix = $tokens[0]['suffix'];
|
|
|
$value = (int) $tokens[0]['value'];
|
|
|
|
|
|
switch ($suffix) {
|
|
|
case 's':
|
|
|
if ($value < 60 || $value % 60 != 0) {
|
|
|
return _n('Last %1$d second', 'Last %1$d seconds', $value);
|
|
|
}
|
|
|
$value /= 60;
|
|
|
// break; is not missing here.
|
|
|
|
|
|
case 'm':
|
|
|
if ($value < 60 || $value % 60 != 0) {
|
|
|
return _n('Last %1$d minute', 'Last %1$d minutes', $value);
|
|
|
}
|
|
|
$value /= 60;
|
|
|
// break; is not missing here.
|
|
|
|
|
|
case 'h':
|
|
|
if ($value < 24 || $value % 24 != 0) {
|
|
|
return _n('Last %1$d hour', 'Last %1$d hours', $value);
|
|
|
}
|
|
|
$value /= 24;
|
|
|
// break; is not missing here.
|
|
|
|
|
|
case 'd':
|
|
|
return _n('Last %1$d day', 'Last %1$d days', $value);
|
|
|
|
|
|
case 'M':
|
|
|
return _n('Last %1$d month', 'Last %1$d months', $value);
|
|
|
|
|
|
case 'y':
|
|
|
return _n('Last %1$d year', 'Last %1$d years', $value);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return $from.' – '.$to;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get human readable time period.
|
|
|
*
|
|
|
* @param int $seconds
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function secondsToPeriod(int $seconds): string {
|
|
|
$hours = floor($seconds / 3600);
|
|
|
$seconds -= $hours * 3600;
|
|
|
|
|
|
$minutes = floor($seconds / 60);
|
|
|
$seconds -= $minutes * 60;
|
|
|
|
|
|
$period = ($hours > 0) ? _n('%1$s hour', '%1$s hours', $hours) : '';
|
|
|
|
|
|
if ($minutes > 0) {
|
|
|
if ($period !== '') {
|
|
|
$period .= ', ';
|
|
|
}
|
|
|
$period .= _n('%1$s minute', '%1$s minutes', $minutes);
|
|
|
}
|
|
|
|
|
|
if ($seconds > 0 || $period === '') {
|
|
|
if ($period !== '') {
|
|
|
$period .= ', ';
|
|
|
}
|
|
|
$period .= _n('%1$s second', '%1$s seconds', $seconds);
|
|
|
}
|
|
|
|
|
|
return $period;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Generates UUID version 4.
|
|
|
*
|
|
|
* @param string $seed String to be hashed as md5 and used as UUID body.
|
|
|
*
|
|
|
* @return string
|
|
|
*/
|
|
|
function generateUuidV4($seed = '') {
|
|
|
$data = ($seed === '') ? random_bytes(16) : hex2bin(md5($seed));
|
|
|
|
|
|
// Set head of 7th byte to 0100 (0100xxxx)
|
|
|
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
|
|
|
|
|
// Set head of 9th byte to 10 (10xxxxxx)
|
|
|
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
|
|
|
|
|
return bin2hex($data);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Function returns predefined Leaflet Tile providers with parameters.
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
function getTileProviders(): array {
|
|
|
return [
|
|
|
'OpenStreetMap.Mapnik' => [
|
|
|
'name' => 'OpenStreetMap Mapnik',
|
|
|
'geomaps_tile_url' => 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
|
'geomaps_max_zoom' => '19',
|
|
|
'geomaps_attribution' => '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
|
],
|
|
|
'OpenTopoMap' => [
|
|
|
'name' => 'OpenTopoMap',
|
|
|
'geomaps_tile_url' => 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
|
|
|
'geomaps_max_zoom' => '17',
|
|
|
'geomaps_attribution' => 'Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: © <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
|
|
|
],
|
|
|
'Stamen.TonerLite' => [
|
|
|
'name' => 'Stamen Toner Lite',
|
|
|
'geomaps_tile_url' => 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png',
|
|
|
'geomaps_max_zoom' => '20',
|
|
|
'geomaps_attribution' => 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
|
],
|
|
|
'Stamen.Terrain' => [
|
|
|
'name' => 'Stamen Terrain',
|
|
|
'geomaps_tile_url' => 'https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}{r}.png',
|
|
|
'geomaps_max_zoom' => '18',
|
|
|
'geomaps_attribution' => 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
|
],
|
|
|
'USGS.USTopo' => [
|
|
|
'name' => 'USGS US Topo',
|
|
|
'geomaps_tile_url' => 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}',
|
|
|
'geomaps_max_zoom' => '20',
|
|
|
'geomaps_attribution' => 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
|
|
|
],
|
|
|
'USGS.USImagery' => [
|
|
|
'name' => 'USGS US Imagery',
|
|
|
'geomaps_tile_url' => 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}',
|
|
|
'geomaps_max_zoom' => '20',
|
|
|
'geomaps_attribution' => 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
|
|
|
]
|
|
|
];
|
|
|
}
|