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.

1502 lines
44 KiB

<?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.
**/
function sysmap_element_types($type = null) {
$types = [
SYSMAP_ELEMENT_TYPE_HOST => _('Host'),
SYSMAP_ELEMENT_TYPE_HOST_GROUP => _('Host group'),
SYSMAP_ELEMENT_TYPE_TRIGGER => _('Trigger'),
SYSMAP_ELEMENT_TYPE_MAP => _('Map'),
SYSMAP_ELEMENT_TYPE_IMAGE => _('Image')
];
if (is_null($type)) {
natsort($types);
return $types;
}
elseif (isset($types[$type])) {
return $types[$type];
}
else {
return _('Unknown');
}
}
function sysmapElementLabel($label = null) {
$labels = [
MAP_LABEL_TYPE_LABEL => _('Label'),
MAP_LABEL_TYPE_IP => _('IP address'),
MAP_LABEL_TYPE_NAME => _('Element name'),
MAP_LABEL_TYPE_STATUS => _('Status only'),
MAP_LABEL_TYPE_NOTHING => _('Nothing'),
MAP_LABEL_TYPE_CUSTOM => _('Custom label')
];
if (is_null($label)) {
return $labels;
}
elseif (isset($labels[$label])) {
return $labels[$label];
}
else {
return false;
}
}
/**
* Get actions (data for popup menu) for map elements.
*
* @param array $sysmap
* @param array $sysmap['selements']
* @param array $options Options used to retrieve actions.
* @param int $options['severity_min'] Minimal severity used.
* @param int $options['unique_id']
*
* @return array
*/
function getActionsBySysmap(array $sysmap, array $options = []) {
$actions = [];
$severity_min = array_key_exists('severity_min', $options)
? $options['severity_min']
: TRIGGER_SEVERITY_NOT_CLASSIFIED;
foreach ($sysmap['selements'] as $selementid => $elem) {
if ($elem['permission'] < PERM_READ) {
continue;
}
if (array_key_exists('unique_id', $options)) {
$elem['unique_id'] = $options['unique_id'];
}
$hostid = ($elem['elementtype_orig'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
&& $elem['elementsubtype_orig'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS)
? $elem['elements'][0]['hostid']
: 0;
$map = CMenuPopupHelper::getMapElement($sysmap['sysmapid'], $elem, $severity_min, $hostid);
$actions[$selementid] = json_encode($map);
}
return $actions;
}
function get_png_by_selement($info) {
$image = get_image_by_imageid($info['iconid']);
return $image['image'] ? imagecreatefromstring($image['image']) : get_default_image();
}
function get_map_elements($db_element, &$elements) {
switch ($db_element['elementtype']) {
case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
$elements['hosts_groups'][] = $db_element['elements'][0]['groupid'];
break;
case SYSMAP_ELEMENT_TYPE_HOST:
$elements['hosts'][] = $db_element['elements'][0]['hostid'];
break;
case SYSMAP_ELEMENT_TYPE_TRIGGER:
foreach ($db_element['elements'] as $db_element) {
$elements['triggers'][] = $db_element['triggerid'];
}
break;
case SYSMAP_ELEMENT_TYPE_MAP:
$map = API::Map()->get([
'output' => [],
'selectSelements' => ['selementid', 'elements', 'elementtype'],
'sysmapids' => $db_element['elements'][0]['sysmapid'],
'nopermissions' => true
]);
if ($map) {
$map = reset($map);
foreach ($map['selements'] as $db_mapelement) {
get_map_elements($db_mapelement, $elements);
}
}
break;
}
}
/**
* Adds names to elements. Adds expression for SYSMAP_ELEMENT_TYPE_TRIGGER elements.
*
* @param array $selements
* @param array $selements[]['elements']
* @param int $selements[]['elementtype']
* @param int $selements[]['iconid_off']
* @param int $selements[]['permission']
*/
function addElementNames(array &$selements) {
$hostids = [];
$triggerids = [];
$sysmapids = [];
$groupids = [];
$imageids = [];
foreach ($selements as $selement) {
if ($selement['permission'] < PERM_READ) {
continue;
}
switch ($selement['elementtype']) {
case SYSMAP_ELEMENT_TYPE_HOST:
$hostids[$selement['elements'][0]['hostid']] = $selement['elements'][0]['hostid'];
break;
case SYSMAP_ELEMENT_TYPE_MAP:
$sysmapids[$selement['elements'][0]['sysmapid']] = $selement['elements'][0]['sysmapid'];
break;
case SYSMAP_ELEMENT_TYPE_TRIGGER:
foreach ($selement['elements'] as $element) {
$triggerids[$element['triggerid']] = $element['triggerid'];
}
break;
case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
$groupids[$selement['elements'][0]['groupid']] = $selement['elements'][0]['groupid'];
break;
case SYSMAP_ELEMENT_TYPE_IMAGE:
$imageids[$selement['iconid_off']] = $selement['iconid_off'];
break;
}
}
$hosts = $hostids
? API::Host()->get([
'output' => ['name'],
'hostids' => $hostids,
'preservekeys' => true
])
: [];
$maps = $sysmapids
? API::Map()->get([
'output' => ['name'],
'sysmapids' => $sysmapids,
'preservekeys' => true
])
: [];
$triggers = $triggerids
? API::Trigger()->get([
'output' => ['description', 'expression', 'priority'],
'selectHosts' => ['name'],
'triggerids' => $triggerids,
'preservekeys' => true
])
: [];
$triggers = CMacrosResolverHelper::resolveTriggerNames($triggers);
$groups = $groupids
? API::HostGroup()->get([
'output' => ['name'],
'groupids' => $groupids,
'preservekeys' => true
])
: [];
$images = $imageids
? API::Image()->get([
'output' => ['name'],
'imageids' => $imageids,
'preservekeys' => true
])
: [];
foreach ($selements as $snum => &$selement) {
if ($selement['permission'] < PERM_READ) {
continue;
}
switch ($selement['elementtype']) {
case SYSMAP_ELEMENT_TYPE_HOST:
$selements[$snum]['elements'][0]['elementName'] = $hosts[$selement['elements'][0]['hostid']]['name'];
break;
case SYSMAP_ELEMENT_TYPE_MAP:
$selements[$snum]['elements'][0]['elementName'] = $maps[$selement['elements'][0]['sysmapid']]['name'];
break;
case SYSMAP_ELEMENT_TYPE_TRIGGER:
foreach ($selement['elements'] as $enum => &$element) {
if (array_key_exists($element['triggerid'], $triggers)) {
$trigger = $triggers[$element['triggerid']];
$element['elementName'] = $trigger['hosts'][0]['name'].NAME_DELIMITER.$trigger['description'];
$element['priority'] = $trigger['priority'];
}
else {
unset($selement['elements'][$enum]);
}
}
unset($element);
$selement['elements'] = array_values($selement['elements']);
break;
case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
$selements[$snum]['elements'][0]['elementName'] = $groups[$selement['elements'][0]['groupid']]['name'];
break;
case SYSMAP_ELEMENT_TYPE_IMAGE:
if (array_key_exists($selement['iconid_off'], $images)) {
$selements[$snum]['elements'][0]['elementName'] = $images[$selement['iconid_off']]['name'];
}
break;
}
}
unset($selement);
}
/**
* Returns selement icon rendering parameters.
*
* @param array $i
* @param int $i['elementtype'] Element type. Possible values:
* SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP,
* SYSMAP_ELEMENT_TYPE_TRIGGER, SYSMAP_ELEMENT_TYPE_HOST_GROUP,
* SYSMAP_ELEMENT_TYPE_IMAGE.
* @param int $i['disabled'] The number of disabled hosts.
* @param int $i['maintenance'] The number of hosts in maintenance.
* @param int $i['problem'] The number of problems.
* @param int $i['problem_unack'] The number of unacknowledged problems.
* @param int $i['iconid_off'] Icon ID for element without problems.
* @param int $i['iconid_on'] Icon ID for element with problems.
* @param int $i['iconid_maintenance'] Icon ID for element with hosts in maintenance.
* @param int $i['iconid_disabled'] Icon ID for disabled element.
* @param bool $i['latelyChanged'] Whether trigger status has changed recently.
* @param int $i['priority'] Problem severity. Possible values:
* TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_INFORMATION,
* TRIGGER_SEVERITY_WARNING, TRIGGER_SEVERITY_AVERAGE, TRIGGER_SEVERITY_HIGH,
* TRIGGER_SEVERITY_DISASTER.
* @param int $i['expandproblem'] Map "Display problems" option. Possible values:
* SYSMAP_SINGLE_PROBLEM, SYSMAP_PROBLEMS_NUMBER,
* SYSMAP_PROBLEMS_NUMBER_CRITICAL.
* @param string $i['problem_title'] (optional) The name of the most critical problem.
* @param int $host_count (optional) Number of unique hosts that the current selement is related to.
* @param int|null $show_unack (optional) Map "Problem display" option. Possible values:
* EXTACK_OPTION_ALL, EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH.
*
* @return array
*/
function getSelementInfo(array $i, int $host_count = 0, int $show_unack = null): array {
if ($i['elementtype'] == SYSMAP_ELEMENT_TYPE_IMAGE) {
return [
'iconid' => $i['iconid_off'],
'icon_type' => SYSMAP_ELEMENT_ICON_OFF,
'name' => _('Image'),
'latelyChanged' => false
];
}
$info = [
'latelyChanged' => $i['latelyChanged'],
'ack' => !$i['problem_unack'],
'priority' => $i['priority'],
'info' => [],
'iconid' => $i['iconid_off'],
'aria_label' => ''
];
if ($i['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST && $i['disabled']) {
$info['iconid'] = $i['iconid_disabled'];
$info['icon_type'] = SYSMAP_ELEMENT_ICON_DISABLED;
$info['info']['status'] = [
'msg' => _('Disabled'),
'color' => '960000'
];
return $info;
}
$has_problem = false;
if ($i['problem']) {
if ($show_unack == EXTACK_OPTION_ALL || $show_unack == EXTACK_OPTION_BOTH) {
$msg = '';
// Expand single problem.
if ($i['expandproblem'] == SYSMAP_SINGLE_PROBLEM) {
$msg = ($i['problem'] == 1) ? $i['problem_title'] : _n('%1$s problem', '%1$s problems', $i['problem']);
}
// Number of problems.
elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER) {
$msg = _n('%1$s problem', '%1$s problems', $i['problem']);
}
// Number of problems and expand most critical one.
elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER_CRITICAL) {
$msg = $i['problem_title'];
if ($i['problem'] > 1) {
$msg .= "\n"._n('%1$s problem', '%1$s problems', $i['problem']);
}
}
$info['info']['problem'] = [
'msg' => $msg,
'color' => getSelementLabelColor(true, !$i['problem_unack'])
];
}
if ($i['problem_unack'] && ($show_unack == EXTACK_OPTION_UNACK || $show_unack == EXTACK_OPTION_BOTH)) {
$msg = '';
if ($show_unack == EXTACK_OPTION_UNACK) {
if ($i['expandproblem'] == SYSMAP_SINGLE_PROBLEM) {
$msg = ($i['problem_unack'] == 1)
? $i['problem_title']
: _n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
}
elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER) {
$msg = _n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
}
elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER_CRITICAL) {
$msg = $i['problem_title'];
if ($i['problem_unack'] > 1) {
$msg .= "\n".
_n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
}
}
}
elseif ($show_unack == EXTACK_OPTION_BOTH) {
$msg = _n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
}
$info['info']['unack'] = [
'msg' => $msg,
'color' => getSelementLabelColor(true, false)
];
}
// Set element to problem state if it has problem events.
if ($info['info']) {
$info['iconid'] = $i['iconid_on'];
$info['icon_type'] = SYSMAP_ELEMENT_ICON_ON;
$has_problem = true;
}
$info['aria_label'] = ($i['problem'] > 1)
? _n('%1$s problem', '%1$s problems', $i['problem'])
: $i['problem_title'];
}
$all_hosts_in_maintenance = $i['maintenance'] && ($host_count == $i['disabled'] + $i['maintenance']);
if ($i['maintenance']) {
if (!$has_problem && $all_hosts_in_maintenance) {
$info['iconid'] = $i['iconid_maintenance'];
$info['icon_type'] = SYSMAP_ELEMENT_ICON_MAINTENANCE;
}
$info['info']['maintenance'] = [
'msg' => ($i['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST)
? _('In maintenance')
: _n('%1$s host in maintenance', '%1$s hosts in maintenance', $i['maintenance']),
'color' => 'EE9600'
];
}
if (!$has_problem) {
if (!$all_hosts_in_maintenance) {
$info['iconid'] = $i['iconid_off'];
$info['icon_type'] = SYSMAP_ELEMENT_ICON_OFF;
}
$info['info']['ok'] = [
'msg' => _('OK'),
'color' => getSelementLabelColor(false, $info['ack'])
];
}
return $info;
}
/**
* Prepare map elements data.
* Calculate problem triggers and priorities. Populate map elements with automatic icon mapping, acknowledging and
* recent change markers.
*
* @param array $sysmap
* @param array $options
* @param int $options['severity_min'] Minimum severity, default value is maximal (Disaster)
*
* @return array
*/
function getSelementsInfo(array $sysmap, array $options = []): array {
if (!isset($options['severity_min'])) {
$options['severity_min'] = TRIGGER_SEVERITY_NOT_CLASSIFIED;
}
$triggerIdToSelementIds = [];
$subSysmapTriggerIdToSelementIds = [];
$hostGroupIdToSelementIds = [];
$hostIdToSelementIds = [];
if ($sysmap['sysmapid']) {
$iconMap = API::IconMap()->get([
'output' => API_OUTPUT_EXTEND,
'selectMappings' => API_OUTPUT_EXTEND,
'sysmapids' => $sysmap['sysmapid']
]);
$iconMap = reset($iconMap);
}
$hostsToGetInventories = [];
$selements = $sysmap['selements'];
$selementIdToSubSysmaps = [];
foreach ($selements as $selementId => &$selement) {
$selement['hosts'] = [];
$selement['triggers'] = [];
if ($selement['permission'] < PERM_READ) {
continue;
}
switch ($selement['elementtype']) {
case SYSMAP_ELEMENT_TYPE_MAP:
$sysmapIds = [$selement['elements'][0]['sysmapid']];
while (!empty($sysmapIds)) {
$subSysmaps = API::Map()->get([
'output' => ['sysmapid'],
'selectSelements' => ['elementtype', 'elements', 'operator', 'tags'],
'sysmapids' => $sysmapIds,
'preservekeys' => true
]);
if(!isset($selementIdToSubSysmaps[$selementId])) {
$selementIdToSubSysmaps[$selementId] = [];
}
$selementIdToSubSysmaps[$selementId] += $subSysmaps;
$sysmapIds = [];
foreach ($subSysmaps as $subSysmap) {
foreach ($subSysmap['selements'] as $subSysmapSelement) {
switch ($subSysmapSelement['elementtype']) {
case SYSMAP_ELEMENT_TYPE_MAP:
$sysmapIds[] = $subSysmapSelement['elements'][0]['sysmapid'];
break;
case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
$hostGroupIdToSelementIds[$subSysmapSelement['elements'][0]['groupid']][$selementId]
= $selementId;
break;
case SYSMAP_ELEMENT_TYPE_HOST:
$hostIdToSelementIds[$subSysmapSelement['elements'][0]['hostid']][$selementId]
= $selementId;
break;
case SYSMAP_ELEMENT_TYPE_TRIGGER:
foreach ($subSysmapSelement['elements'] as $element) {
$subSysmapTriggerIdToSelementIds[$element['triggerid']][$selementId]
= $selementId;
}
break;
}
}
}
}
break;
case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
$hostGroupId = $selement['elements'][0]['groupid'];
$hostGroupIdToSelementIds[$hostGroupId][$selementId] = $selementId;
break;
case SYSMAP_ELEMENT_TYPE_HOST:
$hostId = $selement['elements'][0]['hostid'];
$hostIdToSelementIds[$hostId][$selementId] = $selementId;
/**
* If we have icon map applied, we need to get inventories for all hosts, where automatic icon
* selection is enabled.
*/
if ($sysmap['iconmapid'] && $selement['use_iconmap']) {
$hostsToGetInventories[] = $hostId;
}
break;
case SYSMAP_ELEMENT_TYPE_TRIGGER:
foreach ($selement['elements'] as $element) {
$triggerIdToSelementIds[$element['triggerid']][$selementId] = $selementId;
}
break;
}
}
unset($selement);
// get host inventories
if ($sysmap['iconmapid']) {
$host_inventories = API::Host()->get([
'output' => ['hostid', 'inventory_mode'],
'selectInventory' => API_OUTPUT_EXTEND,
'hostids' => $hostsToGetInventories,
'preservekeys' => true
]);
}
$allHosts = [];
if ($hostIdToSelementIds) {
$allHosts = API::Host()->get([
'output' => ['name', 'status', 'maintenance_status'],
'hostids' => array_keys($hostIdToSelementIds),
'preservekeys' => true
]);
foreach ($allHosts as $hostId => $host) {
foreach ($hostIdToSelementIds[$hostId] as $selementId) {
$selements[$selementId]['hosts'][$hostId] = $hostId;
}
}
}
$hostsFromHostGroups = [];
if ($hostGroupIdToSelementIds) {
$hostsFromHostGroups = API::Host()->get([
'output' => ['name', 'status', 'maintenance_status'],
'selectHostGroups' => ['groupid'],
'groupids' => array_keys($hostGroupIdToSelementIds),
'preservekeys' => true
]);
foreach ($hostsFromHostGroups as $hostId => $host) {
foreach ($host['hostgroups'] as $group) {
$groupId = $group['groupid'];
if (isset($hostGroupIdToSelementIds[$groupId])) {
foreach ($hostGroupIdToSelementIds[$groupId] as $selementId) {
$selement =& $selements[$selementId];
$selement['hosts'][$hostId] = $hostId;
// Add hosts to hosts_map for trigger selection.
if (!isset($hostIdToSelementIds[$hostId])) {
$hostIdToSelementIds[$hostId] = [];
}
$hostIdToSelementIds[$hostId][$selementId] = $selementId;
unset($selement);
}
}
}
}
$allHosts = zbx_array_merge($allHosts, $hostsFromHostGroups);
}
// Get triggers data, triggers from current map and from submaps, select all.
if ($triggerIdToSelementIds || $subSysmapTriggerIdToSelementIds) {
$all_triggerid_to_selementids = [];
foreach ([$triggerIdToSelementIds, $subSysmapTriggerIdToSelementIds] as $triggerid_to_selementids) {
foreach ($triggerid_to_selementids as $triggerid => $selementids) {
if (!array_key_exists($triggerid, $all_triggerid_to_selementids)) {
$all_triggerid_to_selementids[$triggerid] = $selementids;
}
else {
$all_triggerid_to_selementids[$triggerid] += $selementids;
}
}
}
$triggers = API::Trigger()->get([
'output' => ['triggerid', 'status', 'value', 'priority', 'description', 'expression'],
'selectHosts' => ['hostid', 'status', 'maintenance_status'],
'triggerids' => array_keys($all_triggerid_to_selementids),
'filter' => ['state' => null],
'preservekeys' => true
]);
$monitored_triggers = API::Trigger()->get([
'output' => [],
'triggerids' => array_keys($triggers),
'monitored' => true,
'skipDependent' => true,
'preservekeys' => true
]);
foreach ($triggers as $triggerid => $trigger) {
if (!array_key_exists($triggerid, $monitored_triggers)) {
$trigger['status'] = TRIGGER_STATUS_DISABLED;
}
$trigger['source'][SYSMAP_ELEMENT_TYPE_TRIGGER] = true;
if (array_key_exists($triggerid, $all_triggerid_to_selementids)) {
foreach ($all_triggerid_to_selementids[$triggerid] as $selementid) {
$selements[$selementid]['triggers'][$triggerid] = $trigger;
}
}
}
unset($triggers, $monitored_triggers);
}
$monitored_hostids = [];
foreach ($allHosts as $hostid => $host) {
if ($host['status'] == HOST_STATUS_MONITORED) {
$monitored_hostids[$hostid] = true;
}
}
// triggers from all hosts/hostgroups, skip dependent
if ($monitored_hostids) {
$triggers = API::Trigger()->get([
'output' => ['triggerid', 'status', 'value', 'priority', 'description', 'expression'],
'selectHosts' => ['hostid', 'status', 'maintenance_status'],
'selectItems' => ['itemid'],
'hostids' => array_keys($monitored_hostids),
'filter' => ['state' => null],
'monitored' => true,
'skipDependent' => true,
'only_true' => true,
'preservekeys' => true
]);
foreach ($triggers as $triggerid => $trigger) {
$trigger['source'][SYSMAP_ELEMENT_TYPE_HOST] = true;
foreach ($trigger['hosts'] as $host) {
if (array_key_exists($host['hostid'], $hostIdToSelementIds)) {
foreach ($hostIdToSelementIds[$host['hostid']] as $selementid) {
if (!array_key_exists($triggerid, $selements[$selementid]['triggers'])) {
$selements[$selementid]['triggers'][$triggerid] = $trigger;
}
else {
$selements[$selementid]['triggers'][$triggerid]['status'] = $trigger['status'];
$selements[$selementid]['triggers'][$triggerid]['source'] += $trigger['source'];
$selements[$selementid]['triggers'][$triggerid]['items'] = $trigger['items'];
}
}
}
}
}
}
// Get problems by triggerids.
$triggerids = [];
foreach ($selements as $selement) {
foreach ($selement['triggers'] as $trigger) {
if ($trigger['status'] == TRIGGER_STATUS_ENABLED) {
$triggerids[$trigger['triggerid']] = true;
}
}
}
$triggerids = array_keys($triggerids);
$problems = API::Problem()->get([
'output' => ['eventid', 'objectid', 'name', 'acknowledged', 'clock', 'r_clock', 'severity'],
'selectTags' => ['tag', 'value'],
'objectids' => $triggerids,
'acknowledged' => ($sysmap['show_unack'] == EXTACK_OPTION_UNACK) ? false : null,
'severities' => range($options['severity_min'], TRIGGER_SEVERITY_COUNT - 1),
'suppressed' => ($sysmap['show_suppressed'] == ZBX_PROBLEM_SUPPRESSED_FALSE) ? false : null,
'symptom' => false,
'recent' => true
]);
$problems_by_trigger = array_fill_keys($triggerids, []);
foreach ($problems as $problem) {
$problems_by_trigger[$problem['objectid']][] = $problem;
}
foreach ($selements as $num => $selement) {
foreach ($selement['triggers'] as $trigger) {
if ($trigger['status'] == TRIGGER_STATUS_DISABLED) {
continue;
}
$filtered_problems = $problems_by_trigger[$trigger['triggerid']];
if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST
|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP) {
$filtered_problems = getProblemsMatchingTags($filtered_problems, $selement['tags'],
$selement['evaltype']
);
}
$selements[$num]['triggers'][$trigger['triggerid']]['problems'] = $filtered_problems;
}
}
$info = [];
foreach ($selements as $selementId => $selement) {
$i = [
'elementtype' => $selement['elementtype'],
'disabled' => 0,
'maintenance' => 0,
'problem' => 0,
'problem_unack' => 0,
'priority' => 0,
'latelyChanged' => false,
'expandproblem' => $sysmap['expandproblem']
];
/*
* If user has no rights to see the details of particular selement, add only info that is needed to render map
* icons.
*/
if (PERM_READ > $selement['permission']) {
$info[$selementId] = getSelementInfo($i + ['iconid_off' => $selement['iconid_off']]);
continue;
}
$host_count = count($selement['hosts']);
if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER
|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) {
$trigger_hosts = [];
foreach ($selement['triggers'] as $trigger) {
foreach ($trigger['hosts'] as $host) {
if (!array_key_exists($host['hostid'], $trigger_hosts)
&& !array_key_exists($host['hostid'], $selement['hosts'])) {
$trigger_hosts[$host['hostid']] = true;
$host_count++;
if ($host['status'] == HOST_STATUS_MONITORED
&& $host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON
&& ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER
|| ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP
&& array_key_exists(SYSMAP_ELEMENT_TYPE_TRIGGER, $trigger['source'])))) {
$i['maintenance']++;
}
}
}
}
}
foreach ($selement['hosts'] as $hostId) {
$host = $allHosts[$hostId];
if ($host['status'] == HOST_STATUS_NOT_MONITORED) {
$i['disabled']++;
}
elseif ($host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) {
$i['maintenance']++;
}
}
$critical_problem = [];
$trigger_order = ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER)
? zbx_objectValues($selement['elements'], 'triggerid')
: [];
$lately_changed = 0;
foreach ($selement['triggers'] as $trigger) {
if ($trigger['status'] == TRIGGER_STATUS_DISABLED) {
continue;
}
foreach ($trigger['problems'] as $problem) {
if ($problem['r_clock'] == 0) {
$i['problem']++;
if ($problem['acknowledged'] == EVENT_NOT_ACKNOWLEDGED) {
$i['problem_unack']++;
}
if (!$critical_problem || $critical_problem['severity'] < $problem['severity']) {
$critical_problem = $problem;
}
elseif ($critical_problem['severity'] === $problem['severity']) {
if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
if ($critical_problem['objectid'] === $problem['objectid']
&& $critical_problem['eventid'] < $problem['eventid']) {
$critical_problem = $problem;
}
elseif (array_search($critical_problem['objectid'], $trigger_order)
> array_search($problem['objectid'], $trigger_order)) {
$critical_problem = $problem;
}
}
elseif ($critical_problem['eventid'] < $problem['eventid']) {
$critical_problem = $problem;
}
}
}
if ($problem['r_clock'] > $lately_changed) {
$lately_changed = $problem['r_clock'];
}
elseif ($problem['clock'] > $lately_changed) {
$lately_changed = $problem['clock'];
}
}
if ((time() - $lately_changed) < timeUnitToSeconds(CSettingsHelper::get(CSettingsHelper::BLINK_PERIOD))) {
$i['latelyChanged'] = true;
}
}
if ($critical_problem) {
$i['priority'] = $critical_problem['severity'];
$i['problem_title'] = $critical_problem['name'];
}
// replace default icons
if (!$selement['iconid_on']) {
$selement['iconid_on'] = $selement['iconid_off'];
}
if (!$selement['iconid_maintenance']) {
$selement['iconid_maintenance'] = $selement['iconid_off'];
}
if (!$selement['iconid_disabled']) {
$selement['iconid_disabled'] = $selement['iconid_off'];
}
$i['iconid_off'] = $selement['iconid_off'];
$i['iconid_on'] = $selement['iconid_on'];
$i['iconid_maintenance'] = $selement['iconid_maintenance'];
$i['iconid_disabled'] = $selement['iconid_disabled'];
$info[$selementId] = getSelementInfo($i, $host_count, $sysmap['show_unack']);
if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST && $sysmap['iconmapid'] && $selement['use_iconmap']) {
$host_inventory = $host_inventories[$selement['elements'][0]['hostid']];
$info[$selementId]['iconid'] = getIconByMapping($iconMap, $host_inventory);
}
$info[$selementId]['problems_total'] = $i['problem'];
}
if ($sysmap['label_format'] == SYSMAP_LABEL_ADVANCED_OFF) {
$hlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
$hglabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
$tlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
$mlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
}
else {
$hlabel = ($sysmap['label_type_host'] == MAP_LABEL_TYPE_NAME);
$hglabel = ($sysmap['label_type_hostgroup'] == MAP_LABEL_TYPE_NAME);
$tlabel = ($sysmap['label_type_trigger'] == MAP_LABEL_TYPE_NAME);
$mlabel = ($sysmap['label_type_map'] == MAP_LABEL_TYPE_NAME);
}
// get names if needed
$elems = separateMapElements($sysmap);
if ($elems['sysmaps'] && $mlabel) {
$sysmapids = [];
foreach ($elems['sysmaps'] as $selement) {
if ($selement['permission'] >= PERM_READ) {
$sysmapids[$selement['elements'][0]['sysmapid']] = true;
}
}
$db_sysmaps = API::Map()->get([
'output' => ['name'],
'sysmapids' => array_keys($sysmapids),
'preservekeys' => true
]);
foreach ($elems['sysmaps'] as $selement) {
if ($selement['permission'] >= PERM_READ) {
$info[$selement['selementid']]['name'] =
array_key_exists($selement['elements'][0]['sysmapid'], $db_sysmaps)
? $db_sysmaps[$selement['elements'][0]['sysmapid']]['name']
: '';
}
}
}
if ($elems['hostgroups'] && $hglabel) {
$groupids = [];
foreach ($elems['hostgroups'] as $selement) {
if ($selement['permission'] >= PERM_READ) {
$groupids[$selement['elements'][0]['groupid']] = true;
}
}
$db_groups = $groupids
? API::HostGroup()->get([
'output' => ['name'],
'groupids' => array_keys($groupids),
'preservekeys' => true
])
: [];
foreach ($elems['hostgroups'] as $selement) {
if ($selement['permission'] >= PERM_READ) {
$info[$selement['selementid']]['name'] =
array_key_exists($selement['elements'][0]['groupid'], $db_groups)
? $db_groups[$selement['elements'][0]['groupid']]['name']
: '';
}
}
}
if ($elems['triggers'] && $tlabel) {
$selements = zbx_toHash($selements, 'selementid');
foreach ($elems['triggers'] as $selementid => $selement) {
foreach ($selement['elements'] as $element) {
if ($selement['permission'] >= PERM_READ) {
$trigger = array_key_exists($element['triggerid'], $selements[$selementid]['triggers'])
? $selements[$selementid]['triggers'][$element['triggerid']]
: null;
$info[$selement['selementid']]['name'] = ($trigger != null)
? CMacrosResolverHelper::resolveTriggerName($trigger)
: '';
}
}
}
}
if ($elems['hosts'] && $hlabel) {
foreach ($elems['hosts'] as $selement) {
if ($selement['permission'] >= PERM_READ) {
$info[$selement['selementid']]['name'] = array_key_exists($selement['elements'][0]['hostid'], $allHosts)
? $allHosts[$selement['elements'][0]['hostid']]['name']
: [];
}
}
}
return $info;
}
function separateMapElements($sysmap) {
$elements = [
'sysmaps' => [],
'hostgroups' => [],
'hosts' => [],
'triggers' => [],
'images' => []
];
foreach ($sysmap['selements'] as $selement) {
switch ($selement['elementtype']) {
case SYSMAP_ELEMENT_TYPE_MAP:
$elements['sysmaps'][$selement['selementid']] = $selement;
break;
case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
$elements['hostgroups'][$selement['selementid']] = $selement;
break;
case SYSMAP_ELEMENT_TYPE_HOST:
$elements['hosts'][$selement['selementid']] = $selement;
break;
case SYSMAP_ELEMENT_TYPE_TRIGGER:
$elements['triggers'][$selement['selementid']] = $selement;
break;
case SYSMAP_ELEMENT_TYPE_IMAGE:
default:
$elements['images'][$selement['selementid']] = $selement;
}
}
return $elements;
}
/**
* Calculates coordinates from elements inside areas
*
* @param array $map
* @param array $areas
* @param array $mapInfo
*/
function processAreasCoordinates(array &$map, array $areas, array $mapInfo) {
foreach ($areas as $area) {
$rowPlaceCount = ceil(sqrt(count($area['selementids'])));
// offset from area borders
$area['x'] += 5;
$area['y'] += 5;
$area['width'] -= 5;
$area['height'] -= 5;
$xOffset = floor($area['width'] / $rowPlaceCount);
$yOffset = floor($area['height'] / $rowPlaceCount);
$colNum = 0;
$rowNum = 0;
// some offset is required so that icon highlights are not drawn outside area
$borderOffset = 20;
foreach ($area['selementids'] as $selementId) {
$selement = $map['selements'][$selementId];
$image = get_png_by_selement($mapInfo[$selementId]);
$iconX = imagesx($image);
$iconY = imagesy($image);
$labelLocation = (is_null($selement['label_location']) || ($selement['label_location'] < 0))
? $map['label_location'] : $selement['label_location'];
switch ($labelLocation) {
case MAP_LABEL_LOC_TOP:
$newX = $area['x'] + ($xOffset / 2) - ($iconX / 2);
$newY = $area['y'] + $yOffset - $iconY - ($iconY >= $iconX ? 0 : abs($iconX - $iconY) / 2) - $borderOffset;
break;
case MAP_LABEL_LOC_LEFT:
$newX = $area['x'] + $xOffset - $iconX - $borderOffset;
$newY = $area['y'] + ($yOffset / 2) - ($iconY / 2);
break;
case MAP_LABEL_LOC_RIGHT:
$newX = $area['x'] + $borderOffset;
$newY = $area['y'] + ($yOffset / 2) - ($iconY / 2);
break;
case MAP_LABEL_LOC_BOTTOM:
$newX = $area['x'] + ($xOffset / 2) - ($iconX / 2);
$newY = $area['y'] + abs($iconX - $iconY) / 2 + $borderOffset;
break;
}
$map['selements'][$selementId]['x'] = $newX + ($colNum * $xOffset);
$map['selements'][$selementId]['y'] = $newY + ($rowNum * $yOffset);
$colNum++;
if ($colNum == $rowPlaceCount) {
$colNum = 0;
$rowNum++;
}
}
}
}
/**
* Calculates area connector point on area perimeter
*
* @param int $ax x area coordinate
* @param int $ay y area coordinate
* @param int $aWidth area width
* @param int $aHeight area height
* @param int $x2 x coordinate of connector second element
* @param int $y2 y coordinate of connector second element
*
* @return array contains two values, x and y coordinates of new area connector point
*/
function calculateMapAreaLinkCoord($ax, $ay, $aWidth, $aHeight, $x2, $y2) {
$dY = abs($y2 - $ay);
$dX = abs($x2 - $ax);
$halfHeight = $aHeight / 2;
$halfWidth = $aWidth / 2;
if ($dY == 0) {
$ay = $y2;
$ax = ($x2 < $ax) ? $ax - $halfWidth : $ax + $halfWidth;
}
elseif ($dX == 0) {
$ay = ($y2 > $ay) ? $ay + $halfHeight : $ay - $halfHeight;
$ax = $x2;
}
else {
$koef = $halfHeight / $dY;
$c = $dX * $koef;
// If point is further than area diagonal, we should use calculations with width instead of height.
if (($halfHeight / $c) > ($halfHeight / $halfWidth)) {
$ay = ($y2 > $ay) ? $ay + $halfHeight : $ay - $halfHeight;
$ax = ($x2 < $ax) ? $ax - $c : $ax + $c;
}
else {
$koef = $halfWidth / $dX;
$c = $dY * $koef;
$ay = ($y2 > $ay) ? $ay + $c : $ay - $c;
$ax = ($x2 < $ax) ? $ax - $halfWidth : $ax + $halfWidth;
}
}
return [$ax, $ay];
}
/**
* Get icon id by mapping.
*
* @param array $icon_map
* @param array $host
* @param int $host['inventory_mode']
* @param array $host['inventory']
*
* @return int
*/
function getIconByMapping(array $icon_map, array $host) {
if ($host['inventory_mode'] == HOST_INVENTORY_DISABLED) {
return $icon_map['default_iconid'];
}
$inventories = getHostInventories();
foreach ($icon_map['mappings'] as $mapping) {
try {
$expr = new CGlobalRegexp($mapping['expression']);
if ($expr->match($host['inventory'][$inventories[$mapping['inventory_link']]['db_field']])) {
return $mapping['iconid'];
}
}
catch(Exception $e) {
continue;
}
}
return $icon_map['default_iconid'];
}
/**
* Get parent maps for current map.
*
* @param int $sysmapid
*
* @return array
*/
function get_parent_sysmaps($sysmapid) {
$db_sysmaps_elements = DBselect(
'SELECT DISTINCT se.sysmapid'.
' FROM sysmaps_elements se'.
' WHERE '.dbConditionInt('se.elementtype', [SYSMAP_ELEMENT_TYPE_MAP]).
' AND '.dbConditionInt('se.elementid', [$sysmapid])
);
$sysmapids = [];
while ($db_sysmaps_element = DBfetch($db_sysmaps_elements)) {
$sysmapids[] = $db_sysmaps_element['sysmapid'];
}
if ($sysmapids) {
$sysmaps = API::Map()->get([
'output' => ['sysmapid', 'name'],
'sysmapids' => $sysmapids
]);
CArrayHelper::sort($sysmaps, ['name']);
return $sysmaps;
}
return [];
}
/**
* Get labels for map elements.
*
* @param array $map Sysmap data array.
* @param array $map_info Array of selements (@see getSelementsInfo).
*
* @return array
*/
function getMapLabels($map, $map_info) {
$selements = $map['selements'];
// Collect labels for each map element and apply appropriate values.
$labels = [];
foreach ($selements as $selementId => $selement) {
if ($selement['permission'] < PERM_READ) {
continue;
}
elseif ($selement['label_type'] == MAP_LABEL_TYPE_NOTHING) {
$labels[$selementId] = [];
continue;
}
$label_lines = [];
$msgs = explode("\n", $selement['label']);
foreach ($msgs as $msg) {
$label_lines[] = ['content' => $msg];
}
$status_lines = [];
$element_info = $map_info[$selementId];
if (array_key_exists('info', $element_info)) {
foreach (['problem', 'unack', 'maintenance', 'ok', 'status'] as $caption) {
if (array_key_exists($caption, $element_info['info'])
&& $element_info['info'][$caption]['msg'] !== '') {
$msgs = explode("\n", $element_info['info'][$caption]['msg']);
foreach ($msgs as $msg) {
$status_lines[] = [
'content' => $msg,
'attributes' => [
'fill' => '#'.$element_info['info'][$caption]['color']
]
];
}
}
}
}
if ($selement['label_type'] == MAP_LABEL_TYPE_IP && $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
$label = array_merge([['content' => $selement['label']]], $status_lines);
}
elseif ($selement['label_type'] == MAP_LABEL_TYPE_STATUS) {
$label = $status_lines;
}
elseif ($selement['label_type'] == MAP_LABEL_TYPE_NAME) {
$label = array_merge([['content' => $element_info['name']]], $status_lines);
}
else {
$label = array_merge($label_lines, $status_lines);
}
$labels[$selementId] = $label;
}
return $labels;
}
/**
* Get map element highlights (information about elements with marks or background).
*
* @param array $map Sysmap data array.
* @param array $map_info Array of selements (@see getSelementsInfo).
*
* @return array
*/
function getMapHighligts(array $map, array $map_info) {
$highlights = [];
foreach ($map['selements'] as $id => $selement) {
if ((($map['highlight'] % 2) != SYSMAP_HIGHLIGHT_ON) || (array_key_exists('elementtype', $selement)
&& $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
&& array_key_exists('elementsubtype', $selement)
&& $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS)) {
$highlights[$id] = null;
continue;
}
$hl_color = null;
$st_color = null;
$element_info = $map_info[$id];
switch ($element_info['icon_type']) {
case SYSMAP_ELEMENT_ICON_ON:
$hl_color = CSeverityHelper::getColor((int) $element_info['priority']);
break;
case SYSMAP_ELEMENT_ICON_MAINTENANCE:
$st_color = 'FF9933';
break;
case SYSMAP_ELEMENT_ICON_DISABLED:
$st_color = '999999';
break;
}
if (array_key_exists('elementtype', $selement)
&& ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) && $hl_color !== null) {
$st_color = null;
}
elseif ($st_color !== null) {
$hl_color = null;
}
$highlights[$id] = [
'st' => $st_color,
'hl' => $hl_color,
'ack' => ($hl_color !== null && array_key_exists('ack', $element_info) && $element_info['ack'])
];
}
return $highlights;
}
/**
* Get trigger data for all linktriggers.
*
* @param array $sysmap
* @param array $sysmap['links'] Map element link options.
* @param array $sysmap['show_suppressed'] Whether to show suppressed problems.
* @param array $sysmap['show_unack'] Property specified in sysmap's 'Problem display' field. Used to determine
* whether to show unacknowledged problems only.
* @param array $options Options used to retrieve actions.
* @param int $options['severity_min'] Minimal severity used.
*
* @return array
*/
function getMapLinkTriggerInfo($sysmap, $options) {
if (!array_key_exists('severity_min', $options)) {
$options['severity_min'] = TRIGGER_SEVERITY_NOT_CLASSIFIED;
}
$triggerids = [];
foreach ($sysmap['links'] as $link) {
foreach ($link['linktriggers'] as $linktrigger) {
$triggerids[$linktrigger['triggerid']] = true;
}
}
$trigger_options = [
'output' => ['status', 'value', 'priority'],
'triggerids' => array_keys($triggerids),
'monitored' => true,
'preservekeys' => true
];
$problem_options = [
'show_suppressed' => $sysmap['show_suppressed'],
'acknowledged' => ($sysmap['show_unack'] == EXTACK_OPTION_UNACK) ? false : null
];
return getTriggersWithActualSeverity($trigger_options, $problem_options);
}
/**
* Get map selement label color based on problem and acknowledgement state
* as well as taking custom event status color settings into account.
*
* @throws APIException if the given table does not exist
*
* @param bool $is_problem
* @param bool $is_ack
*
* @return string
*/
function getSelementLabelColor($is_problem, $is_ack) {
static $schema = null;
if ($schema === null) {
$schema = DB::getSchema('config');
}
if ($is_problem) {
$param = $is_ack ? CSettingsHelper::PROBLEM_ACK_COLOR : CSettingsHelper::PROBLEM_UNACK_COLOR;
}
else {
$param = $is_ack ? CSettingsHelper::OK_ACK_COLOR : CSettingsHelper::OK_UNACK_COLOR;
}
if (CSettingsHelper::get(CSettingsHelper::CUSTOM_COLOR) === '1') {
return CSettingsHelper::get($param);
}
return $schema['fields'][$param]['default'];
}
/**
* Filter problems by given tags.
*
* @param array $problems
* @param array $problems[]['tags']
* @param string $problems[]['tags'][]['tag']
* @param string $problems[]['tags'][]['value']
* @param array $filter_tags
* @param string $filter_tags[]['tag']
* @param string $filter_tags[]['value']
* @param int $filter_tags[]['operator']
* @param int $evaltype
*
* @return array
*/
function getProblemsMatchingTags(array $problems, array $filter_tags, int $evaltype): array {
if (!$problems) {
return [];
}
$tags = [];
foreach ($filter_tags as $tag) {
$tags[$tag['tag']][] = $tag;
}
$filtered_problems = [];
foreach ($problems as $problem) {
$matching_tags = array_fill_keys(array_keys($tags), 0);
array_walk($matching_tags, function (&$match, $key) use ($tags, $problem) {
foreach ($tags[$key] as $tag) {
if (checkIfProblemTagsMatches($tag, $problem['tags'])) {
$match = 1;
break;
}
}
});
$matching_tags = array_flip($matching_tags);
if ($evaltype == TAG_EVAL_TYPE_OR && array_key_exists(1, $matching_tags)) {
$filtered_problems[] = $problem;
}
elseif ($evaltype == TAG_EVAL_TYPE_AND_OR && !array_key_exists(0, $matching_tags)) {
$filtered_problems[] = $problem;
}
}
return $filtered_problems;
}
/**
* Check if $filter_tag matches one of tags in $tags array.
*
* @param array $filter_tag
* @param string $filter_tag['tag']
* @param string $filter_tag['value']
* @param int $filter_tag['operator']
* @param array $tags
* @param string $tags[]['tag']
* @param string $tags[]['value']
*
* @return bool
*/
function checkIfProblemTagsMatches(array $filter_tag, array $tags): bool {
if (in_array($filter_tag['operator'], [TAG_OPERATOR_NOT_LIKE, TAG_OPERATOR_NOT_EQUAL, TAG_OPERATOR_NOT_EXISTS])
&& !$tags) {
return true;
}
if ($filter_tag['operator'] == TAG_OPERATOR_NOT_LIKE && $filter_tag['value'] === '') {
$filter_tag['operator'] = TAG_OPERATOR_NOT_EXISTS;
}
elseif ($filter_tag['operator'] == TAG_OPERATOR_LIKE && $filter_tag['value'] === '') {
$filter_tag['operator'] = TAG_OPERATOR_EXISTS;
}
switch ($filter_tag['operator']) {
case TAG_OPERATOR_LIKE:
foreach ($tags as $tag) {
if ($filter_tag['tag'] === $tag['tag'] && mb_stripos($tag['value'], $filter_tag['value']) !== false) {
return true;
}
}
break;
case TAG_OPERATOR_EQUAL:
foreach ($tags as $tag) {
if ($filter_tag['tag'] === $tag['tag'] && $filter_tag['value'] === $tag['value']) {
return true;
}
}
break;
case TAG_OPERATOR_NOT_LIKE:
$tags_count = count($tags);
$tags = array_filter($tags, function ($tag) use ($filter_tag) {
return !($filter_tag['tag'] === $tag['tag']
&& mb_stripos($tag['value'], $filter_tag['value']) !== false
);
});
return (count($tags) == $tags_count);
case TAG_OPERATOR_NOT_EQUAL:
$tags_count = count($tags);
$tags = array_filter($tags, function ($tag) use ($filter_tag) {
return !($filter_tag['tag'] === $tag['tag'] && $filter_tag['value'] === $tag['value']);
});
return (count($tags) == $tags_count);
case TAG_OPERATOR_EXISTS:
return array_key_exists($filter_tag['tag'], zbx_toHash($tags, 'tag'));
case TAG_OPERATOR_NOT_EXISTS:
return !array_key_exists($filter_tag['tag'], zbx_toHash($tags, 'tag'));
}
return false;
}