1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22function sysmap_element_types($type = null) {
23	$types = [
24		SYSMAP_ELEMENT_TYPE_HOST => _('Host'),
25		SYSMAP_ELEMENT_TYPE_HOST_GROUP => _('Host group'),
26		SYSMAP_ELEMENT_TYPE_TRIGGER => _('Trigger'),
27		SYSMAP_ELEMENT_TYPE_MAP => _('Map'),
28		SYSMAP_ELEMENT_TYPE_IMAGE => _('Image')
29	];
30
31	if (is_null($type)) {
32		natsort($types);
33		return $types;
34	}
35	elseif (isset($types[$type])) {
36		return $types[$type];
37	}
38	else {
39		return _('Unknown');
40	}
41}
42
43function sysmapElementLabel($label = null) {
44	$labels = [
45		MAP_LABEL_TYPE_LABEL => _('Label'),
46		MAP_LABEL_TYPE_IP => _('IP address'),
47		MAP_LABEL_TYPE_NAME => _('Element name'),
48		MAP_LABEL_TYPE_STATUS => _('Status only'),
49		MAP_LABEL_TYPE_NOTHING => _('Nothing'),
50		MAP_LABEL_TYPE_CUSTOM => _('Custom label')
51	];
52
53	if (is_null($label)) {
54		return $labels;
55	}
56	elseif (isset($labels[$label])) {
57		return $labels[$label];
58	}
59	else {
60		return false;
61	}
62}
63
64/**
65 * Get actions (data for popup menu) for map elements.
66 *
67 * @param array  $sysmap
68 * @param array  $sysmap['selements']
69 * @param array  $options                   Options used to retrieve actions.
70 * @param int    $options['severity_min']   Minimal severity used.
71 * @param int    $options['unique_id']
72 *
73 * @return array
74 */
75function getActionsBySysmap(array $sysmap, array $options = []) {
76	$actions = [];
77	$severity_min = array_key_exists('severity_min', $options)
78		? $options['severity_min']
79		: TRIGGER_SEVERITY_NOT_CLASSIFIED;
80
81	foreach ($sysmap['selements'] as $selementid => $elem) {
82		if ($elem['permission'] < PERM_READ) {
83			continue;
84		}
85
86		if (array_key_exists('unique_id', $options)) {
87			$elem['unique_id'] = $options['unique_id'];
88		}
89
90		$hostid = ($elem['elementtype_orig'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
91				&& $elem['elementsubtype_orig'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS)
92			? $elem['elements'][0]['hostid']
93			: 0;
94
95		$map = CMenuPopupHelper::getMapElement($sysmap['sysmapid'], $elem, $severity_min, $hostid);
96
97		$actions[$selementid] = json_encode($map);
98	}
99
100	return $actions;
101}
102
103function get_png_by_selement($info) {
104	$image = get_image_by_imageid($info['iconid']);
105
106	return $image['image'] ? imagecreatefromstring($image['image']) : get_default_image();
107}
108
109function get_map_elements($db_element, &$elements) {
110	switch ($db_element['elementtype']) {
111		case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
112			$elements['hosts_groups'][] = $db_element['elements'][0]['groupid'];
113			break;
114		case SYSMAP_ELEMENT_TYPE_HOST:
115			$elements['hosts'][] = $db_element['elements'][0]['hostid'];
116			break;
117		case SYSMAP_ELEMENT_TYPE_TRIGGER:
118			foreach ($db_element['elements'] as $db_element) {
119				$elements['triggers'][] = $db_element['triggerid'];
120			}
121			break;
122		case SYSMAP_ELEMENT_TYPE_MAP:
123			$map = API::Map()->get([
124				'output' => [],
125				'selectSelements' => ['selementid', 'elements', 'elementtype'],
126				'sysmapids' => $db_element['elements'][0]['sysmapid'],
127				'nopermissions' => true
128			]);
129
130			if ($map) {
131				$map = reset($map);
132
133				foreach ($map['selements'] as $db_mapelement) {
134					get_map_elements($db_mapelement, $elements);
135				}
136			}
137			break;
138	}
139}
140
141/**
142 * Adds names to elements. Adds expression for SYSMAP_ELEMENT_TYPE_TRIGGER elements.
143 *
144 * @param array $selements
145 * @param array $selements[]['elements']
146 * @param int   $selements[]['elementtype']
147 * @param int   $selements[]['iconid_off']
148 * @param int   $selements[]['permission']
149 */
150function addElementNames(array &$selements) {
151	$hostids = [];
152	$triggerids = [];
153	$sysmapids = [];
154	$groupids = [];
155	$imageids = [];
156
157	foreach ($selements as $selement) {
158		if ($selement['permission'] < PERM_READ) {
159			continue;
160		}
161
162		switch ($selement['elementtype']) {
163			case SYSMAP_ELEMENT_TYPE_HOST:
164				$hostids[$selement['elements'][0]['hostid']] = $selement['elements'][0]['hostid'];
165				break;
166
167			case SYSMAP_ELEMENT_TYPE_MAP:
168				$sysmapids[$selement['elements'][0]['sysmapid']] = $selement['elements'][0]['sysmapid'];
169				break;
170
171			case SYSMAP_ELEMENT_TYPE_TRIGGER:
172				foreach ($selement['elements'] as $element) {
173					$triggerids[$element['triggerid']] = $element['triggerid'];
174				}
175				break;
176
177			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
178				$groupids[$selement['elements'][0]['groupid']] = $selement['elements'][0]['groupid'];
179				break;
180
181			case SYSMAP_ELEMENT_TYPE_IMAGE:
182				$imageids[$selement['iconid_off']] = $selement['iconid_off'];
183				break;
184		}
185	}
186
187	$hosts = $hostids
188		? API::Host()->get([
189			'output' => ['name'],
190			'hostids' => $hostids,
191			'preservekeys' => true
192		])
193		: [];
194
195	$maps = $sysmapids
196		? API::Map()->get([
197			'output' => ['name'],
198			'sysmapids' => $sysmapids,
199			'preservekeys' => true
200		])
201		: [];
202
203	$triggers = $triggerids
204		? API::Trigger()->get([
205			'output' => ['description', 'expression', 'priority'],
206			'selectHosts' => ['name'],
207			'triggerids' => $triggerids,
208			'preservekeys' => true
209		])
210		: [];
211	$triggers = CMacrosResolverHelper::resolveTriggerNames($triggers);
212
213	$groups = $groupids
214		? API::HostGroup()->get([
215			'output' => ['name'],
216			'hostgroupids' => $groupids,
217			'preservekeys' => true
218		])
219		: [];
220
221	$images = $imageids
222		? API::Image()->get([
223			'output' => ['name'],
224			'imageids' => $imageids,
225			'preservekeys' => true
226		])
227		: [];
228
229	foreach ($selements as $snum => &$selement) {
230		if ($selement['permission'] < PERM_READ) {
231			continue;
232		}
233
234		switch ($selement['elementtype']) {
235			case SYSMAP_ELEMENT_TYPE_HOST:
236				$selements[$snum]['elements'][0]['elementName'] = $hosts[$selement['elements'][0]['hostid']]['name'];
237				break;
238
239			case SYSMAP_ELEMENT_TYPE_MAP:
240				$selements[$snum]['elements'][0]['elementName'] = $maps[$selement['elements'][0]['sysmapid']]['name'];
241				break;
242
243			case SYSMAP_ELEMENT_TYPE_TRIGGER:
244				foreach ($selement['elements'] as $enum => &$element) {
245					if (array_key_exists($element['triggerid'], $triggers)) {
246						$trigger = $triggers[$element['triggerid']];
247						$element['elementName'] = $trigger['hosts'][0]['name'].NAME_DELIMITER.$trigger['description'];
248						$element['priority'] = $trigger['priority'];
249					}
250					else {
251						unset($selement['elements'][$enum]);
252					}
253				}
254				unset($element);
255				$selement['elements'] = array_values($selement['elements']);
256				break;
257
258			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
259				$selements[$snum]['elements'][0]['elementName'] = $groups[$selement['elements'][0]['groupid']]['name'];
260				break;
261
262			case SYSMAP_ELEMENT_TYPE_IMAGE:
263				if (array_key_exists($selement['iconid_off'], $images)) {
264					$selements[$snum]['elements'][0]['elementName'] = $images[$selement['iconid_off']]['name'];
265				}
266				break;
267		}
268	}
269	unset($selement);
270}
271
272/**
273 * Returns selement icon rendering parameters.
274 *
275 * @param array    $i
276 * @param int      $i['elementtype']         Element type. Possible values:
277 *                                           SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP,
278 *                                           SYSMAP_ELEMENT_TYPE_TRIGGER, SYSMAP_ELEMENT_TYPE_HOST_GROUP,
279 *                                           SYSMAP_ELEMENT_TYPE_IMAGE.
280 * @param int      $i['disabled']            The number of disabled hosts.
281 * @param int      $i['maintenance']         The number of hosts in maintenance.
282 * @param int      $i['problem']             The number of problems.
283 * @param int      $i['problem_unack']       The number of unacknowledged problems.
284 * @param int      $i['iconid_off']          Icon ID for element without problems.
285 * @param int      $i['iconid_on']           Icon ID for element with problems.
286 * @param int      $i['iconid_maintenance']  Icon ID for element with hosts in maintenance.
287 * @param int      $i['iconid_disabled']     Icon ID for disabled element.
288 * @param bool     $i['latelyChanged']       Whether trigger status has changed recently.
289 * @param int      $i['priority']            Problem severity. Possible values:
290 *                                           TRIGGER_SEVERITY_NOT_CLASSIFIED, TRIGGER_SEVERITY_INFORMATION,
291 *                                           TRIGGER_SEVERITY_WARNING, TRIGGER_SEVERITY_AVERAGE, TRIGGER_SEVERITY_HIGH,
292 *                                           TRIGGER_SEVERITY_DISASTER.
293 * @param int      $i['expandproblem']       Map "Display problems" option. Possible values:
294 *                                           SYSMAP_SINGLE_PROBLEM, SYSMAP_PROBLEMS_NUMBER,
295 *                                           SYSMAP_PROBLEMS_NUMBER_CRITICAL.
296 * @param string   $i['problem_title']       (optional) The name of the most critical problem.
297 * @param int|null $show_unack               (optional) Map "Problem display" option. Possible values:
298 *                                           EXTACK_OPTION_ALL, EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH.
299 *
300 * @return array
301 */
302function getSelementInfo(array $i, ?int $show_unack = null): array {
303	if ($i['elementtype'] == SYSMAP_ELEMENT_TYPE_IMAGE) {
304		return [
305			'iconid' => $i['iconid_off'],
306			'icon_type' => SYSMAP_ELEMENT_ICON_OFF,
307			'name' => _('Image'),
308			'latelyChanged' => false
309		];
310	}
311
312	$info = [
313		'latelyChanged' => $i['latelyChanged'],
314		'ack' => !$i['problem_unack'],
315		'priority' => $i['priority'],
316		'info' => [],
317		'iconid' => $i['iconid_off'],
318		'aria_label' => ''
319	];
320
321	if ($i['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST && $i['disabled']) {
322		$info['iconid'] = $i['iconid_disabled'];
323		$info['icon_type'] = SYSMAP_ELEMENT_ICON_DISABLED;
324		$info['info']['status'] = [
325			'msg' => _('Disabled'),
326			'color' => '960000'
327		];
328
329		return $info;
330	}
331
332	$has_problem = false;
333
334	if ($i['problem']) {
335		if ($show_unack == EXTACK_OPTION_ALL || $show_unack == EXTACK_OPTION_BOTH) {
336			$msg = '';
337
338			// Expand single problem.
339			if ($i['expandproblem'] == SYSMAP_SINGLE_PROBLEM) {
340				$msg = ($i['problem'] == 1) ? $i['problem_title'] : _n('%1$s problem', '%1$s problems', $i['problem']);
341			}
342			// Number of problems.
343			elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER) {
344				$msg = _n('%1$s problem', '%1$s problems', $i['problem']);
345			}
346			// Number of problems and expand most critical one.
347			elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER_CRITICAL) {
348				$msg = $i['problem_title'];
349
350				if ($i['problem'] > 1) {
351					$msg .= "\n"._n('%1$s problem', '%1$s problems', $i['problem']);
352				}
353			}
354
355			$info['info']['problem'] = [
356				'msg' => $msg,
357				'color' => getSelementLabelColor(true, !$i['problem_unack'])
358			];
359		}
360
361		if ($i['problem_unack'] && ($show_unack == EXTACK_OPTION_UNACK || $show_unack == EXTACK_OPTION_BOTH)) {
362			$msg = '';
363
364			if ($show_unack == EXTACK_OPTION_UNACK) {
365				if ($i['expandproblem'] == SYSMAP_SINGLE_PROBLEM) {
366					$msg = ($i['problem_unack'] == 1)
367						? $i['problem_title']
368						: _n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
369				}
370				elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER) {
371					$msg = _n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
372				}
373				elseif ($i['expandproblem'] == SYSMAP_PROBLEMS_NUMBER_CRITICAL) {
374					$msg = $i['problem_title'];
375
376					if ($i['problem_unack'] > 1) {
377						$msg .= "\n".
378							_n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
379					}
380				}
381			}
382			elseif ($show_unack == EXTACK_OPTION_BOTH) {
383				$msg = _n('%1$s unacknowledged problem', '%1$s unacknowledged problems', $i['problem_unack']);
384			}
385
386			$info['info']['unack'] = [
387				'msg' => $msg,
388				'color' => getSelementLabelColor(true, false)
389			];
390		}
391
392		// Set element to problem state if it has problem events.
393		if ($info['info']) {
394			$info['iconid'] = $i['iconid_on'];
395			$info['icon_type'] = SYSMAP_ELEMENT_ICON_ON;
396			$has_problem = true;
397		}
398
399		$info['aria_label'] = ($i['problem'] > 1)
400			? _n('%1$s problem', '%1$s problems', $i['problem'])
401			: $i['problem_title'];
402	}
403
404	if ($i['maintenance']) {
405		$info['iconid'] = $i['iconid_maintenance'];
406		$info['icon_type'] = SYSMAP_ELEMENT_ICON_MAINTENANCE;
407		$info['info']['maintenance'] = [
408			'msg' => ($i['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST)
409				? _('In maintenance')
410				: _n('%1$s host in maintenance', '%1$s hosts in maintenance', $i['maintenance']),
411			'color' => 'EE9600'
412		];
413	}
414
415	if (!$has_problem) {
416		if (!$i['maintenance']) {
417			$info['iconid'] = $i['iconid_off'];
418			$info['icon_type'] = SYSMAP_ELEMENT_ICON_OFF;
419		}
420
421		$info['info']['ok'] = [
422			'msg' => _('OK'),
423			'color' => getSelementLabelColor(false, $info['ack'])
424		];
425	}
426
427	return $info;
428}
429
430/**
431 * Prepare map elements data.
432 * Calculate problem triggers and priorities. Populate map elements with automatic icon mapping, acknowledging and
433 * recent change markers.
434 *
435 * @param array $sysmap
436 * @param array $options
437 * @param int   $options['severity_min']  Minimum severity, default value is maximal (Disaster)
438 *
439 * @return array
440 */
441function getSelementsInfo(array $sysmap, array $options = []): array {
442	if (!isset($options['severity_min'])) {
443		$options['severity_min'] = TRIGGER_SEVERITY_NOT_CLASSIFIED;
444	}
445
446	$triggerIdToSelementIds = [];
447	$subSysmapTriggerIdToSelementIds = [];
448	$hostGroupIdToSelementIds = [];
449	$hostIdToSelementIds = [];
450
451	if ($sysmap['sysmapid']) {
452		$iconMap = API::IconMap()->get([
453			'output' => API_OUTPUT_EXTEND,
454			'selectMappings' => API_OUTPUT_EXTEND,
455			'sysmapids' => $sysmap['sysmapid']
456		]);
457		$iconMap = reset($iconMap);
458	}
459	$hostsToGetInventories = [];
460
461	$selements = $sysmap['selements'];
462	$selementIdToSubSysmaps = [];
463	foreach ($selements as $selementId => &$selement) {
464		$selement['hosts'] = [];
465		$selement['triggers'] = [];
466
467		if ($selement['permission'] < PERM_READ) {
468			continue;
469		}
470
471		switch ($selement['elementtype']) {
472			case SYSMAP_ELEMENT_TYPE_MAP:
473				$sysmapIds = [$selement['elements'][0]['sysmapid']];
474
475				while (!empty($sysmapIds)) {
476					$subSysmaps = API::Map()->get([
477						'output' => ['sysmapid'],
478						'selectSelements' => ['elementtype', 'elements', 'operator', 'tags'],
479						'sysmapids' => $sysmapIds,
480						'preservekeys' => true
481					]);
482
483					if(!isset($selementIdToSubSysmaps[$selementId])) {
484						$selementIdToSubSysmaps[$selementId] = [];
485					}
486					$selementIdToSubSysmaps[$selementId] += $subSysmaps;
487
488					$sysmapIds = [];
489					foreach ($subSysmaps as $subSysmap) {
490						foreach ($subSysmap['selements'] as $subSysmapSelement) {
491							switch ($subSysmapSelement['elementtype']) {
492								case SYSMAP_ELEMENT_TYPE_MAP:
493									$sysmapIds[] = $subSysmapSelement['elements'][0]['sysmapid'];
494									break;
495								case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
496									$hostGroupIdToSelementIds[$subSysmapSelement['elements'][0]['groupid']][$selementId]
497										= $selementId;
498									break;
499								case SYSMAP_ELEMENT_TYPE_HOST:
500									$hostIdToSelementIds[$subSysmapSelement['elements'][0]['hostid']][$selementId]
501										= $selementId;
502									break;
503								case SYSMAP_ELEMENT_TYPE_TRIGGER:
504									foreach ($subSysmapSelement['elements'] as $element) {
505										$subSysmapTriggerIdToSelementIds[$element['triggerid']][$selementId]
506											= $selementId;
507									}
508									break;
509							}
510						}
511					}
512				}
513				break;
514			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
515				$hostGroupId = $selement['elements'][0]['groupid'];
516				$hostGroupIdToSelementIds[$hostGroupId][$selementId] = $selementId;
517				break;
518			case SYSMAP_ELEMENT_TYPE_HOST:
519				$hostId = $selement['elements'][0]['hostid'];
520				$hostIdToSelementIds[$hostId][$selementId] = $selementId;
521
522				/**
523				 * If we have icon map applied, we need to get inventories for all hosts, where automatic icon
524				 * selection is enabled.
525				 */
526				if ($sysmap['iconmapid'] && $selement['use_iconmap']) {
527					$hostsToGetInventories[] = $hostId;
528				}
529				break;
530			case SYSMAP_ELEMENT_TYPE_TRIGGER:
531				foreach ($selement['elements'] as $element) {
532					$triggerIdToSelementIds[$element['triggerid']][$selementId] = $selementId;
533				}
534				break;
535		}
536	}
537	unset($selement);
538
539	// get host inventories
540	if ($sysmap['iconmapid']) {
541		$host_inventories = API::Host()->get([
542			'output' => ['hostid', 'inventory_mode'],
543			'selectInventory' => API_OUTPUT_EXTEND,
544			'hostids' => $hostsToGetInventories,
545			'preservekeys' => true
546		]);
547	}
548
549	$allHosts = [];
550	if ($hostIdToSelementIds) {
551		$allHosts = API::Host()->get([
552			'output' => ['name', 'status', 'maintenance_status'],
553			'hostids' => array_keys($hostIdToSelementIds),
554			'preservekeys' => true
555		]);
556
557		foreach ($allHosts as $hostId => $host) {
558			foreach ($hostIdToSelementIds[$hostId] as $selementId) {
559				$selements[$selementId]['hosts'][$hostId] = $hostId;
560			}
561		}
562	}
563
564	$hostsFromHostGroups = [];
565	if ($hostGroupIdToSelementIds) {
566		$hostsFromHostGroups = API::Host()->get([
567			'output' => ['name', 'status', 'maintenance_status'],
568			'selectGroups' => ['groupid'],
569			'groupids' => array_keys($hostGroupIdToSelementIds),
570			'preservekeys' => true
571		]);
572
573		foreach ($hostsFromHostGroups as $hostId => $host) {
574			foreach ($host['groups'] as $group) {
575				$groupId = $group['groupid'];
576
577				if (isset($hostGroupIdToSelementIds[$groupId])) {
578					foreach ($hostGroupIdToSelementIds[$groupId] as $selementId) {
579						$selement =& $selements[$selementId];
580
581						$selement['hosts'][$hostId] = $hostId;
582
583						// Add hosts to hosts_map for trigger selection.
584						if (!isset($hostIdToSelementIds[$hostId])) {
585							$hostIdToSelementIds[$hostId] = [];
586						}
587						$hostIdToSelementIds[$hostId][$selementId] = $selementId;
588
589						unset($selement);
590					}
591				}
592			}
593		}
594
595		$allHosts = zbx_array_merge($allHosts, $hostsFromHostGroups);
596	}
597
598	// Get triggers data, triggers from current map and from submaps, select all.
599	if ($triggerIdToSelementIds || $subSysmapTriggerIdToSelementIds) {
600		$all_triggerid_to_selementids = [];
601
602		foreach ([$triggerIdToSelementIds, $subSysmapTriggerIdToSelementIds] as $triggerid_to_selementids) {
603			foreach ($triggerid_to_selementids as $triggerid => $selementids) {
604				if (!array_key_exists($triggerid, $all_triggerid_to_selementids)) {
605					$all_triggerid_to_selementids[$triggerid] = $selementids;
606				}
607				else {
608					$all_triggerid_to_selementids[$triggerid] += $selementids;
609				}
610			}
611		}
612
613		$triggers = API::Trigger()->get([
614			'output' => ['triggerid', 'status', 'value', 'priority', 'description', 'expression'],
615			'selectHosts' => ['hostid', 'maintenance_status'],
616			'triggerids' => array_keys($all_triggerid_to_selementids),
617			'filter' => ['state' => null],
618			'preservekeys' => true
619		]);
620
621		$monitored_triggers = API::Trigger()->get([
622			'output' => [],
623			'triggerids' => array_keys($triggers),
624			'monitored' => true,
625			'skipDependent' => true,
626			'preservekeys' => true
627		]);
628
629		foreach ($triggers as $triggerid => $trigger) {
630			if (!array_key_exists($triggerid, $monitored_triggers)) {
631				$trigger['status'] = TRIGGER_STATUS_DISABLED;
632			}
633
634			$trigger['source'][SYSMAP_ELEMENT_TYPE_TRIGGER] = true;
635
636			if (array_key_exists($triggerid, $all_triggerid_to_selementids)) {
637				foreach ($all_triggerid_to_selementids[$triggerid] as $selementid) {
638					$selements[$selementid]['triggers'][$triggerid] = $trigger;
639				}
640			}
641		}
642		unset($triggers, $monitored_triggers);
643	}
644
645	$monitored_hostids = [];
646	foreach ($allHosts as $hostid => $host) {
647		if ($host['status'] == HOST_STATUS_MONITORED) {
648			$monitored_hostids[$hostid] = true;
649		}
650	}
651
652	// triggers from all hosts/hostgroups, skip dependent
653	if ($monitored_hostids) {
654		$triggers = API::Trigger()->get([
655			'output' => ['triggerid', 'status', 'value', 'priority', 'description', 'expression'],
656			'selectHosts' => ['hostid', 'maintenance_status'],
657			'selectItems' => ['itemid'],
658			'hostids' => array_keys($monitored_hostids),
659			'filter' => ['state' => null],
660			'monitored' => true,
661			'skipDependent' => true,
662			'only_true' => true,
663			'preservekeys' => true
664		]);
665
666		foreach ($triggers as $triggerid => $trigger) {
667			$trigger['source'][SYSMAP_ELEMENT_TYPE_HOST] = true;
668
669			foreach ($trigger['hosts'] as $host) {
670				if (array_key_exists($host['hostid'], $hostIdToSelementIds)) {
671					foreach ($hostIdToSelementIds[$host['hostid']] as $selementid) {
672						if (!array_key_exists($triggerid, $selements[$selementid]['triggers'])) {
673							$selements[$selementid]['triggers'][$triggerid] = $trigger;
674						}
675						else {
676							$selements[$selementid]['triggers'][$triggerid]['status'] = $trigger['status'];
677							$selements[$selementid]['triggers'][$triggerid]['source'] += $trigger['source'];
678							$selements[$selementid]['triggers'][$triggerid]['items'] = $trigger['items'];
679						}
680					}
681				}
682			}
683		}
684	}
685
686	// Get problems by triggerids.
687	$triggerids = [];
688	foreach ($selements as $selement) {
689		foreach ($selement['triggers'] as $trigger) {
690			if ($trigger['status'] == TRIGGER_STATUS_ENABLED) {
691				$triggerids[$trigger['triggerid']] = true;
692			}
693		}
694	}
695
696	$problems = API::Problem()->get([
697		'output' => ['eventid', 'objectid', 'name', 'acknowledged', 'clock', 'r_clock', 'severity'],
698		'selectTags' => ['tag', 'value'],
699		'objectids' => array_keys($triggerids),
700		'acknowledged' => ($sysmap['show_unack'] == EXTACK_OPTION_UNACK) ? false : null,
701		'severities' => range($options['severity_min'], TRIGGER_SEVERITY_COUNT - 1),
702		'suppressed' => ($sysmap['show_suppressed'] == ZBX_PROBLEM_SUPPRESSED_FALSE) ? false : null,
703		'recent' => true
704	]);
705
706	foreach ($selements as &$selement) {
707		// Find problems originated from $selement triggers.
708		$filtered_problems = array_filter($problems, function ($problem) use ($selement) {
709			return array_key_exists($problem['objectid'], $selement['triggers']);
710		});
711
712		// Check if $filtered_problems tags matches $selement filter tags.
713		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST
714				|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP) {
715			$filtered_problems = getProblemsMatchingTags($filtered_problems, $selement['tags'], $selement['evaltype']);
716		}
717
718		// Apply filtgered problems to elements they belong to.
719		foreach ($filtered_problems as $problem) {
720			$selement['triggers'][$problem['objectid']]['problems'][] = $problem;
721		}
722	}
723	unset($selement);
724
725	$info = [];
726	foreach ($selements as $selementId => $selement) {
727		$i = [
728			'elementtype' => $selement['elementtype'],
729			'disabled' => 0,
730			'maintenance' => 0,
731			'problem' => 0,
732			'problem_unack' => 0,
733			'priority' => 0,
734			'latelyChanged' => false,
735			'expandproblem' => $sysmap['expandproblem']
736		];
737
738		/*
739		 * If user has no rights to see the details of particular selement, add only info that is needed to render map
740		 * icons.
741		 */
742		if (PERM_READ > $selement['permission']) {
743			$info[$selementId] = getSelementInfo($i + ['iconid_off' => $selement['iconid_off']]);
744
745			continue;
746		}
747
748		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER
749				|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) {
750			$trigger_hosts = [];
751			foreach ($selement['triggers'] as $trigger) {
752				foreach ($trigger['hosts'] as $host) {
753					if (!array_key_exists($host['hostid'], $trigger_hosts)
754							&& !array_key_exists($host['hostid'], $selement['hosts'])) {
755						$trigger_hosts[$host['hostid']] = true;
756
757						if ($host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON
758								&& ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER
759								|| ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP
760									&& array_key_exists(SYSMAP_ELEMENT_TYPE_TRIGGER, $trigger['source'])))) {
761							$i['maintenance']++;
762						}
763					}
764				}
765			}
766		}
767
768		foreach ($selement['hosts'] as $hostId) {
769			$host = $allHosts[$hostId];
770
771			if ($host['status'] == HOST_STATUS_NOT_MONITORED) {
772				$i['disabled']++;
773			}
774			elseif ($host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) {
775				$i['maintenance']++;
776			}
777		}
778
779		$critical_problem = [];
780		$trigger_order = ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER)
781			? zbx_objectValues($selement['elements'], 'triggerid')
782			: [];
783		$lately_changed = 0;
784
785		foreach ($selement['triggers'] as $trigger) {
786			if ($trigger['status'] == TRIGGER_STATUS_DISABLED) {
787				continue;
788			}
789
790			if (array_key_exists('problems', $trigger)) {
791				foreach ($trigger['problems'] as $problem) {
792					if ($problem['r_clock'] == 0) {
793						$i['problem']++;
794
795						if ($problem['acknowledged'] == EVENT_NOT_ACKNOWLEDGED) {
796							$i['problem_unack']++;
797						}
798
799						if (!$critical_problem || $critical_problem['severity'] < $problem['severity']) {
800							$critical_problem = $problem;
801						}
802						elseif ($critical_problem['severity'] === $problem['severity']) {
803							if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
804								if ($critical_problem['objectid'] === $problem['objectid']
805										&& $critical_problem['eventid'] < $problem['eventid']) {
806									$critical_problem = $problem;
807								}
808								elseif (array_search($critical_problem['objectid'], $trigger_order)
809										> array_search($problem['objectid'], $trigger_order)) {
810									$critical_problem = $problem;
811								}
812							}
813							elseif ($critical_problem['eventid'] < $problem['eventid']) {
814								$critical_problem = $problem;
815							}
816						}
817					}
818
819					if ($problem['r_clock'] > $lately_changed) {
820						$lately_changed = $problem['r_clock'];
821					}
822					elseif ($problem['clock'] > $lately_changed) {
823						$lately_changed = $problem['clock'];
824					}
825				}
826			}
827
828			$i['latelyChanged'] |= ((time() - $lately_changed) < timeUnitToSeconds(CSettingsHelper::get(
829				CSettingsHelper::BLINK_PERIOD
830			)));
831		}
832
833		if ($critical_problem) {
834			$i['priority'] = $critical_problem['severity'];
835			$i['problem_title'] = $critical_problem['name'];
836		}
837
838		// replace default icons
839		if (!$selement['iconid_on']) {
840			$selement['iconid_on'] = $selement['iconid_off'];
841		}
842		if (!$selement['iconid_maintenance']) {
843			$selement['iconid_maintenance'] = $selement['iconid_off'];
844		}
845		if (!$selement['iconid_disabled']) {
846			$selement['iconid_disabled'] = $selement['iconid_off'];
847		}
848
849		$i['iconid_off'] = $selement['iconid_off'];
850		$i['iconid_on'] = $selement['iconid_on'];
851		$i['iconid_maintenance'] = $selement['iconid_maintenance'];
852		$i['iconid_disabled'] = $selement['iconid_disabled'];
853
854		$info[$selementId] = getSelementInfo($i, $sysmap['show_unack']);
855
856		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST && $sysmap['iconmapid'] && $selement['use_iconmap']) {
857			$host_inventory = $host_inventories[$selement['elements'][0]['hostid']];
858			$info[$selementId]['iconid'] = getIconByMapping($iconMap, $host_inventory);
859		}
860
861		$info[$selementId]['problems_total'] = $i['problem'];
862	}
863
864	if ($sysmap['label_format'] == SYSMAP_LABEL_ADVANCED_OFF) {
865		$hlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
866		$hglabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
867		$tlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
868		$mlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
869	}
870	else {
871		$hlabel = ($sysmap['label_type_host'] == MAP_LABEL_TYPE_NAME);
872		$hglabel = ($sysmap['label_type_hostgroup'] == MAP_LABEL_TYPE_NAME);
873		$tlabel = ($sysmap['label_type_trigger'] == MAP_LABEL_TYPE_NAME);
874		$mlabel = ($sysmap['label_type_map'] == MAP_LABEL_TYPE_NAME);
875	}
876
877	// get names if needed
878	$elems = separateMapElements($sysmap);
879
880	if ($elems['sysmaps'] && $mlabel) {
881		$sysmapids = [];
882
883		foreach ($elems['sysmaps'] as $selement) {
884			if ($selement['permission'] >= PERM_READ) {
885				$sysmapids[$selement['elements'][0]['sysmapid']] = true;
886			}
887		}
888
889		$db_sysmaps = API::Map()->get([
890			'output' => ['name'],
891			'sysmapids' => array_keys($sysmapids),
892			'preservekeys' => true
893		]);
894
895		foreach ($elems['sysmaps'] as $selement) {
896			if ($selement['permission'] >= PERM_READ) {
897				$info[$selement['selementid']]['name'] =
898					array_key_exists($selement['elements'][0]['sysmapid'], $db_sysmaps)
899						? $db_sysmaps[$selement['elements'][0]['sysmapid']]['name']
900						: '';
901			}
902		}
903	}
904
905	if ($elems['hostgroups'] && $hglabel) {
906		$groupids = [];
907
908		foreach ($elems['hostgroups'] as $selement) {
909			if ($selement['permission'] >= PERM_READ) {
910				$groupids[$selement['elements'][0]['groupid']] = true;
911			}
912		}
913
914		$db_groups = $groupids
915			? API::HostGroup()->get([
916				'output' => ['name'],
917				'groupids' => array_keys($groupids),
918				'preservekeys' => true
919			])
920			: [];
921
922		foreach ($elems['hostgroups'] as $selement) {
923			if ($selement['permission'] >= PERM_READ) {
924				$info[$selement['selementid']]['name'] =
925					array_key_exists($selement['elements'][0]['groupid'], $db_groups)
926						? $db_groups[$selement['elements'][0]['groupid']]['name']
927						: '';
928			}
929		}
930	}
931
932	if ($elems['triggers'] && $tlabel) {
933		$selements = zbx_toHash($selements, 'selementid');
934		foreach ($elems['triggers'] as $selementid => $selement) {
935			foreach ($selement['elements'] as $element) {
936				if ($selement['permission'] >= PERM_READ) {
937					$trigger = array_key_exists($element['triggerid'], $selements[$selementid]['triggers'])
938						? $selements[$selementid]['triggers'][$element['triggerid']]
939						: null;
940					$info[$selement['selementid']]['name'] = ($trigger != null)
941						? CMacrosResolverHelper::resolveTriggerName($trigger)
942						: '';
943				}
944			}
945		}
946	}
947
948	if ($elems['hosts'] && $hlabel) {
949		foreach ($elems['hosts'] as $selement) {
950			if ($selement['permission'] >= PERM_READ) {
951				$info[$selement['selementid']]['name'] = array_key_exists($selement['elements'][0]['hostid'], $allHosts)
952					? $allHosts[$selement['elements'][0]['hostid']]['name']
953					: [];
954			}
955		}
956	}
957
958	return $info;
959}
960
961function separateMapElements($sysmap) {
962	$elements = [
963		'sysmaps' => [],
964		'hostgroups' => [],
965		'hosts' => [],
966		'triggers' => [],
967		'images' => []
968	];
969
970	foreach ($sysmap['selements'] as $selement) {
971		switch ($selement['elementtype']) {
972			case SYSMAP_ELEMENT_TYPE_MAP:
973				$elements['sysmaps'][$selement['selementid']] = $selement;
974				break;
975			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
976				$elements['hostgroups'][$selement['selementid']] = $selement;
977				break;
978			case SYSMAP_ELEMENT_TYPE_HOST:
979				$elements['hosts'][$selement['selementid']] = $selement;
980				break;
981			case SYSMAP_ELEMENT_TYPE_TRIGGER:
982				$elements['triggers'][$selement['selementid']] = $selement;
983				break;
984			case SYSMAP_ELEMENT_TYPE_IMAGE:
985			default:
986				$elements['images'][$selement['selementid']] = $selement;
987		}
988	}
989	return $elements;
990}
991
992/**
993 * Calculates coordinates from elements inside areas
994 *
995 * @param array $map
996 * @param array $areas
997 * @param array $mapInfo
998 */
999function processAreasCoordinates(array &$map, array $areas, array $mapInfo) {
1000	foreach ($areas as $area) {
1001		$rowPlaceCount = ceil(sqrt(count($area['selementids'])));
1002
1003		// offset from area borders
1004		$area['x'] += 5;
1005		$area['y'] += 5;
1006		$area['width'] -= 5;
1007		$area['height'] -= 5;
1008
1009		$xOffset = floor($area['width'] / $rowPlaceCount);
1010		$yOffset = floor($area['height'] / $rowPlaceCount);
1011
1012		$colNum = 0;
1013		$rowNum = 0;
1014		// some offset is required so that icon highlights are not drawn outside area
1015		$borderOffset = 20;
1016		foreach ($area['selementids'] as $selementId) {
1017			$selement = $map['selements'][$selementId];
1018
1019			$image = get_png_by_selement($mapInfo[$selementId]);
1020			$iconX = imagesx($image);
1021			$iconY = imagesy($image);
1022
1023			$labelLocation = (is_null($selement['label_location']) || ($selement['label_location'] < 0))
1024				? $map['label_location'] : $selement['label_location'];
1025			switch ($labelLocation) {
1026				case MAP_LABEL_LOC_TOP:
1027					$newX = $area['x'] + ($xOffset / 2) - ($iconX / 2);
1028					$newY = $area['y'] + $yOffset - $iconY - ($iconY >= $iconX ? 0 : abs($iconX - $iconY) / 2) - $borderOffset;
1029					break;
1030				case MAP_LABEL_LOC_LEFT:
1031					$newX = $area['x'] + $xOffset - $iconX - $borderOffset;
1032					$newY = $area['y'] + ($yOffset / 2) - ($iconY / 2);
1033					break;
1034				case MAP_LABEL_LOC_RIGHT:
1035					$newX = $area['x'] + $borderOffset;
1036					$newY = $area['y'] + ($yOffset / 2) - ($iconY / 2);
1037					break;
1038				case MAP_LABEL_LOC_BOTTOM:
1039					$newX = $area['x'] + ($xOffset / 2) - ($iconX / 2);
1040					$newY = $area['y'] + abs($iconX - $iconY) / 2 + $borderOffset;
1041					break;
1042			}
1043
1044			$map['selements'][$selementId]['x'] = $newX + ($colNum * $xOffset);
1045			$map['selements'][$selementId]['y'] = $newY + ($rowNum * $yOffset);
1046
1047			$colNum++;
1048			if ($colNum == $rowPlaceCount) {
1049				$colNum = 0;
1050				$rowNum++;
1051			}
1052		}
1053	}
1054}
1055
1056/**
1057 * Calculates area connector point on area perimeter
1058 *
1059 * @param int $ax      x area coordinate
1060 * @param int $ay      y area coordinate
1061 * @param int $aWidth  area width
1062 * @param int $aHeight area height
1063 * @param int $x2      x coordinate of connector second element
1064 * @param int $y2      y coordinate of connector second element
1065 *
1066 * @return array contains two values, x and y coordinates of new area connector point
1067 */
1068function calculateMapAreaLinkCoord($ax, $ay, $aWidth, $aHeight, $x2, $y2) {
1069	$dY = abs($y2 - $ay);
1070	$dX = abs($x2 - $ax);
1071
1072	$halfHeight = $aHeight / 2;
1073	$halfWidth = $aWidth / 2;
1074
1075	if ($dY == 0) {
1076		$ay = $y2;
1077		$ax = ($x2 < $ax) ? $ax - $halfWidth : $ax + $halfWidth;
1078	}
1079	elseif ($dX == 0) {
1080		$ay = ($y2 > $ay) ? $ay + $halfHeight : $ay - $halfHeight;
1081		$ax = $x2;
1082	}
1083	else {
1084		$koef = $halfHeight / $dY;
1085
1086		$c = $dX * $koef;
1087
1088		// If point is further than area diagonal, we should use calculations with width instead of height.
1089		if (($halfHeight / $c) > ($halfHeight / $halfWidth)) {
1090			$ay = ($y2 > $ay) ? $ay + $halfHeight : $ay - $halfHeight;
1091			$ax = ($x2 < $ax) ? $ax - $c : $ax + $c;
1092		}
1093		else {
1094			$koef = $halfWidth / $dX;
1095
1096			$c = $dY * $koef;
1097
1098			$ay = ($y2 > $ay) ? $ay + $c : $ay - $c;
1099			$ax = ($x2 < $ax) ? $ax - $halfWidth : $ax + $halfWidth;
1100		}
1101	}
1102
1103	return [$ax, $ay];
1104}
1105
1106/**
1107 * Get icon id by mapping.
1108 *
1109 * @param array $icon_map
1110 * @param array $host
1111 * @param int   $host['inventory_mode']
1112 * @param array $host['inventory']
1113 *
1114 * @return int
1115 */
1116function getIconByMapping(array $icon_map, array $host) {
1117	if ($host['inventory_mode'] == HOST_INVENTORY_DISABLED) {
1118		return $icon_map['default_iconid'];
1119	}
1120
1121	$inventories = getHostInventories();
1122
1123	foreach ($icon_map['mappings'] as $mapping) {
1124		try {
1125			$expr = new CGlobalRegexp($mapping['expression']);
1126			if ($expr->match($host['inventory'][$inventories[$mapping['inventory_link']]['db_field']])) {
1127				return $mapping['iconid'];
1128			}
1129		}
1130		catch(Exception $e) {
1131			continue;
1132		}
1133	}
1134
1135	return $icon_map['default_iconid'];
1136}
1137
1138/**
1139 * Get parent maps for current map.
1140 *
1141 * @param int $sysmapid
1142 *
1143 * @return array
1144 */
1145function get_parent_sysmaps($sysmapid) {
1146	$db_sysmaps_elements = DBselect(
1147		'SELECT DISTINCT se.sysmapid'.
1148		' FROM sysmaps_elements se'.
1149		' WHERE '.dbConditionInt('se.elementtype', [SYSMAP_ELEMENT_TYPE_MAP]).
1150			' AND '.dbConditionInt('se.elementid', [$sysmapid])
1151	);
1152
1153	$sysmapids = [];
1154
1155	while ($db_sysmaps_element = DBfetch($db_sysmaps_elements)) {
1156		$sysmapids[] = $db_sysmaps_element['sysmapid'];
1157	}
1158
1159	if ($sysmapids) {
1160		$sysmaps = API::Map()->get([
1161			'output' => ['sysmapid', 'name'],
1162			'sysmapids' => $sysmapids
1163		]);
1164
1165		CArrayHelper::sort($sysmaps, ['name']);
1166
1167		return $sysmaps;
1168	}
1169
1170	return [];
1171}
1172
1173/**
1174 * Get labels for map elements.
1175 *
1176 * @param array $map       Sysmap data array.
1177 * @param array $map_info  Array of selements (@see getSelementsInfo).
1178 *
1179 * @return array
1180 */
1181function getMapLabels($map, $map_info) {
1182	$selements = $map['selements'];
1183
1184	// Collect labels for each map element and apply appropriate values.
1185	$labels = [];
1186	foreach ($selements as $selementId => $selement) {
1187		if ($selement['permission'] < PERM_READ) {
1188			continue;
1189		}
1190		elseif ($selement['label_type'] == MAP_LABEL_TYPE_NOTHING) {
1191			$labels[$selementId] = [];
1192			continue;
1193		}
1194
1195		$label_lines = [];
1196		$msgs = explode("\n", $selement['label']);
1197		foreach ($msgs as $msg) {
1198			$label_lines[] = ['content' => $msg];
1199		}
1200
1201		$status_lines = [];
1202		$element_info = $map_info[$selementId];
1203		if (array_key_exists('info', $element_info)) {
1204			foreach (['problem', 'unack', 'maintenance', 'ok', 'status'] as $caption) {
1205				if (array_key_exists($caption, $element_info['info'])
1206						&& $element_info['info'][$caption]['msg'] !== '') {
1207					$msgs = explode("\n", $element_info['info'][$caption]['msg']);
1208					foreach ($msgs as $msg) {
1209						$status_lines[] = [
1210							'content' => $msg,
1211							'attributes' => [
1212								'fill' => '#'.$element_info['info'][$caption]['color']
1213							]
1214						];
1215					}
1216				}
1217			}
1218		}
1219
1220		if ($selement['label_type'] == MAP_LABEL_TYPE_IP && $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
1221			$label = array_merge([['content' => $selement['label']]], $status_lines);
1222		}
1223		elseif ($selement['label_type'] == MAP_LABEL_TYPE_STATUS) {
1224			$label = $status_lines;
1225		}
1226		elseif ($selement['label_type'] == MAP_LABEL_TYPE_NAME) {
1227			$label = array_merge([['content' => $element_info['name']]], $status_lines);
1228		}
1229		else {
1230			$label = array_merge($label_lines, $status_lines);
1231		}
1232
1233		$labels[$selementId] = $label;
1234	}
1235
1236	return $labels;
1237}
1238
1239/**
1240 * Get map element highlights (information about elements with marks or background).
1241 *
1242 * @param array $map       Sysmap data array.
1243 * @param array $map_info  Array of selements (@see getSelementsInfo).
1244 *
1245 * @return array
1246 */
1247function getMapHighligts(array $map, array $map_info) {
1248	$highlights = [];
1249
1250	foreach ($map['selements'] as $id => $selement) {
1251		if ((($map['highlight'] % 2) != SYSMAP_HIGHLIGHT_ON) || (array_key_exists('elementtype', $selement)
1252				&& $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
1253				&& array_key_exists('elementsubtype', $selement)
1254				&& $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS)) {
1255			$highlights[$id] = null;
1256			continue;
1257		}
1258
1259		$hl_color = null;
1260		$st_color = null;
1261		$element_info = $map_info[$id];
1262
1263		switch ($element_info['icon_type']) {
1264			case SYSMAP_ELEMENT_ICON_ON:
1265				$hl_color = getSeverityColor($element_info['priority']);
1266				break;
1267
1268			case SYSMAP_ELEMENT_ICON_MAINTENANCE:
1269				$st_color = 'FF9933';
1270				break;
1271
1272			case SYSMAP_ELEMENT_ICON_DISABLED:
1273				$st_color = '999999';
1274				break;
1275		}
1276
1277		if (array_key_exists('elementtype', $selement)
1278				&& ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
1279				|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) && $hl_color !== null) {
1280			$st_color = null;
1281		}
1282		elseif ($st_color !== null) {
1283			$hl_color = null;
1284		}
1285
1286		$highlights[$id] = [
1287			'st' =>  $st_color,
1288			'hl' => $hl_color,
1289			'ack' => ($hl_color !== null && array_key_exists('ack', $element_info) && $element_info['ack'])
1290		];
1291	}
1292
1293	return $highlights;
1294}
1295
1296/**
1297 * Get trigger data for all linktriggers.
1298 *
1299 * @param array $sysmap
1300 * @param array $sysmap['show_suppressed']  Whether to show suppressed problems.
1301 * @param array $sysmap['show_unack']       Property specified in sysmap's 'Problem display' field. Used to determine
1302 *                                          whether to show unacknowledged problems only.
1303 * @param array $options                    Options used to retrieve actions.
1304 * @param int   $options['severity_min']    Minimal severity used.
1305 *
1306 * @return array
1307 */
1308function getMapLinkTriggerInfo($sysmap, $options) {
1309	if (!array_key_exists('severity_min', $options)) {
1310		$options['severity_min'] = TRIGGER_SEVERITY_NOT_CLASSIFIED;
1311	}
1312
1313	$triggerids = [];
1314
1315	foreach ($sysmap['links'] as $link) {
1316		foreach ($link['linktriggers'] as $linktrigger) {
1317			$triggerids[$linktrigger['triggerid']] = true;
1318		}
1319	}
1320
1321	$trigger_options = [
1322		'output' => ['status', 'value', 'priority'],
1323		'triggerids' => array_keys($triggerids),
1324		'monitored' => true,
1325		'preservekeys' => true
1326	];
1327
1328	$problem_options = [
1329		'show_suppressed' => $sysmap['show_suppressed'],
1330		'acknowledged' => ($sysmap['show_unack'] == EXTACK_OPTION_UNACK) ? false : null
1331	];
1332
1333	return getTriggersWithActualSeverity($trigger_options, $problem_options);
1334}
1335
1336/**
1337 * Get map selement label color based on problem and acknowledgement state
1338 * as well as taking custom event status color settings into account.
1339 *
1340 * @throws APIException if the given table does not exist
1341 *
1342 * @param bool $is_problem
1343 * @param bool $is_ack
1344 *
1345 * @return string
1346 */
1347function getSelementLabelColor($is_problem, $is_ack) {
1348	static $schema = null;
1349
1350	if ($schema === null) {
1351		$schema = DB::getSchema('config');
1352	}
1353
1354	if ($is_problem) {
1355		$param = $is_ack ? CSettingsHelper::PROBLEM_ACK_COLOR : CSettingsHelper::PROBLEM_UNACK_COLOR;
1356	}
1357	else {
1358		$param = $is_ack ? CSettingsHelper::OK_ACK_COLOR : CSettingsHelper::OK_UNACK_COLOR;
1359	}
1360
1361	if (CSettingsHelper::get(CSettingsHelper::CUSTOM_COLOR) === '1') {
1362		return CSettingsHelper::get($param);
1363	}
1364
1365	return $schema['fields'][$param]['default'];
1366}
1367
1368/**
1369 * Filter problems by given tags.
1370 *
1371 * @param array  $problems
1372 * @param array  $problems[]['tags']
1373 * @param string $problems[]['tags'][]['tag']
1374 * @param string $problems[]['tags'][]['value']
1375 * @param array  $filter_tags
1376 * @param string $filter_tags[]['tag']
1377 * @param string $filter_tags[]['value']
1378 * @param int    $filter_tags[]['operator']
1379 * @param int    $evaltype
1380 *
1381 * @return array
1382 */
1383function getProblemsMatchingTags(array $problems, array $filter_tags, int $evaltype): array {
1384	if (!$problems) {
1385		return [];
1386	}
1387
1388	$tags = [];
1389	foreach ($filter_tags as $tag) {
1390		$tags[$tag['tag']][] = $tag;
1391	}
1392
1393	$filtered_problems = [];
1394	foreach ($problems as $problem) {
1395		$matching_tags = array_fill_keys(array_keys($tags), 0);
1396		array_walk($matching_tags, function (&$match, $key) use ($tags, $problem) {
1397			foreach ($tags[$key] as $tag) {
1398				if (checkIfProblemTagsMatches($tag, $problem['tags'])) {
1399					$match = 1;
1400					break;
1401				}
1402			}
1403		});
1404
1405		$matching_tags = array_flip($matching_tags);
1406		if ($evaltype == TAG_EVAL_TYPE_OR && array_key_exists(1, $matching_tags)) {
1407			$filtered_problems[] = $problem;
1408		}
1409		elseif ($evaltype == TAG_EVAL_TYPE_AND_OR && !array_key_exists(0, $matching_tags)) {
1410			$filtered_problems[] = $problem;
1411		}
1412	}
1413
1414	return $filtered_problems;
1415}
1416
1417/**
1418 * Check if $filter_tag matches one of tags in $tags array.
1419 *
1420 * @param array  $filter_tag
1421 * @param string $filter_tag['tag']
1422 * @param string $filter_tag['value']
1423 * @param int    $filter_tag['operator']
1424 * @param array  $tags
1425 * @param string $tags[]['tag']
1426 * @param string $tags[]['value']
1427 *
1428 * @return bool
1429 */
1430function checkIfProblemTagsMatches(array $filter_tag, array $tags): bool {
1431	if (in_array($filter_tag['operator'], [TAG_OPERATOR_NOT_LIKE, TAG_OPERATOR_NOT_EQUAL, TAG_OPERATOR_NOT_EXISTS])
1432			&& !$tags) {
1433		return true;
1434	}
1435
1436	if ($filter_tag['operator'] == TAG_OPERATOR_NOT_LIKE && $filter_tag['value'] === '') {
1437		$filter_tag['operator'] = TAG_OPERATOR_NOT_EXISTS;
1438	}
1439	elseif ($filter_tag['operator'] == TAG_OPERATOR_LIKE && $filter_tag['value'] === '') {
1440		$filter_tag['operator'] = TAG_OPERATOR_EXISTS;
1441	}
1442
1443	switch ($filter_tag['operator']) {
1444		case TAG_OPERATOR_LIKE:
1445			foreach ($tags as $tag) {
1446				if ($filter_tag['tag'] === $tag['tag'] && mb_stripos($tag['value'], $filter_tag['value']) !== false) {
1447					return true;
1448				}
1449			}
1450			break;
1451
1452		case TAG_OPERATOR_EQUAL:
1453			foreach ($tags as $tag) {
1454				if ($filter_tag['tag'] === $tag['tag'] && $filter_tag['value'] === $tag['value']) {
1455					return true;
1456				}
1457			}
1458			break;
1459
1460		case TAG_OPERATOR_NOT_LIKE:
1461			$tags_count = count($tags);
1462			$tags = array_filter($tags, function ($tag) use ($filter_tag) {
1463				return !($filter_tag['tag'] === $tag['tag']
1464					&& mb_stripos($tag['value'], $filter_tag['value']) !== false
1465				);
1466			});
1467			return (count($tags) == $tags_count);
1468
1469		case TAG_OPERATOR_NOT_EQUAL:
1470			$tags_count = count($tags);
1471			$tags = array_filter($tags, function ($tag) use ($filter_tag) {
1472				return !($filter_tag['tag'] === $tag['tag'] && $filter_tag['value'] === $tag['value']);
1473			});
1474			return (count($tags) == $tags_count);
1475
1476		case TAG_OPERATOR_EXISTS:
1477			return array_key_exists($filter_tag['tag'], zbx_toHash($tags, 'tag'));
1478
1479		case TAG_OPERATOR_NOT_EXISTS:
1480			return !array_key_exists($filter_tag['tag'], zbx_toHash($tags, 'tag'));
1481	}
1482
1483	return false;
1484}
1485