<?php
/*
** Zabbix
** Copyright (C) 2001-2022 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;
			}
		}
	}

	$problems = API::Problem()->get([
		'output' => ['eventid', 'objectid', 'name', 'acknowledged', 'clock', 'r_clock', 'severity'],
		'selectTags' => ['tag', 'value'],
		'objectids' => array_keys($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,
		'recent' => true
	]);

	foreach ($selements as &$selement) {
		// Find problems originated from $selement triggers.
		$filtered_problems = array_filter($problems, function ($problem) use ($selement) {
			return array_key_exists($problem['objectid'], $selement['triggers']);
		});

		// Check if $filtered_problems tags matches $selement filter tags.
		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST
				|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP) {
			$filtered_problems = getProblemsMatchingTags($filtered_problems, $selement['tags'], $selement['evaltype']);
		}

		// Apply filtered problems to elements they belong to.
		foreach ($filtered_problems as $problem) {
			$selement['triggers'][$problem['objectid']]['problems'][] = $problem;
		}
	}
	unset($selement);

	$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;
			}

			if (array_key_exists('problems', $trigger)) {
				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['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;
}
