You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2040 lines
59 KiB

1 year ago
<?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.
**/
/**
* Add color style and blinking to an object like CSpan or CDiv depending on trigger status.
* Settings and colors are kept in 'config' database table.
*
* @param mixed $object object like CSpan, CDiv, etc.
* @param int $triggerValue TRIGGER_VALUE_FALSE or TRIGGER_VALUE_TRUE
* @param int $triggerLastChange
* @param bool $isAcknowledged
*/
function addTriggerValueStyle($object, $triggerValue, $triggerLastChange, $isAcknowledged) {
$color_class = null;
$blinks = null;
// Color class for text and blinking depends on trigger value and whether event is acknowledged.
if ($triggerValue == TRIGGER_VALUE_TRUE && !$isAcknowledged) {
$color_class = ZBX_STYLE_PROBLEM_UNACK_FG;
$blinks = CSettingsHelper::get(CSettingsHelper::PROBLEM_UNACK_STYLE);
}
elseif ($triggerValue == TRIGGER_VALUE_TRUE && $isAcknowledged) {
$color_class = ZBX_STYLE_PROBLEM_ACK_FG;
$blinks = CSettingsHelper::get(CSettingsHelper::PROBLEM_ACK_STYLE);
}
elseif ($triggerValue == TRIGGER_VALUE_FALSE && !$isAcknowledged) {
$color_class = ZBX_STYLE_OK_UNACK_FG;
$blinks = CSettingsHelper::get(CSettingsHelper::OK_UNACK_STYLE);
}
elseif ($triggerValue == TRIGGER_VALUE_FALSE && $isAcknowledged) {
$color_class = ZBX_STYLE_OK_ACK_FG;
$blinks = CSettingsHelper::get(CSettingsHelper::OK_ACK_STYLE);
}
if ($color_class != null && $blinks != null) {
$object->addClass($color_class);
// blinking
$timeSinceLastChange = time() - $triggerLastChange;
$blink_period = timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::BLINK_PERIOD));
if ($blinks && $timeSinceLastChange < $blink_period) {
$object->addClass('js-blink'); // elements with this class will blink
$object->setAttribute('data-time-to-blink', $blink_period - $timeSinceLastChange);
}
}
else {
$object->addClass(ZBX_STYLE_GREY);
}
}
function trigger_value2str($value = null) {
$triggerValues = [
TRIGGER_VALUE_FALSE => _('OK'),
TRIGGER_VALUE_TRUE => _('PROBLEM')
];
if ($value === null) {
return $triggerValues;
}
elseif (isset($triggerValues[$value])) {
return $triggerValues[$value];
}
return _('Unknown');
}
function get_trigger_by_triggerid($triggerid) {
$db_trigger = DBfetch(DBselect('SELECT t.* FROM triggers t WHERE t.triggerid='.zbx_dbstr($triggerid)));
if (!empty($db_trigger)) {
return $db_trigger;
}
error(_s('No trigger with trigger ID "%1$s".', $triggerid));
return false;
}
function get_triggers_by_hostid($hostid) {
return DBselect(
'SELECT DISTINCT t.*'.
' FROM triggers t,functions f,items i'.
' WHERE i.hostid='.zbx_dbstr($hostid).
' AND f.itemid=i.itemid'.
' AND f.triggerid=t.triggerid'
);
}
/**
* Prepare arrays containing only hosts and triggers that will be shown results table.
*
* @param array $db_hosts
* @param array $db_triggers
*
* @return array
*/
function getTriggersOverviewTableData(array $db_hosts, array $db_triggers): array {
// Prepare triggers to show in results table.
$triggers_by_name = [];
foreach ($db_triggers as $trigger) {
foreach ($trigger['hosts'] as $host) {
if (!array_key_exists($host['hostid'], $db_hosts)) {
continue;
}
$triggers_by_name[$trigger['description']][$host['hostid']] = $trigger['triggerid'];
}
}
$limit = (int) CSettingsHelper::get(CSettingsHelper::MAX_OVERVIEW_TABLE_SIZE);
$exceeded_trigs = (count($triggers_by_name) > $limit);
$triggers_by_name = array_slice($triggers_by_name, 0, $limit, true);
foreach ($triggers_by_name as $name => $triggers) {
$triggers_by_name[$name] = array_slice($triggers, 0, $limit, true);
}
// Prepare hosts to show in results table.
$exceeded_hosts = false;
$hosts_by_name = [];
foreach ($db_hosts as $host) {
if (count($hosts_by_name) >= $limit) {
$exceeded_hosts = true;
break;
}
else {
$hosts_by_name[$host['name']] = $host['hostid'];
}
}
return [$triggers_by_name, $hosts_by_name, ($exceeded_hosts || $exceeded_trigs)];
}
/**
* @param array $groupids
* @param array $host_options
* @param array $trigger_options
* @param array $problem_options
* @param int $problem_options['min_severity'] (optional) Minimal problem severity.
* @param int $problem_options['show_suppressed'] (optional) Whether to show triggers with suppressed problems.
* @param int $problem_options['time_from'] (optional) The time starting from which the problems were created.
* @param array $problem_options['tags'] (optional)
* @param string $problem_options['tags'][]['tag'] (optional)
* @param int $problem_options['tags'][]['operation'] (optional)
* @param string $problem_options['tags'][]['value'] (optional)
* @param int $problem_options['evaltype'] (optional)
*
* @return array
*/
function getTriggersOverviewData(array $groupids, array $host_options = [], array $trigger_options = [],
array $problem_options = []): array {
$host_options = [
'output' => ['hostid', 'name'],
'groupids' => $groupids ? $groupids : null,
'with_monitored_triggers' => true,
'preservekeys' => true
] + $host_options;
$trigger_options = [
'output' => ['triggerid', 'expression', 'description', 'value', 'priority', 'lastchange', 'flags', 'comments',
'manual_close'
],
'selectHosts' => ['hostid', 'name'],
'selectDependencies' => ['triggerid'],
'monitored' => true
] + $trigger_options;
$problem_options += [
'show_suppressed' => ZBX_PROBLEM_SUPPRESSED_FALSE
];
$limit = 0;
do {
$limit += (int) CSettingsHelper::get(CSettingsHelper::MAX_OVERVIEW_TABLE_SIZE);
$db_hosts = API::Host()->get(['limit' => $limit + 1] + $host_options);
$fetch_more = (count($db_hosts) > $limit);
$db_triggers = getTriggersWithActualSeverity([
'hostids' => array_keys($db_hosts)
] + $trigger_options, $problem_options);
if (!$db_triggers) {
$db_hosts = [];
}
// Unset hosts without having matching triggers.
$represented_hosts = [];
foreach ($db_triggers as $trigger) {
$hostids = array_column($trigger['hosts'], 'hostid');
$represented_hosts += array_combine($hostids, $hostids);
}
$db_hosts = array_intersect_key($db_hosts, $represented_hosts);
} while ($fetch_more && count($db_hosts) < $limit);
CArrayHelper::sort($db_hosts, [
['field' => 'name', 'order' => ZBX_SORT_UP]
]);
$db_triggers = CMacrosResolverHelper::resolveTriggerNames($db_triggers, true);
$dependencies = $db_triggers ? getTriggerDependencies($db_triggers) : [];
CArrayHelper::sort($db_triggers, [
['field' => 'description', 'order' => ZBX_SORT_UP]
]);
[$triggers_by_name, $hosts_by_name, $exceeded_limit] = getTriggersOverviewTableData($db_hosts, $db_triggers);
return [$db_hosts, $db_triggers, $dependencies, $triggers_by_name, $hosts_by_name, $exceeded_limit];
}
/**
* Get triggers data with priority set to highest priority of unresolved problems generated by this trigger.
*
* @param array $trigger_options API options. Array 'output' should contain 'value', option
* 'preservekeys' should be set to true.
* @param array $problem_options
* @param int $problem_options['show_suppressed'] Whether to show triggers with suppressed problems.
* @param int $problem_options['min_severity'] (optional) Minimal problem severity.
* @param int $problem_options['time_from'] (optional) The time starting from which the problems were
* created.
* @param bool $problem_options['acknowledged'] (optional) Whether to show triggers with acknowledged
* problems.
* @param array $problem_options['tags'] (optional)
* @param string $problem_options['tags'][]['tag'] (optional)
* @param int $problem_options['tags'][]['operation'] (optional)
* @param string $problem_options['tags'][]['value'] (optional)
* @param int $problem_options['evaltype'] (optional)
*
* @return array
*/
function getTriggersWithActualSeverity(array $trigger_options, array $problem_options) {
$problem_options += [
'min_severity' => TRIGGER_SEVERITY_NOT_CLASSIFIED,
'show_suppressed' => null,
'show_recent' => null,
'time_from' => null,
'acknowledged' => null
];
$triggers = API::Trigger()->get(['preservekeys' => true] + $trigger_options);
$nondependent_trigger_options = [
'output' => [],
'triggerids' => array_keys($triggers),
'skipDependent' => true,
'preservekeys' => true
];
$nondependent_triggers = API::Trigger()->get($nondependent_trigger_options);
CArrayHelper::sort($triggers, ['description']);
if ($triggers) {
$problem_stats = [];
foreach ($triggers as $triggerid => &$trigger) {
$trigger['priority'] = TRIGGER_SEVERITY_NOT_CLASSIFIED;
$trigger['resolved'] = true;
$problem_stats[$triggerid] = [
'has_resolved' => false,
'has_unresolved' => false,
'has_resolved_unacknowledged' => false,
'has_unresolved_unacknowledged' => false
];
if ($trigger['value'] == TRIGGER_VALUE_TRUE && !array_key_exists($triggerid, $nondependent_triggers)) {
$trigger['value'] = TRIGGER_VALUE_FALSE;
}
}
unset($trigger);
$problems = API::Problem()->get([
'output' => ['eventid', 'acknowledged', 'objectid', 'severity', 'r_eventid'],
'objectids' => array_keys($triggers),
'suppressed' => ($problem_options['show_suppressed'] == ZBX_PROBLEM_SUPPRESSED_FALSE) ? false : null,
'symptom' => false,
'recent' => $problem_options['show_recent'],
'acknowledged' => $problem_options['acknowledged'],
'time_from' => $problem_options['time_from'],
'tags' => array_key_exists('tags', $problem_options) && $problem_options['tags']
? $problem_options['tags']
: null,
'evaltype' => array_key_exists('evaltype', $problem_options)
? $problem_options['evaltype']
: TAG_EVAL_TYPE_AND_OR
]);
foreach ($problems as $problem) {
$triggerid = $problem['objectid'];
if ($problem['r_eventid'] == 0 && array_key_exists($triggerid, $nondependent_triggers)) {
$triggers[$triggerid]['resolved'] = false;
}
$triggers[$triggerid]['problem']['eventid'] = $problem['eventid'];
if ($triggers[$triggerid]['priority'] < $problem['severity']) {
$triggers[$triggerid]['priority'] = $problem['severity'];
}
if ($problem['r_eventid'] == 0) {
$problem_stats[$triggerid]['has_unresolved'] = true;
if ($problem['acknowledged'] == 0 && $problem['severity'] >= $problem_options['min_severity']) {
$problem_stats[$triggerid]['has_unresolved_unacknowledged'] = true;
}
}
else {
$problem_stats[$triggerid]['has_resolved'] = true;
if ($problem['acknowledged'] == 0 && $problem['severity'] >= $problem_options['min_severity']) {
$problem_stats[$triggerid]['has_resolved_unacknowledged'] = true;
}
}
}
foreach ($triggers as $triggerid => &$trigger) {
$stats = $problem_stats[$triggerid];
$trigger['problem']['acknowledged'] = (
// Trigger has only resolved problems, all acknowledged.
($stats['has_resolved'] && !$stats['has_resolved_unacknowledged'] && !$stats['has_unresolved'])
// Trigger has unresolved problems, all acknowledged.
|| ($stats['has_unresolved'] && !$stats['has_unresolved_unacknowledged'])
) ? 1 : 0;
$trigger['value'] = ($triggers[$triggerid]['resolved'] === true)
? TRIGGER_VALUE_FALSE
: TRIGGER_VALUE_TRUE;
if (($stats['has_resolved'] || $stats['has_unresolved'])
&& $trigger['priority'] >= $problem_options['min_severity']) {
continue;
}
if (!array_key_exists('only_true', $trigger_options)
|| ($trigger_options['only_true'] === null && $trigger_options['filter']['value'] === null)) {
// Overview type = 'Data', Maps, Dashboard or Overview 'show any' mode.
$trigger['value'] = TRIGGER_VALUE_FALSE;
}
else {
unset($triggers[$triggerid]);
}
}
unset($trigger);
}
return $triggers;
}
/**
* Calculate trigger availability.
*
* @param int $triggerId trigger id
* @param int $startTime begin period
* @param int $endTime end period
*
* @return array
*/
function calculateAvailability($triggerId, $startTime, $endTime) {
$startValue = TRIGGER_VALUE_FALSE;
if ($startTime > 0 && $startTime <= time()) {
$sql = 'SELECT e.eventid,e.value'.
' FROM events e'.
' WHERE e.objectid='.zbx_dbstr($triggerId).
' AND e.source='.EVENT_SOURCE_TRIGGERS.
' AND e.object='.EVENT_OBJECT_TRIGGER.
' AND e.clock<'.zbx_dbstr($startTime).
' ORDER BY e.eventid DESC';
if ($row = DBfetch(DBselect($sql, 1))) {
$startValue = $row['value'];
}
$min = $startTime;
}
$sql = 'SELECT COUNT(e.eventid) AS cnt,MIN(e.clock) AS min_clock,MAX(e.clock) AS max_clock'.
' FROM events e'.
' WHERE e.objectid='.zbx_dbstr($triggerId).
' AND e.source='.EVENT_SOURCE_TRIGGERS.
' AND e.object='.EVENT_OBJECT_TRIGGER;
if ($startTime) {
$sql .= ' AND e.clock>='.zbx_dbstr($startTime);
}
if ($endTime) {
$sql .= ' AND e.clock<='.zbx_dbstr($endTime);
}
$dbEvents = DBfetch(DBselect($sql));
if ($dbEvents['cnt'] > 0) {
if (!isset($min)) {
$min = $dbEvents['min_clock'];
}
$max = $dbEvents['max_clock'];
}
else {
if ($startTime == 0 && $endTime == 0) {
$max = time();
$min = $max - SEC_PER_DAY;
}
else {
$ret['true_time'] = 0;
$ret['false_time'] = 0;
$ret['true'] = (TRIGGER_VALUE_TRUE == $startValue) ? 100 : 0;
$ret['false'] = (TRIGGER_VALUE_FALSE == $startValue) ? 100 : 0;
return $ret;
}
}
$state = $startValue;
$true_time = 0;
$false_time = 0;
$time = $min;
if ($startTime == 0 && $endTime == 0) {
$max = time();
}
if ($endTime == 0) {
$endTime = $max;
}
$rows = 0;
$dbEvents = DBselect(
'SELECT e.eventid,e.clock,e.value'.
' FROM events e'.
' WHERE e.objectid='.zbx_dbstr($triggerId).
' AND e.source='.EVENT_SOURCE_TRIGGERS.
' AND e.object='.EVENT_OBJECT_TRIGGER.
' AND e.clock BETWEEN '.$min.' AND '.$max.
' ORDER BY e.eventid'
);
while ($row = DBfetch($dbEvents)) {
$clock = $row['clock'];
$value = $row['value'];
$diff = max($clock - $time, 0);
$time = $clock;
if ($state == 0) {
$false_time += $diff;
$state = $value;
}
elseif ($state == 1) {
$true_time += $diff;
$state = $value;
}
$rows++;
}
if ($rows == 0) {
$trigger = get_trigger_by_triggerid($triggerId);
$state = $trigger['value'];
}
if ($state == TRIGGER_VALUE_FALSE) {
$false_time = $false_time + $endTime - $time;
}
elseif ($state == TRIGGER_VALUE_TRUE) {
$true_time = $true_time + $endTime - $time;
}
$total_time = $true_time + $false_time;
if ($total_time == 0) {
$ret['true_time'] = 0;
$ret['false_time'] = 0;
$ret['true'] = 0;
$ret['false'] = 0;
}
else {
$ret['true_time'] = $true_time;
$ret['false_time'] = $false_time;
$ret['true'] = (100 * $true_time) / $total_time;
$ret['false'] = (100 * $false_time) / $total_time;
}
return $ret;
}
function get_triggers_unacknowledged($db_element, $count_problems = null, $ack = false) {
$elements = [
'hosts' => [],
'hosts_groups' => [],
'triggers' => []
];
get_map_elements($db_element, $elements);
if (empty($elements['hosts_groups']) && empty($elements['hosts']) && empty($elements['triggers'])) {
return 0;
}
$options = [
'monitored' => true,
'countOutput' => true,
'filter' => [],
'limit' => CSettingsHelper::get(CSettingsHelper::SEARCH_LIMIT) + 1
];
if ($ack) {
$options['withAcknowledgedEvents'] = 1;
}
else {
$options['withUnacknowledgedEvents'] = 1;
}
if ($count_problems) {
$options['filter']['value'] = TRIGGER_VALUE_TRUE;
}
if (!empty($elements['hosts_groups'])) {
$options['groupids'] = array_unique($elements['hosts_groups']);
}
if (!empty($elements['hosts'])) {
$options['hostids'] = array_unique($elements['hosts']);
}
if (!empty($elements['triggers'])) {
$options['triggerids'] = array_unique($elements['triggers']);
}
return API::Trigger()->get($options);
}
/**
* Make trigger info block.
*
* @param array $trigger Trigger described in info block.
* @param string $eventid Associated event ID.
*
* @return object
*/
function make_trigger_details($trigger, $eventid) {
$hostNames = [];
$hostIds = zbx_objectValues($trigger['hosts'], 'hostid');
$hosts = API::Host()->get([
'output' => ['name', 'hostid', 'status'],
'hostids' => $hostIds
]);
if (count($hosts) > 1) {
order_result($hosts, 'name');
}
foreach ($hosts as $host) {
$hostNames[] = (new CLinkAction($host['name']))->setMenuPopup(CMenuPopupHelper::getHost($host['hostid']));
$hostNames[] = ', ';
}
array_pop($hostNames);
$table = (new CTableInfo())
->addRow([
new CCol(_n('Host', 'Hosts', count($hosts))),
(new CCol($hostNames))->addClass(ZBX_STYLE_WORDBREAK)
])
->addRow([
new CCol(_('Trigger')),
new CCol((new CLinkAction(CMacrosResolverHelper::resolveTriggerName($trigger)))
->addClass(ZBX_STYLE_WORDWRAP)
->setMenuPopup(CMenuPopupHelper::getTrigger([
'triggerid' => $trigger['triggerid'],
'backurl' => (new CUrl('tr_events.php'))
->setArgument('triggerid', $trigger['triggerid'])
->setArgument('eventid', $eventid)
->getUrl(),
'eventid' => $eventid,
'show_rank_change_cause' => true
]))
)
])
->addRow([
_('Severity'),
CSeverityHelper::makeSeverityCell((int) $trigger['priority'])
]);
$trigger = CMacrosResolverHelper::resolveTriggerExpressions(zbx_toHash($trigger, 'triggerid'), [
'html' => true,
'resolve_usermacros' => true,
'resolve_macros' => true,
'sources' => ['expression', 'recovery_expression']
]);
$trigger = reset($trigger);
$table
->addRow([
new CCol(_('Problem expression')),
new CCol((new CDiv($trigger['expression']))->addClass(ZBX_STYLE_WORDWRAP))
])
->addRow([
new CCol(_('Recovery expression')),
new CCol((new CDiv($trigger['recovery_expression']))->addClass(ZBX_STYLE_WORDWRAP))
])
->addRow([_('Event generation'), _('Normal').((TRIGGER_MULT_EVENT_ENABLED == $trigger['type'])
? ' + '._('Multiple PROBLEM events')
: '')
]);
$table->addRow([_('Allow manual close'), ($trigger['manual_close'] == ZBX_TRIGGER_MANUAL_CLOSE_ALLOWED)
? (new CCol(_('Yes')))->addClass(ZBX_STYLE_GREEN)
: (new CCol(_('No')))->addClass(ZBX_STYLE_RED)
]);
$table->addRow([_('Enabled'), ($trigger['status'] == TRIGGER_STATUS_ENABLED)
? (new CCol(_('Yes')))->addClass(ZBX_STYLE_GREEN)
: (new CCol(_('No')))->addClass(ZBX_STYLE_RED)
]);
return $table;
}
/**
* Analyze an expression and returns expression html tree.
*
* @param string $expression Trigger expression or recovery expression string.
* @param int $type Type can be either TRIGGER_EXPRESSION or TRIGGER_RECOVERY_EXPRESSION.
* @param string $error [OUT] An error message.
*
* @return array|bool
*/
function analyzeExpression(string $expression, int $type, string &$error = null) {
if ($expression === '') {
return ['', null];
}
$expression_parser = new CExpressionParser(['usermacros' => true, 'lldmacros' => true]);
if ($expression_parser->parse($expression) != CParser::PARSE_SUCCESS) {
$error = $expression_parser->getError();
return false;
}
$expression_tree[] = getExpressionTree($expression_parser, 0, $expression_parser->getLength() - 1);
$next = [];
$letter_num = 0;
return buildExpressionHtmlTree($expression_tree, $next, $letter_num, 0, null, $type);
}
/**
* Builds expression HTML tree.
*
* @param array $expressionTree Output of getExpressionTree() function.
* @param array $next Parameter only for recursive call; should be empty array.
* @param int $letterNum Parameter only for recursive call; should be 0.
* @param int $level Parameter only for recursive call.
* @param string $operator Parameter only for recursive call.
* @param int $type Type can be either TRIGGER_EXPRESSION or TRIGGER_RECOVERY_EXPRESSION.
*
* @return array Array containing the trigger expression formula as the first element and an array describing the
* expression tree as the second.
*/
function buildExpressionHtmlTree(array $expressionTree, array &$next, &$letterNum, $level, $operator, $type) {
$treeList = [];
$outline = '';
end($expressionTree);
$lastKey = key($expressionTree);
foreach ($expressionTree as $key => $element) {
switch ($element['type']) {
case 'operator':
$next[$level] = ($key != $lastKey);
$expr = expressionLevelDraw($next, $level);
$expr[] = NBSP();
$expr[] = ($element['operator'] === 'and') ? _('And') : _('Or');
$levelDetails = [
'list' => $expr,
'id' => $element['id'],
'expression' => [
'value' => $element['expression']
]
];
$levelErrors = expressionHighLevelErrors($element['expression']);
if ($levelErrors) {
$levelDetails['expression']['levelErrors'] = $levelErrors;
}
$treeList[] = $levelDetails;
list($subOutline, $subTreeList) = buildExpressionHtmlTree($element['elements'], $next, $letterNum,
$level + 1, $element['operator'], $type
);
$treeList = array_merge($treeList, $subTreeList);
$outline .= ($level == 0) ? $subOutline : '('.$subOutline.')';
if ($operator !== null && $next[$level]) {
$outline .= ' '.$operator.' ';
}
break;
case 'expression':
$next[$level] = ($key != $lastKey);
$letter = num2letter($letterNum++);
$outline .= $letter;
if ($operator !== null && $next[$level]) {
$outline .= ' '.$operator.' ';
}
if (defined('NO_LINK_IN_TESTING')) {
$url = $element['expression'];
}
else {
if ($type == TRIGGER_EXPRESSION) {
$expressionId = 'expr_'.$element['id'];
}
else {
$expressionId = 'recovery_expr_'.$element['id'];
}
$url = (new CLinkAction($element['expression']))
->setId($expressionId)
->onClick('copy_expression(this.id, '.$type.');');
}
$expr = expressionLevelDraw($next, $level);
$expr[] = NBSP();
$expr[] = bold($letter);
$expr[] = NBSP();
$expr[] = $url;
$levelDetails = [
'list' => $expr,
'id' => $element['id'],
'expression' => [
'value' => $element['expression']
]
];
$levelErrors = expressionHighLevelErrors($element['expression']);
if ($levelErrors) {
$levelDetails['expression']['levelErrors'] = $levelErrors;
}
$treeList[] = $levelDetails;
break;
}
}
return [$outline, $treeList];
}
function expressionHighLevelErrors($expression) {
static $errors, $definedErrorPhrases;
if (!isset($errors)) {
$definedErrorPhrases = [
EXPRESSION_HOST_UNKNOWN => _('Unknown host, no such host present in system'),
EXPRESSION_HOST_ITEM_UNKNOWN => _('Unknown host item, no such item in selected host'),
EXPRESSION_NOT_A_MACRO_ERROR => _('Given expression is not a macro'),
EXPRESSION_FUNCTION_UNKNOWN => _('Incorrect function is used'),
EXPRESSION_UNSUPPORTED_VALUE_TYPE => _('Incorrect item value type')
];
$errors = [];
}
if (!isset($errors[$expression])) {
$errors[$expression] = [];
$expression_parser = new CExpressionParser(['usermacros' => true, 'lldmacros' => true]);
if ($expression_parser->parse($expression) == CParser::PARSE_SUCCESS) {
$tokens = $expression_parser->getResult()->getTokensOfTypes([
CExpressionParserResult::TOKEN_TYPE_MATH_FUNCTION,
CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION
]);
foreach ($tokens as $token) {
$info = get_item_function_info($token['match']);
if (!is_array($info) && isset($definedErrorPhrases[$info])) {
if (!isset($errors[$expression][$token['match']])) {
$errors[$expression][$token['match']] = $definedErrorPhrases[$info];
}
}
}
}
}
$ret = [];
if (!$errors[$expression]) {
return $ret;
}
$expression_parser = new CExpressionParser(['usermacros' => true, 'lldmacros' => true]);
if ($expression_parser->parse($expression) == CParser::PARSE_SUCCESS) {
$tokens = $expression_parser->getResult()->getTokensOfTypes([
CExpressionParserResult::TOKEN_TYPE_MATH_FUNCTION,
CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION
]);
foreach ($tokens as $token) {
if (isset($errors[$expression][$token['match']])) {
$ret[$token['match']] = $errors[$expression][$token['match']];
}
}
}
return $ret;
}
/**
* Draw level for trigger expression builder tree.
*
* @param array $next
* @param int $level
*
* @return array
*/
function expressionLevelDraw(array $next, $level) {
$expr = [];
for ($i = 1; $i <= $level; $i++) {
if ($i == $level) {
$class_name = $next[$i] ? ZBX_ICON_TREE_TOP_RIGHT_BOTTOM_SMALL : ZBX_ICON_TREE_TOP_RIGHT_SMALL;
}
else {
$class_name = $next[$i] ? ZBX_ICON_TREE_TOP_BOTTOM_SMALL : ZBX_STYLE_ICON_EMPTY_SMALL;
}
$expr[] = (new CSpan(''))->addClass($class_name);
}
return $expr;
}
/**
* Makes tree of expression elements
*
* Expression:
* "last(/host1/system.cpu.util[,iowait], 0) > 50 and last(/host2/system.cpu.util[,iowait], 0) > 50"
* Result:
* [
* [0] => [
* 'id' => '0_94',
* 'type' => 'operator',
* 'operator' => 'and',
* 'elements' => [
* [0] => [
* 'id' => '0_44',
* 'type' => 'expression',
* 'expression' => 'last(/host1/system.cpu.util[,iowait], 0) > 50'
* ],
* [1] => [
* 'id' => '50_94',
* 'type' => 'expression',
* 'expression' => 'last(/host2/system.cpu.util[,iowait], 0) > 50'
* ]
* ]
* ]
* ]
*
* @param CExpressionParser $expression_parser
* @param int $start
* @param int $end
*
* @return array
*/
function getExpressionTree(CExpressionParser $expression_parser, int $start, int $end) {
$tokens = array_column($expression_parser->getResult()->getTokens(), null, 'pos');
$expression = $expression_parser->getMatch();
$expressionTree = [];
foreach (['or', 'and'] as $operator) {
$operatorFound = false;
$lParentheses = -1;
$rParentheses = -1;
$expressions = [];
$openSymbolNum = $start;
for ($i = $start, $level = 0; $i <= $end; $i++) {
switch ($expression[$i]) {
case ' ':
case "\r":
case "\n":
case "\t":
if ($openSymbolNum == $i) {
$openSymbolNum++;
}
break;
case '(':
if ($level == 0) {
$lParentheses = $i;
}
$level++;
break;
case ')':
$level--;
if ($level == 0) {
$rParentheses = $i;
}
break;
default:
/*
* Once reached the end of a complete expression, parse the expression on the left side of the
* operator.
*/
if ($level == 0 && array_key_exists($i, $tokens)
&& $tokens[$i]['type'] == CExpressionParserResult::TOKEN_TYPE_OPERATOR
&& $tokens[$i]['match'] === $operator) {
// Find the last symbol of the expression before the operator.
$closeSymbolNum = $i - 1;
// Trim blank symbols after the expression.
while (strpos(CExpressionParser::WHITESPACES, $expression[$closeSymbolNum]) !== false) {
$closeSymbolNum--;
}
$expressions[] = getExpressionTree($expression_parser, $openSymbolNum, $closeSymbolNum);
$openSymbolNum = $i + $tokens[$i]['length'];
$operatorFound = true;
}
}
}
// Trim blank symbols in the end of the trigger expression.
$closeSymbolNum = $end;
while (strpos(CExpressionParser::WHITESPACES, $expression[$closeSymbolNum]) !== false) {
$closeSymbolNum--;
}
/*
* Once found a whole expression and parsed the expression on the left side of the operator, parse the
* expression on the right.
*/
if ($operatorFound) {
$expressions[] = getExpressionTree($expression_parser, $openSymbolNum, $closeSymbolNum);
// Trim blank symbols in the beginning of the trigger expression.
$openSymbolNum = $start;
while (strpos(CExpressionParser::WHITESPACES, $expression[$openSymbolNum]) !== false) {
$openSymbolNum++;
}
// Trim blank symbols in the end of the trigger expression.
$closeSymbolNum = $end;
while (strpos(CExpressionParser::WHITESPACES, $expression[$closeSymbolNum]) !== false) {
$closeSymbolNum--;
}
$expressionTree = [
'id' => $openSymbolNum.'_'.$closeSymbolNum,
'expression' => substr($expression, $openSymbolNum, $closeSymbolNum - $openSymbolNum + 1),
'type' => 'operator',
'operator' => $operator,
'elements' => $expressions
];
break;
}
// If finding both operators failed, it means there's only one expression return the result.
elseif ($operator === 'and') {
// Trim extra parentheses.
if ($openSymbolNum == $lParentheses && $closeSymbolNum == $rParentheses) {
$openSymbolNum++;
$closeSymbolNum--;
$expressionTree = getExpressionTree($expression_parser, $openSymbolNum, $closeSymbolNum);
}
// No extra parentheses remain, return the result.
else {
$expressionTree = [
'id' => $openSymbolNum.'_'.$closeSymbolNum,
'expression' => substr($expression, $openSymbolNum, $closeSymbolNum - $openSymbolNum + 1),
'type' => 'expression'
];
}
}
}
return $expressionTree;
}
/**
* Recreate an expression depending on action.
*
* Supported action values:
* - and - add an expression using "and";
* - or - add an expression using "or";
* - r - replace;
* - R - remove.
*
* @param string $expression
* @param string $expression_id Element identifier like "0_55".
* @param string $action Action to perform.
* @param string $new_expression Expression for AND, OR or replace actions.
* @param string $error [OUT] An error message.
*
* @return bool|string Returns new expression or false if expression is incorrect.
*/
function remakeExpression($expression, $expression_id, $action, $new_expression, string &$error = null) {
if ($expression === '') {
return false;
}
$expression_parser = new CExpressionParser(['usermacros' => true, 'lldmacros' => true]);
if ($action !== 'R' && $expression_parser->parse($new_expression) != CParser::PARSE_SUCCESS) {
$error = $expression_parser->getError();
return false;
}
if ($expression_parser->parse($expression) != CParser::PARSE_SUCCESS) {
$error = $expression_parser->getError();
return false;
}
$expression_tree[] = getExpressionTree($expression_parser, 0, $expression_parser->getLength() - 1);
if (rebuildExpressionTree($expression_tree, $expression_id, $action, $new_expression)) {
$expression = makeExpression($expression_tree);
}
return $expression;
}
/**
* Rebuild expression depending on action.
*
* Supported action values:
* - and - add an expression using "and";
* - or - add an expression using "or";
* - r - replace;
* - R - remove.
*
* Example:
* $expressionTree = array(
* [0] => array(
* 'id' => '0_94',
* 'type' => 'operator',
* 'operator' => 'and',
* 'elements' => array(
* [0] => array(
* 'id' => '0_44',
* 'type' => 'expression',
* 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50'
* ),
* [1] => array(
* 'id' => '50_94',
* 'type' => 'expression',
* 'expression' => '{host2:system.cpu.util[,iowait].last(0)} > 50'
* )
* )
* )
* )
* $action = 'R'
* $expressionId = '50_94'
*
* Result:
* $expressionTree = array(
* [0] => array(
* 'id' => '0_44',
* 'type' => 'expression',
* 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50'
* )
* )
*
* @param array $expressionTree
* @param string $expressionId element identifier like "0_55"
* @param string $action action to perform
* @param string $newExpression expression for AND, OR or replace actions
* @param string $operator parameter only for recursive call
*
* @return bool returns true if element is found, false - otherwise
*/
function rebuildExpressionTree(array &$expressionTree, $expressionId, $action, $newExpression, $operator = null) {
foreach ($expressionTree as $key => $expression) {
if ($expressionId == $expressionTree[$key]['id']) {
switch ($action) {
case 'and':
case 'or':
switch ($expressionTree[$key]['type']) {
case 'operator':
if ($expressionTree[$key]['operator'] == $action) {
$expressionTree[$key]['elements'][] = [
'expression' => $newExpression,
'type' => 'expression'
];
}
else {
$element = [
'type' => 'operator',
'operator' => $action,
'elements' => [
$expressionTree[$key],
[
'expression' => $newExpression,
'type' => 'expression'
]
]
];
$expressionTree[$key] = $element;
}
break;
case 'expression':
if (!$operator || $operator != $action) {
$element = [
'type' => 'operator',
'operator' => $action,
'elements' => [
$expressionTree[$key],
[
'expression' => $newExpression,
'type' => 'expression'
]
]
];
$expressionTree[$key] = $element;
}
else {
$expressionTree[] = [
'expression' => $newExpression,
'type' => 'expression'
];
}
break;
}
break;
// replace
case 'r':
$expressionTree[$key]['expression'] = $newExpression;
if ($expressionTree[$key]['type'] == 'operator') {
$expressionTree[$key]['type'] = 'expression';
unset($expressionTree[$key]['operator'], $expressionTree[$key]['elements']);
}
break;
// remove
case 'R':
unset($expressionTree[$key]);
break;
}
return true;
}
if ($expressionTree[$key]['type'] == 'operator') {
if (rebuildExpressionTree($expressionTree[$key]['elements'], $expressionId, $action, $newExpression,
$expressionTree[$key]['operator'])) {
return true;
}
}
}
return false;
}
/**
* Makes expression by expression tree
*
* Example:
* $expressionTree = array(
* [0] => array(
* 'type' => 'operator',
* 'operator' => 'and',
* 'elements' => array(
* [0] => array(
* 'type' => 'expression',
* 'expression' => '{host1:system.cpu.util[,iowait].last(0)} > 50'
* ),
* [1] => array(
* 'type' => 'expression',
* 'expression' => '{host2:system.cpu.util[,iowait].last(0)} > 50'
* )
* )
* )
* )
*
* Result:
* "{host1:system.cpu.util[,iowait].last(0)} > 50 and {host2:system.cpu.util[,iowait].last(0)} > 50"
*
* @param array $expressionTree
* @param int $level parameter only for recursive call
* @param string $operator parameter only for recursive call
*
* @return string
*/
function makeExpression(array $expressionTree, $level = 0, $operator = null) {
$expression = '';
end($expressionTree);
$lastKey = key($expressionTree);
foreach ($expressionTree as $key => $element) {
switch ($element['type']) {
case 'operator':
$subExpression = makeExpression($element['elements'], $level + 1, $element['operator']);
$expression .= ($level == 0) ? $subExpression : '('.$subExpression.')';
break;
case 'expression':
$expression .= $element['expression'];
break;
}
if ($operator !== null && $key != $lastKey) {
$expression .= ' '.$operator.' ';
}
}
return $expression;
}
function get_item_function_info(string $expr) {
$rule_float = ['value_type' => _('Numeric (float)'), 'values' => null];
$rule_int = ['value_type' => _('Numeric (integer)'), 'values' => null];
$rule_str = ['value_type' => _('String'), 'values' => null];
$rule_any = ['value_type' => _('Any'), 'values' => null];
$rule_0or1 = ['value_type' => _('0 or 1'), 'values' => [0 => 0, 1 => 1]];
$rules = [
// Every nested array should have two elements: label, values.
'integer' => [
ITEM_VALUE_TYPE_UINT64 => $rule_int
],
'numeric' => [
ITEM_VALUE_TYPE_UINT64 => $rule_int,
ITEM_VALUE_TYPE_FLOAT => $rule_float
],
'numeric_as_float' => [
ITEM_VALUE_TYPE_UINT64 => $rule_float,
ITEM_VALUE_TYPE_FLOAT => $rule_float
],
'numeric_as_uint' => [
ITEM_VALUE_TYPE_UINT64 => $rule_int,
ITEM_VALUE_TYPE_FLOAT => $rule_int
],
'numeric_as_0or1' => [
ITEM_VALUE_TYPE_UINT64 => $rule_0or1,
ITEM_VALUE_TYPE_FLOAT => $rule_0or1
],
'string_as_0or1' => [
ITEM_VALUE_TYPE_TEXT => $rule_0or1,
ITEM_VALUE_TYPE_STR => $rule_0or1,
ITEM_VALUE_TYPE_LOG => $rule_0or1
],
'string_as_uint' => [
ITEM_VALUE_TYPE_TEXT => $rule_int,
ITEM_VALUE_TYPE_STR => $rule_int,
ITEM_VALUE_TYPE_LOG => $rule_int
],
'string' => [
ITEM_VALUE_TYPE_TEXT => $rule_str,
ITEM_VALUE_TYPE_STR => $rule_str,
ITEM_VALUE_TYPE_LOG => $rule_str
],
'log_as_uint' => [
ITEM_VALUE_TYPE_LOG => $rule_int
],
'log_as_0or1' => [
ITEM_VALUE_TYPE_LOG => $rule_0or1
]
];
$hist_functions = [
'avg' => $rules['numeric_as_float'],
'baselinedev' => $rules['numeric_as_float'],
'baselinewma' => $rules['numeric_as_float'],
'change' => $rules['numeric'] + $rules['string_as_0or1'],
'count' => $rules['numeric_as_uint'] + $rules['string_as_uint'],
'changecount' => $rules['numeric_as_uint'] + $rules['string_as_uint'],
'countunique' => $rules['numeric_as_uint'] + $rules['string_as_uint'],
'find' => $rules['numeric_as_0or1'] + $rules['string_as_0or1'],
'first' => $rules['numeric'] + $rules['string'],
'forecast' => $rules['numeric_as_float'],
'fuzzytime' => $rules['numeric_as_0or1'],
'kurtosis' => $rules['numeric_as_float'],
'last' => $rules['numeric'] + $rules['string'],
'logeventid' => $rules['log_as_0or1'],
'logseverity' => $rules['log_as_uint'],
'logsource' => $rules['log_as_0or1'],
'mad' => $rules['numeric_as_float'],
'max' => $rules['numeric'],
'min' => $rules['numeric'],
'monodec' => $rules['numeric_as_uint'],
'monoinc' => $rules['numeric_as_uint'],
'nodata' => $rules['numeric_as_0or1'] + $rules['string_as_0or1'],
'percentile' => $rules['numeric'],
'rate' => $rules['numeric'],
'skewness' => $rules['numeric_as_float'],
'stddevpop' => $rules['numeric_as_float'],
'stddevsamp' => $rules['numeric_as_float'],
'sum' => $rules['numeric'],
'sumofsquares' => $rules['numeric_as_float'],
'timeleft' => $rules['numeric_as_float'],
'trendavg' => $rules['numeric'],
'trendcount' => $rules['numeric'],
'trendmax' => $rules['numeric'],
'trendmin' => $rules['numeric'],
'trendstl' => $rules['numeric'],
'trendsum' => $rules['numeric'],
'varpop' => $rules['numeric_as_float'],
'varsamp' => $rules['numeric_as_float']
];
$math_functions = [
'abs' => ['any' => $rule_float],
'acos' => ['any' => $rule_float],
'ascii' => ['any' => $rule_int],
'asin' => ['any' => $rule_float],
'atan' => ['any' => $rule_float],
'atan2' => ['any' => $rule_float],
'avg' => ['any' => $rule_float],
'between' => ['any' => $rule_0or1],
'bitand' => ['any' => $rule_int],
'bitlength' => ['any' => $rule_int],
'bitlshift' => ['any' => $rule_int],
'bitnot' => ['any' => $rule_int],
'bitor' => ['any' => $rule_int],
'bitrshift' => ['any' => $rule_int],
'bitxor' => ['any' => $rule_int],
'bytelength' => ['any' => $rule_int],
'cbrt' => ['any' => $rule_float],
'ceil' => ['any' => $rule_int],
'char' => ['any' => $rule_str],
'concat' => ['any' => $rule_str],
'cos' => ['any' => $rule_float],
'cosh' => ['any' => $rule_float],
'cot' => ['any' => $rule_float],
'date' => [
'any' => ['value_type' => 'YYYYMMDD', 'values' => null]
],
'dayofmonth' => [
'any' => ['value_type' => '1-31', 'values' => null]
],
'dayofweek' => [
'any' => ['value_type' => '1-7', 'values' => [1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7]]
],
'degrees' => ['any' => $rule_float],
'e' => ['any' => $rule_float],
'exp' => ['any' => $rule_float],
'expm1' => ['any' => $rule_float],
'floor' => ['any' => $rule_int],
'in' => ['any' => $rule_0or1],
'insert' => ['any' => $rule_str],
'left' => ['any' => $rule_str],
'length' => ['any' => $rule_int],
'log' => ['any' => $rule_float],
'log10' => ['any' => $rule_float],
'ltrim' => ['any' => $rule_str],
'max' => ['any' => $rule_float],
'mid' => ['any' => $rule_str],
'min' => ['any' => $rule_float],
'mod' => ['any' => $rule_float],
'now' => ['any' => $rule_int],
'pi' => ['any' => $rule_float],
'power' => ['any' => $rule_float],
'radians' => ['any' => $rule_float],
'rand' => ['any' => $rule_int],
'repeat' => ['any' => $rule_str],
'replace' => ['any' => $rule_str],
'right' => ['any' => $rule_str],
'round' => ['any' => $rule_float],
'rtrim' => ['any' => $rule_str],
'signum' => ['any' => $rule_int],
'sin' => ['any' => $rule_float],
'sinh' => ['any' => $rule_float],
'sqrt' => ['any' => $rule_float],
'sum' => ['any' => $rule_float],
'tan' => ['any' => $rule_float],
'time' => [
'any' => ['value_type' => 'HHMMSS', 'values' => null]
],
'trim' => ['any' => $rule_str],
'truncate' => ['any' => $rule_float]
];
$expression_parser = new CExpressionParser(['usermacros' => true, 'lldmacros' => true]);
$expression_parser->parse($expr);
$token = $expression_parser->getResult()->getTokens()[0];
switch ($token['type']) {
case CExpressionParserResult::TOKEN_TYPE_MACRO:
$result = $rule_0or1;
break;
case CExpressionParserResult::TOKEN_TYPE_USER_MACRO:
case CExpressionParserResult::TOKEN_TYPE_LLD_MACRO:
$result = $rule_any;
break;
case CExpressionParserResult::TOKEN_TYPE_HIST_FUNCTION:
if (!array_key_exists($token['data']['function'], $hist_functions)) {
$result = EXPRESSION_FUNCTION_UNKNOWN;
break;
}
$hosts = API::Host()->get([
'output' => ['hostid'],
'filter' => [
'host' => $token['data']['parameters'][0]['data']['host']
],
'templated_hosts' => true
]);
if (!$hosts) {
$result = EXPRESSION_HOST_UNKNOWN;
break;
}
$items = API::Item()->get([
'output' => ['value_type'],
'hostids' => $hosts[0]['hostid'],
'filter' => [
'key_' => $token['data']['parameters'][0]['data']['item']
],
'webitems' => true
]);
if (!$items) {
$items = API::ItemPrototype()->get([
'output' => ['value_type'],
'hostids' => $hosts[0]['hostid'],
'filter' => [
'key_' => $token['data']['parameters'][0]['data']['item']
]
]);
}
if (!$items) {
$result = EXPRESSION_HOST_ITEM_UNKNOWN;
break;
}
$hist_function = $hist_functions[$token['data']['function']];
$value_type = $items[0]['value_type'];
if (array_key_exists('any', $hist_function)) {
$value_type = 'any';
}
elseif (!array_key_exists($value_type, $hist_function)) {
$result = EXPRESSION_UNSUPPORTED_VALUE_TYPE;
break;
}
$result = $hist_function[$value_type];
break;
case CExpressionParserResult::TOKEN_TYPE_MATH_FUNCTION:
if (!array_key_exists($token['data']['function'], $math_functions)) {
$result = EXPRESSION_FUNCTION_UNKNOWN;
break;
}
$result = $math_functions[$token['data']['function']]['any'];
break;
default:
$result = EXPRESSION_NOT_A_MACRO_ERROR;
}
return $result;
}
/**
* Returns the text indicating the trigger's status and state. If the $state parameter is not given, only the status of
* the trigger will be taken into account.
*
* @param int $status
* @param int $state
*
* @return string
*/
function triggerIndicator($status, $state = null) {
if ($status == TRIGGER_STATUS_ENABLED) {
return ($state == TRIGGER_STATE_UNKNOWN) ? _('Unknown') : _('Enabled');
}
return _('Disabled');
}
/**
* Returns the CSS class for the trigger's status and state indicator. If the $state parameter is not given, only the
* status of the trigger will be taken into account.
*
* @param int $status
* @param int $state
*
* @return string
*/
function triggerIndicatorStyle($status, $state = null) {
if ($status == TRIGGER_STATUS_ENABLED) {
return ($state == TRIGGER_STATE_UNKNOWN) ?
ZBX_STYLE_GREY :
ZBX_STYLE_GREEN;
}
return ZBX_STYLE_RED;
}
/**
* Orders triggers by both status and state. Triggers are sorted in the following order: enabled, disabled, unknown.
*
* Keep in sync with orderItemsByStatus().
*
* @param array $triggers
* @param string $sortorder
*/
function orderTriggersByStatus(array &$triggers, $sortorder = ZBX_SORT_UP) {
$sort = [];
foreach ($triggers as $key => $trigger) {
if ($trigger['status'] == TRIGGER_STATUS_ENABLED) {
$sort[$key] = ($trigger['state'] == TRIGGER_STATE_UNKNOWN) ? 2 : 0;
}
else {
$sort[$key] = 1;
}
}
if ($sortorder == ZBX_SORT_UP) {
asort($sort);
}
else {
arsort($sort);
}
$sortedTriggers = [];
foreach ($sort as $key => $val) {
$sortedTriggers[$key] = $triggers[$key];
}
$triggers = $sortedTriggers;
}
/**
* Create the list of hosts for each trigger.
*
* @param array $triggers
* @param string $triggers[]['triggerid']
* @param array $triggers[]['hosts']
* @param string $triggers[]['hosts'][]['hostid']
*
* @return array
*/
function getTriggersHostsList(array $triggers) {
$hostids = [];
foreach ($triggers as $trigger) {
foreach ($trigger['hosts'] as $host) {
$hostids[$host['hostid']] = true;
}
}
$db_hosts = $hostids
? API::Host()->get([
'output' => ['hostid', 'name', 'maintenanceid', 'maintenance_status', 'maintenance_type'],
'hostids' => array_keys($hostids),
'preservekeys' => true
])
: [];
$triggers_hosts = [];
foreach ($triggers as $trigger) {
$triggers_hosts[$trigger['triggerid']] = [];
foreach ($trigger['hosts'] as $host) {
if (!array_key_exists($host['hostid'], $db_hosts)) {
continue;
}
$triggers_hosts[$trigger['triggerid']][] = $db_hosts[$host['hostid']];
}
order_result($triggers_hosts[$trigger['triggerid']], 'name');
}
return $triggers_hosts;
}
/**
* Make the list of hosts for each trigger.
*
* @param array $triggers_hosts
* @param string $triggers_hosts[<triggerid>][]['hostid']
* @param string $triggers_hosts[<triggerid>][]['name']
* @param string $triggers_hosts[<triggerid>][]['maintenanceid']
* @param int $triggers_hosts[<triggerid>][]['maintenance_status']
* @param int $triggers_hosts[<triggerid>][]['maintenance_type']
*
* @return array
*/
function makeTriggersHostsList(array $triggers_hosts) {
$db_maintenances = [];
$hostids = [];
$maintenanceids = [];
foreach ($triggers_hosts as $hosts) {
foreach ($hosts as $host) {
$hostids[$host['hostid']] = true;
if ($host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) {
$maintenanceids[$host['maintenanceid']] = true;
}
}
}
if ($hostids) {
if ($maintenanceids) {
$db_maintenances = API::Maintenance()->get([
'output' => ['name', 'description'],
'maintenanceids' => array_keys($maintenanceids),
'preservekeys' => true
]);
}
}
foreach ($triggers_hosts as &$hosts) {
$trigger_hosts = [];
foreach ($hosts as $host) {
$host_name = (new CLinkAction($host['name']))
->setMenuPopup(CMenuPopupHelper::getHost($host['hostid']))
->addClass(ZBX_STYLE_WORDBREAK);
if ($host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) {
if (array_key_exists($host['maintenanceid'], $db_maintenances)) {
$maintenance = $db_maintenances[$host['maintenanceid']];
$maintenance_icon = makeMaintenanceIcon($host['maintenance_type'], $maintenance['name'],
$maintenance['description']
);
}
else {
$maintenance_icon = makeMaintenanceIcon($host['maintenance_type'], _('Inaccessible maintenance'),
''
);
}
$host_name = (new CSpan([$host_name, $maintenance_icon]))->addClass(ZBX_STYLE_REL_CONTAINER);
}
if ($trigger_hosts) {
$trigger_hosts[] = (new CSpan(','))->addClass('separator');
}
$trigger_hosts[] = $host_name;
}
$hosts = $trigger_hosts;
}
unset($hosts);
return $triggers_hosts;
}
/**
* Get parent templates for each given trigger.
*
* @param $array $triggers An array of triggers.
* @param string $triggers[]['triggerid'] ID of a trigger.
* @param string $triggers[]['templateid'] ID of parent template trigger.
* @param int $flag Origin of the trigger (ZBX_FLAG_DISCOVERY_NORMAL or
* ZBX_FLAG_DISCOVERY_PROTOTYPE).
*
* @return array
*/
function getTriggerParentTemplates(array $triggers, $flag) {
$parent_triggerids = [];
$data = [
'links' => [],
'templates' => []
];
foreach ($triggers as $trigger) {
if ($trigger['templateid'] != 0) {
$parent_triggerids[$trigger['templateid']] = true;
$data['links'][$trigger['triggerid']] = ['triggerid' => $trigger['templateid']];
}
}
if (!$parent_triggerids) {
return $data;
}
$all_parent_triggerids = [];
$hostids = [];
if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
$lld_ruleids = [];
}
do {
if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
$db_triggers = API::TriggerPrototype()->get([
'output' => ['triggerid', 'templateid'],
'selectHosts' => ['hostid'],
'selectDiscoveryRule' => ['itemid'],
'triggerids' => array_keys($parent_triggerids)
]);
}
// ZBX_FLAG_DISCOVERY_NORMAL
else {
$db_triggers = API::Trigger()->get([
'output' => ['triggerid', 'templateid'],
'selectHosts' => ['hostid'],
'triggerids' => array_keys($parent_triggerids)
]);
}
$all_parent_triggerids += $parent_triggerids;
$parent_triggerids = [];
foreach ($db_triggers as $db_trigger) {
foreach ($db_trigger['hosts'] as $host) {
$data['templates'][$host['hostid']] = [];
$hostids[$db_trigger['triggerid']][] = $host['hostid'];
}
if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
$lld_ruleids[$db_trigger['triggerid']] = $db_trigger['discoveryRule']['itemid'];
}
if ($db_trigger['templateid'] != 0) {
if (!array_key_exists($db_trigger['templateid'], $all_parent_triggerids)) {
$parent_triggerids[$db_trigger['templateid']] = true;
}
$data['links'][$db_trigger['triggerid']] = ['triggerid' => $db_trigger['templateid']];
}
}
}
while ($parent_triggerids);
foreach ($data['links'] as &$parent_trigger) {
$parent_trigger['hostids'] = array_key_exists($parent_trigger['triggerid'], $hostids)
? $hostids[$parent_trigger['triggerid']]
: [0];
if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
$parent_trigger['lld_ruleid'] = array_key_exists($parent_trigger['triggerid'], $lld_ruleids)
? $lld_ruleids[$parent_trigger['triggerid']]
: 0;
}
}
unset($parent_trigger);
$db_templates = $data['templates']
? API::Template()->get([
'output' => ['name'],
'templateids' => array_keys($data['templates']),
'preservekeys' => true
])
: [];
$rw_templates = $db_templates
? API::Template()->get([
'output' => [],
'templateids' => array_keys($db_templates),
'editable' => true,
'preservekeys' => true
])
: [];
$data['templates'][0] = [];
foreach ($data['templates'] as $hostid => &$template) {
$template = array_key_exists($hostid, $db_templates)
? [
'hostid' => $hostid,
'name' => $db_templates[$hostid]['name'],
'permission' => array_key_exists($hostid, $rw_templates) ? PERM_READ_WRITE : PERM_READ
]
: [
'hostid' => $hostid,
'name' => _('Inaccessible template'),
'permission' => PERM_DENY
];
}
unset($template);
return $data;
}
/**
* Returns a template prefix for selected trigger.
*
* @param string $triggerid
* @param array $parent_templates The list of the templates, prepared by getTriggerParentTemplates() function.
* @param int $flag Origin of the trigger (ZBX_FLAG_DISCOVERY_NORMAL or ZBX_FLAG_DISCOVERY_PROTOTYPE).
* @param bool $provide_links If this parameter is false, prefix will not contain links.
*
* @return array|null
*/
function makeTriggerTemplatePrefix($triggerid, array $parent_templates, $flag, bool $provide_links) {
if (!array_key_exists($triggerid, $parent_templates['links'])) {
return null;
}
while (array_key_exists($parent_templates['links'][$triggerid]['triggerid'], $parent_templates['links'])) {
$triggerid = $parent_templates['links'][$triggerid]['triggerid'];
}
$templates = [];
foreach ($parent_templates['links'][$triggerid]['hostids'] as $hostid) {
$templates[] = $parent_templates['templates'][$hostid];
}
CArrayHelper::sort($templates, ['name']);
$list = [];
foreach ($templates as $template) {
if ($provide_links && $template['permission'] == PERM_READ_WRITE) {
if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
$url = (new CUrl('trigger_prototypes.php'))
->setArgument('parent_discoveryid', $parent_templates['links'][$triggerid]['lld_ruleid'])
->setArgument('context', 'template');
}
// ZBX_FLAG_DISCOVERY_NORMAL
else {
$url = (new CUrl('triggers.php'))
->setArgument('filter_hostids', [$template['hostid']])
->setArgument('filter_set', 1)
->setArgument('context', 'template');
}
$name = (new CLink($template['name'], $url))->addClass(ZBX_STYLE_LINK_ALT);
}
else {
$name = new CSpan($template['name']);
}
$list[] = $name->addClass(ZBX_STYLE_GREY);
$list[] = ', ';
}
array_pop($list);
$list[] = NAME_DELIMITER;
return $list;
}
/**
* Returns a list of trigger templates.
*
* @param string $triggerid
* @param array $parent_templates The list of the templates, prepared by getTriggerParentTemplates() function.
* @param int $flag Origin of the trigger (ZBX_FLAG_DISCOVERY_NORMAL or ZBX_FLAG_DISCOVERY_PROTOTYPE).
* @param bool $provide_links If this parameter is false, prefix will not contain links.
*
* @return array
*/
function makeTriggerTemplatesHtml($triggerid, array $parent_templates, $flag, bool $provide_links) {
$list = [];
while (array_key_exists($triggerid, $parent_templates['links'])) {
$list_item = [];
$templates = [];
foreach ($parent_templates['links'][$triggerid]['hostids'] as $hostid) {
$templates[] = $parent_templates['templates'][$hostid];
}
$show_parentheses = (count($templates) > 1 && $list);
if ($show_parentheses) {
CArrayHelper::sort($templates, ['name']);
$list_item[] = '(';
}
foreach ($templates as $template) {
if ($provide_links && $template['permission'] == PERM_READ_WRITE) {
if ($flag == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
$url = (new CUrl('trigger_prototypes.php'))
->setArgument('form', 'update')
->setArgument('triggerid', $parent_templates['links'][$triggerid]['triggerid'])
->setArgument('parent_discoveryid', $parent_templates['links'][$triggerid]['lld_ruleid'])
->setArgument('context', 'template');
}
// ZBX_FLAG_DISCOVERY_NORMAL
else {
$url = (new CUrl('triggers.php'))
->setArgument('form', 'update')
->setArgument('triggerid', $parent_templates['links'][$triggerid]['triggerid'])
->setArgument('hostid', $template['hostid'])
->setArgument('context', 'template');
}
$name = new CLink($template['name'], $url);
}
else {
$name = (new CSpan($template['name']))->addClass(ZBX_STYLE_GREY);
}
$list_item[] = $name;
$list_item[] = ', ';
}
array_pop($list_item);
if ($show_parentheses) {
$list_item[] = ')';
}
array_unshift($list, $list_item, [NBSP(), RARR(), NBSP()]);
$triggerid = $parent_templates['links'][$triggerid]['triggerid'];
}
if ($list) {
array_pop($list);
}
return $list;
}
/**
* Check if user has read permissions for triggers.
*
* @param $triggerids
*
* @return bool
*/
function isReadableTriggers(array $triggerids) {
return count($triggerids) == API::Trigger()->get([
'triggerids' => $triggerids,
'countOutput' => true
]);
}
/**
* Returns a list of the trigger dependencies.
*
* @param array $triggers
* @param array $triggers[<triggerid>]['dependencies']
* @param string $triggers[<triggerid>]['dependencies'][]['triggerid']
*
* @return array
*/
function getTriggerDependencies(array $triggers) {
$triggerids = [];
$triggerids_up = [];
$triggerids_down = [];
// "Depends on" triggers.
foreach ($triggers as $triggerid => $trigger) {
foreach ($trigger['dependencies'] as $dependency) {
$triggerids[$dependency['triggerid']] = true;
$triggerids_up[$triggerid][] = $dependency['triggerid'];
}
}
// "Dependent" triggers.
$db_trigger_depends = DBselect(
'SELECT triggerid_down,triggerid_up'.
' FROM trigger_depends'.
' WHERE '.dbConditionInt('triggerid_up', array_keys($triggers))
);
while ($row = DBfetch($db_trigger_depends)) {
$triggerids[$row['triggerid_down']] = true;
$triggerids_down[$row['triggerid_up']][] = $row['triggerid_down'];
}
$dependencies = [];
if (!$triggerids) {
return $dependencies;
}
$db_triggers = API::Trigger()->get([
'output' => ['expression', 'description'],
'triggerids' => array_keys($triggerids),
'preservekeys' => true
]);
$db_triggers = CMacrosResolverHelper::resolveTriggerNames($db_triggers);
foreach ($triggerids_up as $triggerid_up => $triggerids) {
foreach ($triggerids as $triggerid) {
$dependencies[$triggerid_up]['down'][] = array_key_exists($triggerid, $db_triggers)
? $db_triggers[$triggerid]['description']
: _('Inaccessible trigger');
}
}
foreach ($triggerids_down as $triggerid_down => $triggerids) {
foreach ($triggerids as $triggerid) {
$dependencies[$triggerid_down]['up'][] = array_key_exists($triggerid, $db_triggers)
? $db_triggers[$triggerid]['description']
: _('Inaccessible trigger');
}
}
return $dependencies;
}
/**
* Returns icons with tooltips for triggers with dependencies.
*
* @param array $dependencies
* array $dependencies['up'] (optional) The list of "Dependent" triggers.
* array $dependencies['down'] (optional) The list of "Depends on" triggers.
* @param bool $freeze_on_click
*
* @return array
*/
function makeTriggerDependencies(array $dependencies, bool $freeze_on_click = true): array {
$result = [];
foreach (['down', 'up'] as $type) {
if (array_key_exists($type, $dependencies)) {
$table = (new CTableInfo())
->setAttribute('style', 'max-width: '.ZBX_TEXTAREA_STANDARD_WIDTH.'px;')
->setHeader([$type === 'down' ? _('Depends on') : _('Dependent')]);
foreach ($dependencies[$type] as $description) {
$table->addRow($description);
}
$result[] = (new CButtonIcon($type === 'down' ? ZBX_ICON_BULLET_ALT_DOWN : ZBX_ICON_BULLET_ALT_UP))
->addClass(ZBX_STYLE_COLOR_ICON)
->setHint($table, '', $freeze_on_click);
}
}
return $result;
}
/**
* Return list of functions that can be used without /host/key reference.
*
* @return array
*/
function getStandaloneFunctions(): array {
return ['date', 'dayofmonth', 'dayofweek', 'time', 'now'];
}
/**
* Returns a list of functions that return a constant or random number.
*
* @return array
*/
function getFunctionsConstants(): array {
return ['e', 'pi', 'rand'];
}