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 * Create map area with submenu for sysmap elements.
66 * In submenu gathered information about urls, scripts and submaps.
67 *
68 * @param array $sysmap
69 * @param array $options
70 * @param int	$options['severity_min']
71 *
72 * @return CAreaMap
73 */
74function getActionMapBySysmap($sysmap, array $options = []) {
75	$sysmap['selements'] = zbx_toHash($sysmap['selements'], 'selementid');
76	$sysmap['links'] = zbx_toHash($sysmap['links'], 'linkid');
77
78	$actionMap = new CAreaMap('links'.$sysmap['sysmapid']);
79
80	$areas = populateFromMapAreas($sysmap);
81	$mapInfo = getSelementsInfo($sysmap, $options);
82	processAreasCoordinates($sysmap, $areas, $mapInfo);
83
84	$hostIds = [];
85	$triggerIds = [];
86	$host_groupids = [];
87
88	foreach ($sysmap['selements'] as $id => &$selement) {
89		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
90			$hostIds[$selement['elementid']] = $selement['elementid'];
91
92			// expanding host URL macros again as some hosts were added from hostgroup areas
93			// and automatic expanding only happens for elements that are defined for map in db
94			foreach ($selement['urls'] as $urlId => $url) {
95				$selement['urls'][$urlId]['url'] = str_replace('{HOST.ID}', $selement['elementid'], $url['url']);
96			}
97		}
98		elseif ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
99			$triggerIds[$selement['elementid']] = $selement['elementid'];
100		}
101		elseif ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP) {
102			$host_groupids[$selement['elementid']] = $selement['elementid'];
103		}
104
105		if ($selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
106			unset($sysmap['selements'][$id]);
107		}
108	}
109	unset($selement);
110
111	$hostScripts = API::Script()->getScriptsByHosts($hostIds);
112
113	$hosts = API::Host()->get([
114		'hostids' => $hostIds,
115		'output' => ['hostid', 'status'],
116		'nopermissions' => true,
117		'preservekeys' => true,
118		'selectGraphs' => API_OUTPUT_COUNT,
119		'selectScreens' => API_OUTPUT_COUNT
120	]);
121
122	$monitored_triggers_hosts = API::Host()->get([
123		'output' => ['hostid'],
124		'hostids' => $hostIds,
125		'with_monitored_triggers' => true,
126		'preservekeys' => true,
127		'nopermissions' => true
128	]);
129
130	$triggers = API::Trigger()->get([
131		'output' => [],
132		'triggerids' => $triggerIds,
133		'preservekeys' => true,
134		'nopermissions' => true
135	]);
136
137	$moniored_triggers = API::Trigger()->get([
138		'output' => [],
139		'triggerids' => array_keys($triggers),
140		'monitored' => true,
141		'nopermissions' => true,
142		'preservekeys' => true
143	]);
144
145	$host_groups = API::HostGroup()->get([
146		'output' => ['groupid'],
147		'groupids' => $host_groupids,
148		'with_monitored_triggers' => true,
149		'preservekeys' => true,
150		'nopermissions' => true
151	]);
152
153	foreach ($sysmap['selements'] as $elem) {
154		$back = get_png_by_selement($mapInfo[$elem['selementid']]);
155		$area = new CArea(
156			[
157				$elem['x'],
158				$elem['y'],
159				$elem['x'] + imagesx($back),
160				$elem['y'] + imagesy($back)
161			],
162			'', '', 'rect'
163		);
164		$area->addClass('menu-map');
165
166		$hostId = null;
167		$scripts = null;
168		$gotos = null;
169
170		switch ($elem['elementtype']) {
171			case SYSMAP_ELEMENT_TYPE_HOST:
172				$host = $hosts[$elem['elementid']];
173
174				if ($hostScripts[$elem['elementid']]) {
175					$hostId = $elem['elementid'];
176					$scripts = $hostScripts[$elem['elementid']];
177				}
178
179				$gotos['triggerStatus'] = [
180					'hostid' => $elem['elementid'],
181					'show_severity' => isset($options['severity_min']) ? $options['severity_min'] : null
182				];
183				$gotos['showTriggers'] = ($host['status'] == HOST_STATUS_MONITORED
184						&& array_key_exists($elem['elementid'], $monitored_triggers_hosts));
185
186				$gotos['graphs'] = ['hostid' => $host['hostid']];
187				$gotos['showGraphs'] = (bool) $host['graphs'];
188
189				$gotos['screens'] = ['hostid' => $host['hostid']];
190				$gotos['showScreens'] = (bool) $host['screens'];
191
192				$gotos['inventory'] = ['hostid' => $host['hostid']];
193
194				$gotos['latestData'] = ['hostids' => [$host['hostid']]];
195				break;
196
197			case SYSMAP_ELEMENT_TYPE_MAP:
198				$gotos['submap'] = [
199					'sysmapid' => $elem['elementid'],
200					'severity_min' => isset($options['severity_min']) ? $options['severity_min'] : null,
201					'fullscreen' => array_key_exists('fullscreen', $options) ? $options['fullscreen'] : 0
202				];
203				break;
204
205			case SYSMAP_ELEMENT_TYPE_TRIGGER:
206				$gotos['showEvents'] = false;
207
208				if (array_key_exists($elem['elementid'], $triggers)) {
209					if (array_key_exists($elem['elementid'], $moniored_triggers)) {
210						$gotos['showEvents'] = true;
211					}
212
213					$gotos['events']['triggerid'] = $elem['elementid'];
214				}
215				break;
216
217			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
218				$gotos['triggerStatus'] = [
219					'groupid' => $elem['elementid'],
220					'hostid' => 0,
221					'show_severity' => isset($options['severity_min']) ? $options['severity_min'] : null
222				];
223
224				// always show active trigger link for host group map elements
225				$gotos['showTriggers'] = array_key_exists($elem['elementid'], $host_groups);
226				break;
227		}
228
229		order_result($elem['urls'], 'name');
230
231		$area->setMenuPopup(CMenuPopupHelper::getMap($hostId, $scripts, $gotos, $elem['urls']));
232
233		$actionMap->addItem($area);
234	}
235
236	return $actionMap;
237}
238
239function get_icon_center_by_selement($element, $info, $map) {
240	$x = $element['x'];
241	$y = $element['y'];
242
243	if (isset($element['elementsubtype']) && $element['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
244		if ($element['areatype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
245			$w = $element['width'];
246			$h = $element['height'];
247		}
248		else {
249			$w = $map['width'];
250			$h = $map['height'];
251		}
252	}
253	else {
254		$image = get_png_by_selement($info);
255		$w = imagesx($image);
256		$h = imagesy($image);
257	}
258
259	$x += $w / 2;
260	$y += $h / 2;
261
262	return [$x, $y];
263}
264
265function myDrawLine($image, $x1, $y1, $x2, $y2, $color, $drawtype) {
266	if ($drawtype == MAP_LINK_DRAWTYPE_BOLD_LINE) {
267		zbx_imagealine($image, $x1, $y1, $x2, $y2, $color, LINE_TYPE_BOLD);
268	}
269	elseif ($drawtype == MAP_LINK_DRAWTYPE_DASHED_LINE) {
270		if (function_exists('imagesetstyle')) {
271			// use imagesetstyle + imageline instead of bugged ImageDashedLine
272			$style = [
273				$color, $color, $color, $color,
274				IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT
275			];
276			imagesetstyle($image, $style);
277			zbx_imageline($image, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED);
278		}
279		else {
280			imagedashedline($image, $x1, $y1, $x2, $y2, $color);
281		}
282	}
283	elseif ($drawtype == MAP_LINK_DRAWTYPE_DOT && function_exists('imagesetstyle')) {
284		$style = [$color, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT, IMG_COLOR_TRANSPARENT];
285		imagesetstyle($image, $style);
286		zbx_imageline($image, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED);
287	}
288	else {
289		zbx_imagealine($image, $x1, $y1, $x2, $y2, $color);
290	}
291}
292
293function get_png_by_selement($info) {
294	$image = get_image_by_imageid($info['iconid']);
295
296	return $image['image'] ? imagecreatefromstring($image['image']) : get_default_image();
297}
298
299function get_map_elements($db_element, &$elements) {
300	switch ($db_element['elementtype']) {
301		case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
302			$elements['hosts_groups'][] = $db_element['elementid'];
303			break;
304		case SYSMAP_ELEMENT_TYPE_HOST:
305			$elements['hosts'][] = $db_element['elementid'];
306			break;
307		case SYSMAP_ELEMENT_TYPE_TRIGGER:
308			$elements['triggers'][] = $db_element['elementid'];
309			break;
310		case SYSMAP_ELEMENT_TYPE_MAP:
311			$db_mapselements = DBselect(
312				'SELECT DISTINCT se.elementtype,se.elementid'.
313				' FROM sysmaps_elements se'.
314				' WHERE se.sysmapid='.zbx_dbstr($db_element['elementid'])
315			);
316			while ($db_mapelement = DBfetch($db_mapselements)) {
317				get_map_elements($db_mapelement, $elements);
318			}
319			break;
320	}
321}
322
323/**
324 * Adds names to elements. Adds expression for SYSMAP_ELEMENT_TYPE_TRIGGER elements.
325 *
326 * @param type $selements
327 */
328function add_elementNames(&$selements) {
329	$hostids = [];
330	$triggerids = [];
331	$mapids = [];
332	$hostgroupids = [];
333	$imageids = [];
334
335	foreach ($selements as $selement) {
336		switch ($selement['elementtype']) {
337			case SYSMAP_ELEMENT_TYPE_HOST:
338				$hostids[$selement['elementid']] = $selement['elementid'];
339				break;
340			case SYSMAP_ELEMENT_TYPE_MAP:
341				$mapids[$selement['elementid']] = $selement['elementid'];
342				break;
343			case SYSMAP_ELEMENT_TYPE_TRIGGER:
344				$triggerids[$selement['elementid']] = $selement['elementid'];
345				break;
346			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
347				$hostgroupids[$selement['elementid']] = $selement['elementid'];
348				break;
349			case SYSMAP_ELEMENT_TYPE_IMAGE:
350				$imageids[$selement['iconid_off']] = $selement['iconid_off'];
351				break;
352		}
353	}
354
355	$hosts = API::Host()->get([
356		'hostids' => $hostids,
357		'output' => ['name'],
358		'nopermissions' => true,
359		'preservekeys' => true
360	]);
361
362	$maps = API::Map()->get([
363		'mapids' => $mapids,
364		'output' => ['name'],
365		'nopermissions' => true,
366		'preservekeys' => true
367	]);
368
369	$triggers = API::Trigger()->get([
370		'triggerids' => $triggerids,
371		'output' => API_OUTPUT_EXTEND,
372		'selectHosts' => ['hostid', 'name'],
373		'nopermissions' => true,
374		'preservekeys' => true
375	]);
376
377	$hostgroups = API::HostGroup()->get([
378		'hostgroupids' => $hostgroupids,
379		'output' => ['name'],
380		'nopermissions' => true,
381		'preservekeys' => true
382	]);
383
384	$images = API::image()->get([
385		'imageids' => $imageids,
386		'output' => API_OUTPUT_EXTEND,
387		'nopermissions' => true,
388		'preservekeys' => true
389	]);
390
391	foreach ($selements as $snum => $selement) {
392		switch ($selement['elementtype']) {
393			case SYSMAP_ELEMENT_TYPE_HOST:
394				$selements[$snum]['elementName'] = $hosts[$selement['elementid']]['name'];
395				break;
396			case SYSMAP_ELEMENT_TYPE_MAP:
397				$selements[$snum]['elementName'] = $maps[$selement['elementid']]['name'];
398				break;
399			case SYSMAP_ELEMENT_TYPE_TRIGGER:
400				$hostname = reset($triggers[$selement['elementid']]['hosts']);
401				$selements[$snum]['elementName'] = $hostname['name'].NAME_DELIMITER.
402					CMacrosResolverHelper::resolveTriggerName($triggers[$selement['elementid']]);
403				break;
404			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
405				$selements[$snum]['elementName'] = $hostgroups[$selement['elementid']]['name'];
406				break;
407			case SYSMAP_ELEMENT_TYPE_IMAGE:
408				if (isset($images[$selement['iconid_off']]['name'])) {
409					$selements[$snum]['elementName'] = $images[$selement['iconid_off']]['name'];
410				}
411				break;
412		}
413	}
414
415	if (!empty($triggers)) {
416		add_triggerExpressions($selements, $triggers);
417	}
418}
419
420function add_triggerExpressions(&$selements, $triggers = []) {
421	if (empty($triggers)) {
422		$triggerIds = [];
423
424		foreach ($selements as $selement) {
425			if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
426				$triggerIds[] = $selement['elementid'];
427			}
428		}
429
430		$triggers = API::Trigger()->get([
431			'triggerids' => $triggerIds,
432			'output' => API_OUTPUT_EXTEND,
433			'selectHosts' => ['name'],
434			'nopermissions' => true,
435			'preservekeys' => true
436		]);
437	}
438
439	foreach ($selements as $snum => $selement) {
440		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
441			$selements[$snum]['elementExpressionTrigger'] = $triggers[$selement['elementid']]['expression'];
442		}
443	}
444}
445
446/**
447 * Returns trigger element icon rendering parameters.
448 *
449 * @param $selement
450 * @param $i
451 * @param $showUnack    map "problem display" parameter
452 *
453 * @return array
454 */
455function getTriggersInfo($selement, $i, $showUnack) {
456	$info = [
457		'latelyChanged' => $i['latelyChanged'],
458		'ack' => $i['ack'],
459		'priority' => $i['priority'],
460		'info' => [],
461		'iconid' => $selement['iconid_off']
462	];
463
464	if ($i['problem'] && ($i['problem_unack'] && $showUnack == EXTACK_OPTION_UNACK
465			|| in_array($showUnack, [EXTACK_OPTION_ALL, EXTACK_OPTION_BOTH]))) {
466		$info['info']['unack'] = [
467			'msg' => _('PROBLEM'),
468			'color' => ($i['priority'] > 3) ? 'FF0000' : '960000'
469		];
470
471		if (!array_key_exists('maintenance_title', $i)) {
472			$info['iconid'] = $selement['iconid_on'];
473			$info['icon_type'] = SYSMAP_ELEMENT_ICON_ON;
474
475			return $info;
476		}
477	}
478
479	if (array_key_exists('maintenance_title', $i)) {
480		$info['iconid'] = $selement['iconid_maintenance'];
481		$info['icon_type'] = SYSMAP_ELEMENT_ICON_MAINTENANCE;
482		$info['info']['maintenance'] = [
483			'msg' => _('MAINTENANCE').' ('.$i['maintenance_title'].')',
484			'color' => 'EE9600'
485		];
486	}
487	elseif ($i['trigger_disabled']) {
488		$info['iconid'] = $selement['iconid_disabled'];
489		$info['icon_type'] = SYSMAP_ELEMENT_ICON_DISABLED;
490		$info['info']['status'] = [
491			'msg' => _('DISABLED'),
492			'color' => '960000'
493		];
494	}
495	else {
496		$info['iconid'] = $selement['iconid_off'];
497		$info['icon_type'] = SYSMAP_ELEMENT_ICON_OFF;
498		$info['info']['ok'] = [
499			'msg' => _('OK'),
500			'color' => '009600'
501		];
502	}
503
504	return $info;
505}
506
507/**
508 * Returns host element icon rendering parameters.
509 *
510 * @param $selement
511 * @param $i
512 * @param $show_unack    map "problem display" parameter
513 *
514 * @return array
515 */
516function getHostsInfo($selement, $i, $show_unack) {
517	$info = [
518		'latelyChanged' => $i['latelyChanged'],
519		'ack' => $i['ack'],
520		'priority' => $i['priority'],
521		'info' => [],
522		'iconid' => $selement['iconid_off']
523	];
524	$hasProblem = false;
525
526	if ($i['problem']) {
527		if (in_array($show_unack, [EXTACK_OPTION_ALL, EXTACK_OPTION_BOTH])) {
528			if ($i['problem'] > 1) {
529				$msg = $i['problem'].' '._('Problems');
530			}
531			elseif (isset($i['problem_title'])) {
532				$msg = $i['problem_title'];
533			}
534			else {
535				$msg = '1 '._('Problem');
536			}
537
538			$info['info']['problem'] = [
539				'msg' => $msg,
540				'color' => ($i['priority'] > 3) ? 'FF0000' : '960000'
541			];
542		}
543
544		if (in_array($show_unack, [EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH]) && $i['problem_unack']) {
545			$info['info']['unack'] = [
546				'msg' => $i['problem_unack'].' '._('Unacknowledged'),
547				'color' => '960000'
548			];
549		}
550
551		// set element to problem state if it has problem events
552		if ($info['info']) {
553			$info['iconid'] = $selement['iconid_on'];
554			$info['icon_type'] = SYSMAP_ELEMENT_ICON_ON;
555			$hasProblem = true;
556		}
557	}
558
559	if (array_key_exists('maintenance_title', $i)) {
560		$info['iconid'] = $selement['iconid_maintenance'];
561		$info['icon_type'] = SYSMAP_ELEMENT_ICON_MAINTENANCE;
562		$info['info']['maintenance'] = [
563			'msg' => _('MAINTENANCE').' ('.$i['maintenance_title'].')',
564			'color' => 'EE9600'
565		];
566	}
567	elseif ($i['disabled']) {
568		$info['iconid'] = $selement['iconid_disabled'];
569		$info['icon_type'] = SYSMAP_ELEMENT_ICON_DISABLED;
570		$info['info']['status'] = [
571			'msg' => _('DISABLED'),
572			'color' => '960000'
573		];
574	}
575	elseif (!$hasProblem) {
576		$info['iconid'] = $selement['iconid_off'];
577		$info['icon_type'] = SYSMAP_ELEMENT_ICON_OFF;
578		$info['info']['ok'] = [
579			'msg' => _('OK'),
580			'color' => '009600'
581		];
582	}
583
584	return $info;
585}
586
587/**
588 * Returns host groups element icon rendering parameters.
589 *
590 * @param $selement
591 * @param $i
592 * @param $show_unack    map "problem display" parameter
593 *
594 * @return array
595 */
596function getHostGroupsInfo($selement, $i, $show_unack) {
597	$info = [
598		'latelyChanged' => $i['latelyChanged'],
599		'ack' => $i['ack'],
600		'priority' => $i['priority'],
601		'info' => [],
602		'iconid' => $selement['iconid_off']
603	];
604	$hasProblem = false;
605	$hasStatus = false;
606
607	if ($i['problem']) {
608		if (in_array($show_unack, [EXTACK_OPTION_ALL, EXTACK_OPTION_BOTH])) {
609			if ($i['problem'] > 1) {
610				$msg = $i['problem'].' '._('Problems');
611			}
612			elseif (isset($i['problem_title'])) {
613				$msg = $i['problem_title'];
614			}
615			else {
616				$msg = '1 '._('Problem');
617			}
618
619			$info['info']['problem'] = [
620				'msg' => $msg,
621				'color' => ($i['priority'] > 3) ? 'FF0000' : '960000'
622			];
623		}
624
625		if (in_array($show_unack, [EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH]) && $i['problem_unack']) {
626			$info['info']['unack'] = [
627				'msg' => $i['problem_unack'].' '._('Unacknowledged'),
628				'color' => '960000'
629			];
630		}
631
632		// set element to problem state if it has problem events
633		if ($info['info']) {
634			$info['iconid'] = $selement['iconid_on'];
635			$info['icon_type'] = SYSMAP_ELEMENT_ICON_ON;
636			$hasProblem = true;
637		}
638	}
639
640	if ($i['maintenance']) {
641		if (!$hasProblem) {
642			$info['iconid'] = $selement['iconid_maintenance'];
643			$info['icon_type'] = SYSMAP_ELEMENT_ICON_MAINTENANCE;
644		}
645		$info['info']['maintenance'] = [
646			'msg' => $i['maintenance'].' '._('Maintenance'),
647			'color' => 'EE9600'
648		];
649		$hasStatus = true;
650	}
651
652	if (!$hasStatus && !$hasProblem) {
653		$info['icon_type'] = SYSMAP_ELEMENT_ICON_OFF;
654		$info['iconid'] = $selement['iconid_off'];
655		$info['info']['ok'] = [
656			'msg' => _('OK'),
657			'color' => '009600'
658		];
659	}
660
661	return $info;
662}
663
664/**
665 * Returns maps groups element icon rendering parameters.
666 *
667 * @param $selement
668 * @param $i
669 * @param $show_unack    map "problem display" parameter
670 *
671 * @return array
672 */
673function getMapsInfo($selement, $i, $show_unack) {
674	$info = [
675		'latelyChanged' => $i['latelyChanged'],
676		'ack' => $i['ack'],
677		'priority' => $i['priority'],
678		'info' => [],
679		'iconid' => $selement['iconid_off']
680	];
681
682	$hasProblem = false;
683	$hasStatus = false;
684
685	if ($i['problem']) {
686		if (in_array($show_unack, [EXTACK_OPTION_ALL, EXTACK_OPTION_BOTH])) {
687			if ($i['problem'] > 1) {
688				$msg = $i['problem'].' '._('Problems');
689			}
690			elseif (isset($i['problem_title'])) {
691				$msg = $i['problem_title'];
692			}
693			else {
694				$msg = '1 '._('Problem');
695			}
696
697			$info['info']['problem'] = [
698				'msg' => $msg,
699				'color' => ($i['priority'] > 3) ? 'FF0000' : '960000'
700			];
701		}
702
703		if (in_array($show_unack, [EXTACK_OPTION_UNACK, EXTACK_OPTION_BOTH]) && $i['problem_unack']) {
704			$info['info']['unack'] = [
705				'msg' => $i['problem_unack'].' '._('Unacknowledged'),
706				'color' => '960000'
707			];
708		}
709
710		if ($info['info']) {
711			$info['iconid'] = $selement['iconid_on'];
712			$info['icon_type'] = SYSMAP_ELEMENT_ICON_ON;
713			$hasProblem = true;
714		}
715	}
716
717	if ($i['maintenance']) {
718		if (!$hasProblem) {
719			$info['iconid'] = $selement['iconid_maintenance'];
720			$info['icon_type'] = SYSMAP_ELEMENT_ICON_MAINTENANCE;
721		}
722		$info['info']['maintenance'] = [
723			'msg' => $i['maintenance'].' '._('Maintenance'),
724			'color' => 'EE9600'
725		];
726		$hasStatus = true;
727	}
728
729	if (!$hasStatus && !$hasProblem) {
730		$info['icon_type'] = SYSMAP_ELEMENT_ICON_OFF;
731		$info['iconid'] = $selement['iconid_off'];
732		$info['info']['ok'] = [
733			'msg' => _('OK'),
734			'color' => '009600'
735		];
736	}
737
738	return $info;
739}
740
741function getImagesInfo($selement) {
742	return [
743		'iconid' => $selement['iconid_off'],
744		'icon_type' => SYSMAP_ELEMENT_ICON_OFF,
745		'name' => _('Image'),
746		'latelyChanged' => false
747	];
748}
749
750/**
751 * Prepare map elements data.
752 * Calculate problem triggers and priorities. Populate map elements with automatic icon mapping, acknowledging and
753 * recent change markers.
754 *
755 * @param array $sysmap
756 * @param array $options
757 * @param int   $options['severity_min'] Minimum trigger severity, default value is maximal (Disaster)
758 *
759 * @return array
760 */
761function getSelementsInfo($sysmap, array $options = []) {
762	if (!isset($options['severity_min'])) {
763		$options['severity_min'] = TRIGGER_SEVERITY_NOT_CLASSIFIED;
764	}
765
766	$config = select_config();
767	$showUnacknowledged = $config['event_ack_enable'] ? $sysmap['show_unack'] : EXTACK_OPTION_ALL;
768
769	$triggerIdToSelementIds = [];
770	$subSysmapTriggerIdToSelementIds = [];
771	$hostGroupIdToSelementIds = [];
772	$hostIdToSelementIds = [];
773
774	if ($sysmap['sysmapid']) {
775		$iconMap = API::IconMap()->get([
776			'sysmapids' => $sysmap['sysmapid'],
777			'selectMappings' => API_OUTPUT_EXTEND,
778			'output' => API_OUTPUT_EXTEND
779		]);
780		$iconMap = reset($iconMap);
781
782	}
783	$hostsToGetInventories = [];
784
785	$selements = $sysmap['selements'];
786	$selementIdToSubSysmaps = [];
787	foreach ($selements as $selementId => &$selement) {
788		$selement['hosts'] = [];
789		$selement['triggers'] = [];
790
791		switch ($selement['elementtype']) {
792			case SYSMAP_ELEMENT_TYPE_MAP:
793				$sysmapIds = [$selement['elementid']];
794
795				while (!empty($sysmapIds)) {
796					$subSysmaps = API::Map()->get([
797						'sysmapids' => $sysmapIds,
798						'output' => ['sysmapid'],
799						'selectSelements' => API_OUTPUT_EXTEND,
800						'nopermissions' => true,
801						'preservekeys' => true
802					]);
803
804					if(!isset($selementIdToSubSysmaps[$selementId])) {
805						$selementIdToSubSysmaps[$selementId] = [];
806					}
807					$selementIdToSubSysmaps[$selementId] += $subSysmaps;
808
809					$sysmapIds = [];
810					foreach ($subSysmaps as $subSysmap) {
811						foreach ($subSysmap['selements'] as $subSysmapSelement) {
812							switch ($subSysmapSelement['elementtype']) {
813								case SYSMAP_ELEMENT_TYPE_MAP:
814									$sysmapIds[] = $subSysmapSelement['elementid'];
815									break;
816								case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
817									$hostGroupIdToSelementIds[$subSysmapSelement['elementid']][$selementId] = $selementId;
818									break;
819								case SYSMAP_ELEMENT_TYPE_HOST:
820									$hostIdToSelementIds[$subSysmapSelement['elementid']][$selementId] = $selementId;
821									break;
822								case SYSMAP_ELEMENT_TYPE_TRIGGER:
823									$subSysmapTriggerIdToSelementIds[$subSysmapSelement['elementid']][$selementId] = $selementId;
824									break;
825							}
826						}
827					}
828				}
829				break;
830			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
831				$hostGroupId = $selement['elementid'];
832				$hostGroupIdToSelementIds[$hostGroupId][$selementId] = $selementId;
833				break;
834			case SYSMAP_ELEMENT_TYPE_HOST:
835				$hostId = $selement['elementid'];
836				$hostIdToSelementIds[$hostId][$selementId] = $selementId;
837
838				// if we have icon map applied, we need to get inventories for all hosts,
839				// where automatic icon selection is enabled.
840				if ($sysmap['iconmapid'] && $selement['use_iconmap']) {
841					$hostsToGetInventories[] = $hostId;
842				}
843				break;
844			case SYSMAP_ELEMENT_TYPE_TRIGGER:
845				$triggerId = $selement['elementid'];
846				$triggerIdToSelementIds[$triggerId][$selementId] = $selementId;
847				break;
848		}
849	}
850	unset($selement);
851
852	// get host inventories
853	if ($sysmap['iconmapid']) {
854		$hostInventories = API::Host()->get([
855			'hostids' => $hostsToGetInventories,
856			'output' => ['hostid'],
857			'nopermissions' => true,
858			'preservekeys' => true,
859			'selectInventory' => API_OUTPUT_EXTEND
860		]);
861	}
862
863	$allHosts = [];
864	if (!empty($hostIdToSelementIds)) {
865		$hosts = API::Host()->get([
866			'hostids' => array_keys($hostIdToSelementIds),
867			'output' => ['name', 'status', 'maintenance_status', 'maintenanceid'],
868			'nopermissions' => true,
869			'preservekeys' => true
870		]);
871		$allHosts = array_merge($allHosts, $hosts);
872		foreach ($hosts as $hostId => $host) {
873			foreach ($hostIdToSelementIds[$hostId] as $selementId) {
874				$selements[$selementId]['hosts'][$hostId] = $hostId;
875			}
876		}
877	}
878
879	$hostsFromHostGroups = [];
880	if (!empty($hostGroupIdToSelementIds)) {
881		$hostsFromHostGroups = API::Host()->get([
882			'groupids' => array_keys($hostGroupIdToSelementIds),
883			'output' => ['name', 'status', 'maintenance_status', 'maintenanceid'],
884			'selectGroups' => ['groupid'],
885			'nopermissions' => true,
886			'preservekeys' => true
887		]);
888
889		foreach ($hostsFromHostGroups as $hostId => $host) {
890			foreach ($host['groups'] as $group) {
891				$groupId = $group['groupid'];
892
893				if (isset($hostGroupIdToSelementIds[$groupId])) {
894					foreach ($hostGroupIdToSelementIds[$groupId] as $selementId) {
895						$selement =& $selements[$selementId];
896
897						$selement['hosts'][$hostId] = $hostId;
898
899						// add hosts to hosts_map for trigger selection;
900						if (!isset($hostIdToSelementIds[$hostId])) {
901							$hostIdToSelementIds[$hostId] = [];
902						}
903						$hostIdToSelementIds[$hostId][$selementId] = $selementId;
904
905						unset($selement);
906					}
907				}
908			}
909		}
910
911		$allHosts = array_merge($allHosts, $hostsFromHostGroups);
912	}
913
914	$allHosts = zbx_toHash($allHosts, 'hostid');
915
916	// get triggers data, triggers from current map, select all
917	if (!empty($triggerIdToSelementIds)) {
918		$triggers = API::Trigger()->get([
919			'output' => ['triggerid', 'status', 'value', 'priority', 'lastchange', 'description', 'expression'],
920			'selectHosts' => ['maintenance_status', 'maintenanceid'],
921			'selectLastEvent' => ['acknowledged'],
922			'triggerids' => array_keys($triggerIdToSelementIds),
923			'filter' => ['state' => null],
924			'nopermissions' => true,
925			'preservekeys' => true
926		]);
927
928		$monitored_triggers = API::Trigger()->get([
929			'output' => [],
930			'triggerids' => array_keys($triggers),
931			'monitored' => true,
932			'nopermissions' => true,
933			'preservekeys' => true
934		]);
935
936		foreach ($triggers as $trigger) {
937			if (!array_key_exists($trigger['triggerid'], $monitored_triggers)) {
938				$trigger['status'] = TRIGGER_STATUS_DISABLED;
939			}
940
941			foreach ($triggerIdToSelementIds[$trigger['triggerid']] as $belongs_to_sel) {
942				$selements[$belongs_to_sel]['triggers'][$trigger['triggerid']] = $trigger;
943			}
944		}
945		unset($triggers, $monitored_triggers);
946	}
947
948	// triggers from submaps, skip dependent
949	if (!empty($subSysmapTriggerIdToSelementIds)) {
950		$triggers = API::Trigger()->get([
951			'output' => ['triggerid', 'status', 'value', 'priority', 'lastchange', 'description', 'expression'],
952			'selectLastEvent' => ['acknowledged'],
953			'triggerids' => array_keys($subSysmapTriggerIdToSelementIds),
954			'filter' => ['state' => null],
955			'skipDependent' => true,
956			'nopermissions' => true,
957			'preservekeys' => true,
958			'only_true' => true
959		]);
960
961		$monitored_triggers = API::Trigger()->get([
962			'output' => [],
963			'triggerids' => array_keys($triggers),
964			'monitored' => true,
965			'nopermissions' => true,
966			'preservekeys' => true
967		]);
968
969		foreach ($triggers as $trigger) {
970			foreach ($subSysmapTriggerIdToSelementIds[$trigger['triggerid']] as $belongs_to_sel) {
971				if (!array_key_exists($trigger['triggerid'], $monitored_triggers)) {
972					$trigger['status'] = TRIGGER_STATUS_DISABLED;
973				}
974
975				$selements[$belongs_to_sel]['triggers'][$trigger['triggerid']] = $trigger;
976			}
977		}
978		unset($triggers, $monitored_triggers);
979	}
980
981	$monitored_hostids = [];
982	foreach ($allHosts as $hostid => $host) {
983		if ($host['status'] == HOST_STATUS_MONITORED) {
984			$monitored_hostids[$hostid] = true;
985		}
986	}
987
988	// triggers from all hosts/hostgroups, skip dependent
989	if ($monitored_hostids) {
990		$triggers = API::Trigger()->get([
991			'output' => ['triggerid', 'status', 'value', 'priority', 'lastchange', 'description', 'expression'],
992			'selectHosts' => ['hostid'],
993			'selectItems' => ['itemid'],
994			'selectLastEvent' => ['acknowledged'],
995			'hostids' => array_keys($monitored_hostids),
996			'filter' => ['state' => null],
997			'monitored' => true,
998			'skipDependent' => true,
999			'nopermissions' => true,
1000			'preservekeys' => true,
1001			'only_true' => true
1002		]);
1003
1004		foreach ($triggers as $trigger) {
1005			foreach ($trigger['hosts'] as $host) {
1006				if (isset($hostIdToSelementIds[$host['hostid']])) {
1007					foreach ($hostIdToSelementIds[$host['hostid']] as $belongs_to_sel) {
1008						$selements[$belongs_to_sel]['triggers'][$trigger['triggerid']] = $trigger;
1009					}
1010				}
1011			}
1012		}
1013
1014		$subSysmapHostApplicationFilters = getSelementHostApplicationFilters($selements, $selementIdToSubSysmaps,
1015			$hostsFromHostGroups
1016		);
1017		$selements = filterSysmapTriggers($selements, $subSysmapHostApplicationFilters, $triggers,
1018			$subSysmapTriggerIdToSelementIds
1019		);
1020	}
1021
1022	$info = [];
1023	foreach ($selements as $selementId => $selement) {
1024		$i = [
1025			'disabled' => 0,
1026			'maintenance' => 0,
1027			'problem' => 0,
1028			'problem_unack' => 0,
1029			'priority' => 0,
1030			'trigger_disabled' => 0,
1031			'latelyChanged' => false,
1032			'ack' => true
1033		];
1034
1035		foreach ($selement['hosts'] as $hostId) {
1036			$host = $allHosts[$hostId];
1037			$last_hostid = $hostId;
1038
1039			if ($host['status'] == HOST_STATUS_NOT_MONITORED) {
1040				$i['disabled']++;
1041			}
1042			elseif ($host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) {
1043				$i['maintenance']++;
1044			}
1045		}
1046
1047		$last_event = false;
1048
1049		foreach ($selement['triggers'] as $trigger) {
1050			if ($options['severity_min'] <= $trigger['priority']) {
1051				if ($trigger['status'] == TRIGGER_STATUS_DISABLED) {
1052					$i['trigger_disabled']++;
1053				}
1054				else {
1055					if ($trigger['value'] == TRIGGER_VALUE_TRUE) {
1056						$i['problem']++;
1057						$lastProblemId = $trigger['triggerid'];
1058
1059						if ($i['priority'] < $trigger['priority']) {
1060							$i['priority'] = $trigger['priority'];
1061						}
1062
1063						if ($trigger['lastEvent']) {
1064							if (!$trigger['lastEvent']['acknowledged']) {
1065								$i['problem_unack']++;
1066							}
1067
1068							$last_event = $last_event || true;
1069						}
1070					}
1071
1072					$i['latelyChanged'] |= ((time() - $trigger['lastchange']) < $config['blink_period']);
1073				}
1074			}
1075		}
1076
1077		// If there are no events, problems cannot be unacknowledged. Hide the green line in this case.
1078		$i['ack'] = ($last_event) ? (bool) !($i['problem_unack']) : false;
1079
1080		if ($sysmap['expandproblem'] && $i['problem'] == 1) {
1081			$i['problem_title'] = CMacrosResolverHelper::resolveTriggerName($selement['triggers'][$lastProblemId]);
1082		}
1083
1084		// replace default icons
1085		if (!$selement['iconid_on']) {
1086			$selement['iconid_on'] = $selement['iconid_off'];
1087		}
1088		if (!$selement['iconid_maintenance']) {
1089			$selement['iconid_maintenance'] = $selement['iconid_off'];
1090		}
1091		if (!$selement['iconid_disabled']) {
1092			$selement['iconid_disabled'] = $selement['iconid_off'];
1093		}
1094
1095		switch ($selement['elementtype']) {
1096			case SYSMAP_ELEMENT_TYPE_MAP:
1097				$info[$selementId] = getMapsInfo($selement, $i, $showUnacknowledged);
1098				break;
1099
1100			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
1101				$info[$selementId] = getHostGroupsInfo($selement, $i, $showUnacknowledged);
1102				break;
1103
1104			case SYSMAP_ELEMENT_TYPE_HOST:
1105				if ($i['maintenance'] == 1) {
1106					$mnt = get_maintenance_by_maintenanceid($allHosts[$last_hostid]['maintenanceid']);
1107					$i['maintenance_title'] = $mnt['name'];
1108				}
1109
1110				$info[$selementId] = getHostsInfo($selement, $i, $showUnacknowledged);
1111				if ($sysmap['iconmapid'] && $selement['use_iconmap']) {
1112					$info[$selementId]['iconid'] = getIconByMapping($iconMap, $hostInventories[$selement['elementid']]);
1113				}
1114				break;
1115
1116			case SYSMAP_ELEMENT_TYPE_TRIGGER:
1117				foreach ($trigger['hosts'] as $host) {
1118					if (array_key_exists('maintenance_status', $host)
1119							&& $host['maintenance_status'] == HOST_MAINTENANCE_STATUS_ON) {
1120						$maintenance = get_maintenance_by_maintenanceid($host['maintenanceid']);
1121						$i['maintenance_title'] = $maintenance['name'];
1122
1123						break;
1124					}
1125				}
1126
1127				$info[$selementId] = getTriggersInfo($selement, $i, $showUnacknowledged);
1128				break;
1129
1130			case SYSMAP_ELEMENT_TYPE_IMAGE:
1131				$info[$selementId] = getImagesInfo($selement);
1132				break;
1133		}
1134	}
1135
1136	if ($sysmap['label_format'] == SYSMAP_LABEL_ADVANCED_OFF) {
1137		$hlabel = $hglabel = $tlabel = $mlabel = ($sysmap['label_type'] == MAP_LABEL_TYPE_NAME);
1138	}
1139	else {
1140		$hlabel = ($sysmap['label_type_host'] == MAP_LABEL_TYPE_NAME);
1141		$hglabel = ($sysmap['label_type_hostgroup'] == MAP_LABEL_TYPE_NAME);
1142		$tlabel = ($sysmap['label_type_trigger'] == MAP_LABEL_TYPE_NAME);
1143		$mlabel = ($sysmap['label_type_map'] == MAP_LABEL_TYPE_NAME);
1144	}
1145
1146	// get names if needed
1147	$elems = separateMapElements($sysmap);
1148	if (!empty($elems['sysmaps']) && $mlabel) {
1149		$subSysmaps = API::Map()->get([
1150			'sysmapids' => zbx_objectValues($elems['sysmaps'], 'elementid'),
1151			'nopermissions' => true,
1152			'output' => ['name']
1153		]);
1154		$subSysmaps = zbx_toHash($subSysmaps, 'sysmapid');
1155
1156		foreach ($elems['sysmaps'] as $elem) {
1157			$info[$elem['selementid']]['name'] = $subSysmaps[$elem['elementid']]['name'];
1158		}
1159	}
1160	if (!empty($elems['hostgroups']) && $hglabel) {
1161		$hostgroups = API::HostGroup()->get([
1162			'groupids' => zbx_objectValues($elems['hostgroups'], 'elementid'),
1163			'nopermissions' => true,
1164			'output' => ['name']
1165		]);
1166		$hostgroups = zbx_toHash($hostgroups, 'groupid');
1167
1168		foreach ($elems['hostgroups'] as $elem) {
1169			$info[$elem['selementid']]['name'] = $hostgroups[$elem['elementid']]['name'];
1170		}
1171	}
1172
1173	if (!empty($elems['triggers']) && $tlabel) {
1174		foreach ($elems['triggers'] as $selementid => $elem) {
1175			$trigger = $selements[$selementid]['triggers'][$elem['elementid']];
1176			$info[$elem['selementid']]['name'] = CMacrosResolverHelper::resolveTriggerName($trigger);
1177		}
1178	}
1179	if (!empty($elems['hosts']) && $hlabel) {
1180		foreach ($elems['hosts'] as $elem) {
1181			$info[$elem['selementid']]['name'] = $allHosts[$elem['elementid']]['name'];
1182		}
1183	}
1184
1185	return $info;
1186}
1187
1188/**
1189 * Takes sysmap selements array, applies filtering by application to triggers and returns sysmap selements array.
1190 *
1191 * @param array $selements                          selements of current sysmap
1192 * @param array $selementHostApplicationFilters     a list of application filters applied to each host under each element
1193 *                                                  @see getSelementHostApplicationFilters()
1194 * @param array $triggersFromMonitoredHosts         triggers that are relevant to filtering
1195 * @param array $subSysmapTriggerIdToSelementIds    a map of triggers in sysmaps to selement IDs
1196 *
1197 * @return array
1198 */
1199function filterSysmapTriggers(array $selements, array $selementHostApplicationFilters, array $triggersFromMonitoredHosts, array $subSysmapTriggerIdToSelementIds) {
1200	// pick only host, host group or map selements
1201	$filterableSelements = [];
1202	foreach ($selements as $selementId => $selement) {
1203		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST
1204				|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP
1205				|| $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_MAP) {
1206			$filterableSelements[$selementId] = $selement;
1207		}
1208	}
1209	// calculate list of triggers that might get removed from $selement['triggers']
1210	$triggersToFilter = [];
1211	foreach ($filterableSelements as $selementId => $selement) {
1212		foreach ($selement['triggers'] as $trigger) {
1213			if (!isset($triggersFromMonitoredHosts[$trigger['triggerid']])) {
1214				continue;
1215			}
1216			$trigger = $triggersFromMonitoredHosts[$trigger['triggerid']];
1217			foreach ($trigger['hosts'] as $host) {
1218				$hostId = $host['hostid'];
1219				if (isset($selementHostApplicationFilters[$selementId][$hostId])) {
1220					$triggersToFilter[$trigger['triggerid']] = $trigger;
1221				}
1222			}
1223		}
1224	}
1225
1226	// if there are no triggers to filter
1227	if (!$triggersToFilter) {
1228		return $selements;
1229	}
1230
1231	// produce mapping of trigger to application names it is related to and produce mapping of host to triggers
1232	$itemIds = [];
1233	foreach ($triggersToFilter as $trigger) {
1234		foreach ($trigger['items'] as $item) {
1235			$itemIds[$item['itemid']] = $item['itemid'];
1236		}
1237	}
1238	$items = API::Item()->get([
1239		'output' => ['itemid'],
1240		'selectApplications' => ['name'],
1241		'itemids' => $itemIds,
1242		'webitems' => true,
1243		'preservekeys' => true
1244	]);
1245
1246	$triggerApplications = [];
1247	$hostIdToTriggers = [];
1248	foreach ($triggersToFilter as $trigger) {
1249		$triggerId = $trigger['triggerid'];
1250
1251		foreach ($trigger['items'] as $item) {
1252			foreach ($items[$item['itemid']]['applications'] as $application) {
1253				$triggerApplications[$triggerId][$application['name']] = true;
1254			}
1255		}
1256
1257		foreach ($trigger['hosts'] as $host) {
1258			$hostIdToTriggers[$host['hostid']][$triggerId] = $trigger;
1259		}
1260	}
1261
1262	foreach ($filterableSelements as $selementId => &$selement) {
1263		// walk through each host of a submap and apply its filters to all its triggers
1264		foreach ($selement['hosts'] as $hostId) {
1265			// skip hosts that don't have any filters or triggers to filter
1266			if (!isset($hostIdToTriggers[$hostId]) || !isset($selementHostApplicationFilters[$selementId][$hostId])) {
1267				continue;
1268			}
1269
1270			// remove the triggers that don't have applications or don't match the filter
1271			$filteredApplicationNames = $selementHostApplicationFilters[$selementId][$hostId];
1272			foreach ($hostIdToTriggers[$hostId] as $trigger) {
1273				$triggerId = $trigger['triggerid'];
1274
1275				// skip if this trigger is standalone trigger and those are not filtered
1276				if (isset($subSysmapTriggerIdToSelementIds[$triggerId])
1277						&& isset($subSysmapTriggerIdToSelementIds[$triggerId][$selementId])) {
1278					continue;
1279				}
1280
1281				$applicationNamesForTrigger = isset($triggerApplications[$triggerId])
1282					? array_keys($triggerApplications[$triggerId])
1283					: [];
1284
1285				if (!array_intersect($applicationNamesForTrigger, $filteredApplicationNames)) {
1286					unset($selement['triggers'][$triggerId]);
1287				}
1288			}
1289		}
1290	}
1291	unset($selement);
1292
1293	// put back updated selements
1294	foreach ($filterableSelements as $selementId => $selement) {
1295		$selements[$selementId] = $selement;
1296	}
1297
1298	return $selements;
1299}
1300
1301/**
1302 * Returns a list of application filters applied to each host under each element.
1303 *
1304 * @param array $selements                  selements of current sysmap
1305 * @param array $selementIdToSubSysmaps     all sub-sysmaps used in current sysmap, indexed by selementId
1306 * @param array $hostsFromHostGroups        collection of hosts that get included via host groups
1307 *
1308 * @return array    a two-dimensional array with selement IDs as the primary key, host IDs as the secondary key
1309 *                  application names as values
1310 */
1311function getSelementHostApplicationFilters(array $selements, array $selementIdToSubSysmaps, array $hostsFromHostGroups) {
1312	$hostIdsForHostGroupId = [];
1313	foreach ($hostsFromHostGroups as $host) {
1314		$hostId = $host['hostid'];
1315		foreach ($host['groups'] as $group) {
1316			$hostIdsForHostGroupId[$group['groupid']][$hostId] = $hostId;
1317		}
1318	}
1319
1320	$selementHostApplicationFilters = [];
1321	foreach ($selements as $selementId => $selement) {
1322		switch ($selement['elementtype']) {
1323			case SYSMAP_ELEMENT_TYPE_HOST:
1324			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
1325				// skip host and host group elements with an empty filter
1326				if ($selement['application'] === '') {
1327					continue 2;
1328				}
1329
1330				foreach ($selement['hosts'] as $hostId) {
1331					$selementHostApplicationFilters[$selementId][$hostId][] = $selement['application'];
1332				}
1333
1334				break;
1335
1336			case SYSMAP_ELEMENT_TYPE_MAP:
1337				foreach ($selementIdToSubSysmaps[$selementId] as $subSysmap) {
1338					// add all filters set for host elements
1339					foreach ($subSysmap['selements'] as $subSysmapSelement) {
1340						if ($subSysmapSelement['elementtype'] != SYSMAP_ELEMENT_TYPE_HOST
1341								|| $subSysmapSelement['application'] === '') {
1342
1343							continue;
1344						}
1345
1346						$hostId = $subSysmapSelement['elementid'];
1347						$selementHostApplicationFilters[$selementId][$hostId][] = $subSysmapSelement['application'];
1348					}
1349
1350					// Find all selements with host groups and sort them into two arrays:
1351					// - with application filter
1352					// - without application filter
1353					$hostGroupSelementsWithApplication = [];
1354					$hostGroupSelementsWithoutApplication = [];
1355					foreach ($subSysmap['selements'] as $subSysmapSelement) {
1356						if ($subSysmapSelement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST_GROUP) {
1357							if ($subSysmapSelement['application'] !== '') {
1358								$hostGroupSelementsWithApplication[] = $subSysmapSelement;
1359							}
1360							else {
1361								$hostGroupSelementsWithoutApplication[] = $subSysmapSelement;
1362							}
1363						}
1364					}
1365
1366					// Combine application filters for hosts from host group selements with
1367					// application filters set.
1368					foreach ($hostGroupSelementsWithApplication as $hostGroupSelement) {
1369						$hostGroupId = $hostGroupSelement['elementid'];
1370
1371						if (isset($hostIdsForHostGroupId[$hostGroupId])) {
1372							foreach ($hostIdsForHostGroupId[$hostGroupId] as $hostId) {
1373								$selementHostApplicationFilters[$selementId][$hostId][] = $hostGroupSelement['application'];
1374							}
1375						}
1376					}
1377
1378					// Unset all application filters for hosts in host group selements without any filters.
1379					// This might reset application filters set by previous foreach.
1380					foreach ($hostGroupSelementsWithoutApplication AS $hostGroupSelement) {
1381						$hostGroupId = $hostGroupSelement['elementid'];
1382
1383						if (isset($hostIdsForHostGroupId[$hostGroupId])) {
1384							foreach ($hostIdsForHostGroupId[$hostGroupId] as $hostId) {
1385								unset($selementHostApplicationFilters[$selementId][$hostId]);
1386							}
1387						}
1388					}
1389				}
1390
1391				break;
1392		}
1393	}
1394
1395	return $selementHostApplicationFilters;
1396}
1397
1398function separateMapElements($sysmap) {
1399	$elements = [
1400		'sysmaps' => [],
1401		'hostgroups' => [],
1402		'hosts' => [],
1403		'triggers' => [],
1404		'images' => []
1405	];
1406
1407	foreach ($sysmap['selements'] as $selement) {
1408		switch ($selement['elementtype']) {
1409			case SYSMAP_ELEMENT_TYPE_MAP:
1410				$elements['sysmaps'][$selement['selementid']] = $selement;
1411				break;
1412			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
1413				$elements['hostgroups'][$selement['selementid']] = $selement;
1414				break;
1415			case SYSMAP_ELEMENT_TYPE_HOST:
1416				$elements['hosts'][$selement['selementid']] = $selement;
1417				break;
1418			case SYSMAP_ELEMENT_TYPE_TRIGGER:
1419				$elements['triggers'][$selement['selementid']] = $selement;
1420				break;
1421			case SYSMAP_ELEMENT_TYPE_IMAGE:
1422			default:
1423				$elements['images'][$selement['selementid']] = $selement;
1424		}
1425	}
1426	return $elements;
1427}
1428
1429function drawMapConnectors(&$im, $map, $mapInfo, $drawAll = false) {
1430	$selements = $map['selements'];
1431
1432	foreach ($map['links'] as $link) {
1433		$selement1 = $selements[$link['selementid1']];
1434		$selement2 = $selements[$link['selementid2']];
1435
1436		list($x1, $y1) = get_icon_center_by_selement($selement1, $mapInfo[$link['selementid1']], $map);
1437		list($x2, $y2) = get_icon_center_by_selement($selement2, $mapInfo[$link['selementid2']], $map);
1438
1439		if (isset($selement1['elementsubtype']) && $selement1['elementsubtype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1440			if (!$drawAll && ($selement2['elementsubtype'] != SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS)) {
1441				continue;
1442			}
1443
1444			if ($selement1['areatype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1445				$w = $selement1['width'];
1446				$h = $selement1['height'];
1447			}
1448			else {
1449				$w = $map['width'];
1450				$h = $map['height'];
1451			}
1452			list($x1, $y1) = calculateMapAreaLinkCoord($x1, $y1, $w, $h, $x2, $y2);
1453		}
1454
1455		if (isset($selement2['elementsubtype']) && $selement2['elementsubtype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1456			if (!$drawAll && ($selement1['elementsubtype'] != SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS)) {
1457				continue;
1458			}
1459
1460			if ($selement2['areatype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1461				$w = $selement2['width'];
1462				$h = $selement2['height'];
1463			}
1464			else {
1465				$w = $map['width'];
1466				$h = $map['height'];
1467			}
1468			list($x2, $y2) = calculateMapAreaLinkCoord($x2, $y2, $w,$h, $x1, $y1);
1469		}
1470
1471		$drawtype = $link['drawtype'];
1472		$color = get_color($im, $link['color']);
1473
1474		$linktriggers = $link['linktriggers'];
1475		order_result($linktriggers, 'triggerid');
1476
1477		if (!empty($linktriggers)) {
1478			$max_severity = 0;
1479
1480			$triggers = [];
1481			foreach ($linktriggers as $link_trigger) {
1482				if (!$link_trigger['monitored']) {
1483					continue;
1484				}
1485
1486				$id = $link_trigger['linktriggerid'];
1487
1488				$triggers[$id] = zbx_array_merge($link_trigger, get_trigger_by_triggerid($link_trigger['triggerid']));
1489				if ($triggers[$id]['status'] == TRIGGER_STATUS_ENABLED && $triggers[$id]['value'] == TRIGGER_VALUE_TRUE) {
1490					if ($triggers[$id]['priority'] >= $max_severity) {
1491						$drawtype = $triggers[$id]['drawtype'];
1492						$color = get_color($im, $triggers[$id]['color']);
1493						$max_severity = $triggers[$id]['priority'];
1494					}
1495				}
1496			}
1497		}
1498
1499		myDrawLine($im, $x1, $y1, $x2, $y2, $color, $drawtype);
1500	}
1501}
1502
1503function drawMapSelements(&$im, $map, $mapInfo) {
1504	$selements = $map['selements'];
1505
1506	foreach ($selements as $selementId => $selement) {
1507		if (isset($selement['elementsubtype']) && $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
1508			continue;
1509		}
1510
1511		$elementInfo = $mapInfo[$selementId];
1512		$img = get_png_by_selement($elementInfo);
1513
1514		$iconX = imagesx($img);
1515		$iconY = imagesy($img);
1516
1517		imagecopy($im, $img, $selement['x'], $selement['y'], 0, 0, $iconX, $iconY);
1518	}
1519}
1520
1521function drawMapHighligts(&$im, $map, $mapInfo) {
1522	$config = select_config();
1523
1524	$selements = $map['selements'];
1525
1526	foreach ($selements as $selementId => $selement) {
1527		if (isset($selement['elementsubtype']) && $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
1528			continue;
1529		}
1530
1531		$elementInfo = $mapInfo[$selementId];
1532		$img = get_png_by_selement($elementInfo);
1533
1534		$iconX = imagesx($img);
1535		$iconY = imagesy($img);
1536
1537		if (($map['highlight'] % 2) == SYSMAP_HIGHLIGHT_ON) {
1538			$hl_color = null;
1539			$st_color = null;
1540
1541			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_ON) {
1542				$hl_color = getSeverityColor($elementInfo['priority']);
1543			}
1544
1545			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_MAINTENANCE) {
1546				$st_color = 'FF9933';
1547			}
1548			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_DISABLED) {
1549				$st_color = '999999';
1550			}
1551
1552			$mainProblems = [
1553				SYSMAP_ELEMENT_TYPE_HOST_GROUP => 1,
1554				SYSMAP_ELEMENT_TYPE_MAP => 1
1555			];
1556
1557			if (isset($mainProblems[$selement['elementtype']])) {
1558				if ($hl_color !== null) {
1559					$st_color = null;
1560				}
1561			}
1562			elseif (!is_null($st_color)) {
1563				$hl_color = null;
1564			}
1565
1566			if ($st_color !== null) {
1567				imagefilledrectangle($im,
1568					$selement['x'] - 2,
1569					$selement['y'] - 2,
1570					$selement['x'] + $iconX + 2,
1571					$selement['y'] + $iconY + 2,
1572					get_color($im, $st_color, 50)
1573				);
1574			}
1575
1576			if ($hl_color !== null) {
1577				imagefilledellipse($im,
1578					$selement['x'] + ($iconX / 2),
1579					$selement['y'] + ($iconY / 2),
1580					$iconX + 20,
1581					$iconX + 20,
1582					get_color($im, $hl_color)
1583				);
1584
1585				if (isset($elementInfo['ack']) && $elementInfo['ack'] && $config['event_ack_enable']) {
1586					imagesetthickness($im, 5);
1587					imagearc($im,
1588						$selement['x'] + ($iconX / 2),
1589						$selement['y'] + ($iconY / 2),
1590						$iconX + 20 - 2,
1591						$iconX + 20 - 2,
1592						0,
1593						359,
1594						imagecolorallocate($im, 50, 150, 50)
1595					);
1596					imagesetthickness($im, 1);
1597				}
1598			}
1599		}
1600	}
1601}
1602
1603function drawMapSelementsMarks(&$im, $map, $mapInfo) {
1604	$selements = $map['selements'];
1605
1606	foreach ($selements as $selementId => $selement) {
1607		if (empty($selement)) {
1608			continue;
1609		}
1610
1611		$elementInfo = $mapInfo[$selementId];
1612		if (!$elementInfo['latelyChanged']) {
1613			continue;
1614		}
1615
1616		// skip host group element containers
1617		if ($selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
1618			continue;
1619		}
1620
1621		$img = get_png_by_selement($elementInfo);
1622
1623		$iconX = imagesx($img);
1624		$iconY = imagesy($img);
1625
1626		$hl_color = null;
1627		$st_color = null;
1628		if (!isset($_REQUEST['noselements']) && (($map['highlight'] % 2) == SYSMAP_HIGHLIGHT_ON)) {
1629			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_ON) {
1630				$hl_color = true;
1631			}
1632			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_MAINTENANCE
1633					|| $elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_DISABLED) {
1634				$st_color = true;
1635			}
1636		}
1637
1638		$mainProblems = [
1639			SYSMAP_ELEMENT_TYPE_HOST_GROUP => 1,
1640			SYSMAP_ELEMENT_TYPE_MAP => 1
1641		];
1642
1643		if (isset($mainProblems[$selement['elementtype']])) {
1644			if (!is_null($hl_color)) {
1645				$st_color = null;
1646			}
1647			elseif (!is_null($st_color)) {
1648				$hl_color = null;
1649			}
1650		}
1651
1652		$markSize = $iconX / 2;
1653		if ($hl_color) {
1654			$markSize += 12;
1655		}
1656		elseif ($st_color) {
1657			$markSize += 8;
1658		}
1659		else {
1660			$markSize += 3;
1661		}
1662
1663		if ($map['label_type'] != MAP_LABEL_TYPE_NOTHING) {
1664			$labelLocation = $selement['label_location'];
1665			if (is_null($labelLocation) || ($labelLocation < 0)) {
1666				$labelLocation = $map['label_location'];
1667			}
1668
1669			switch ($labelLocation) {
1670				case MAP_LABEL_LOC_TOP:
1671					$marks = 'rbl';
1672					break;
1673				case MAP_LABEL_LOC_LEFT:
1674					$marks = 'trb';
1675					break;
1676				case MAP_LABEL_LOC_RIGHT:
1677					$marks = 'tbl';
1678					break;
1679				case MAP_LABEL_LOC_BOTTOM:
1680				default:
1681					$marks = 'trl';
1682			}
1683		}
1684		else {
1685			$marks = 'trbl';
1686		}
1687
1688		imageVerticalMarks($im, $selement['x'] + ($iconX / 2), $selement['y'] + ($iconY / 2), $markSize, 'FF0000', $marks);
1689	}
1690}
1691
1692function drawMapLinkLabels(&$im, $map, $mapInfo, $resolveMacros, $graphtheme) {
1693	$links = $map['links'];
1694	$selements = $map['selements'];
1695
1696	foreach ($links as $link) {
1697		if (empty($link['label'])) {
1698			continue;
1699		}
1700
1701		$selement1 = $selements[$link['selementid1']];
1702		list($x1, $y1) = get_icon_center_by_selement($selement1, $mapInfo[$link['selementid1']], $map);
1703
1704		$selement2 = $selements[$link['selementid2']];
1705		list($x2, $y2) = get_icon_center_by_selement($selement2, $mapInfo[$link['selementid2']], $map);
1706
1707		if (isset($selement1['elementsubtype']) && $selement1['elementsubtype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1708			if ($selement1['areatype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1709				$w = $selement1['width'];
1710				$h = $selement1['height'];
1711			}
1712			else {
1713				$w = $map['width'];
1714				$h = $map['height'];
1715			}
1716			list($x1, $y1) = calculateMapAreaLinkCoord($x1, $y1, $w, $h, $x2, $y2);
1717		}
1718
1719		if (isset($selement2['elementsubtype']) && $selement2['elementsubtype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1720			if ($selement2['areatype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
1721				$w = $selement2['width'];
1722				$h = $selement2['height'];
1723			}
1724			else {
1725				$w = $map['width'];
1726				$h = $map['height'];
1727			}
1728			list($x2, $y2) = calculateMapAreaLinkCoord($x2, $y2, $w, $h, $x1, $y1);
1729		}
1730
1731		$drawtype = $link['drawtype'];
1732		$color = get_color($im, $link['color']);
1733
1734		$linktriggers = $link['linktriggers'];
1735		order_result($linktriggers, 'triggerid');
1736
1737		if (!empty($linktriggers)) {
1738			$max_severity = 0;
1739
1740			$triggers = [];
1741			foreach ($linktriggers as $link_trigger) {
1742				if (!$link_trigger['monitored']) {
1743					continue;
1744				}
1745				$id = $link_trigger['linktriggerid'];
1746
1747				$triggers[$id] = zbx_array_merge($link_trigger, get_trigger_by_triggerid($link_trigger['triggerid']));
1748				if ($triggers[$id]['status'] == TRIGGER_STATUS_ENABLED && $triggers[$id]['value'] == TRIGGER_VALUE_TRUE) {
1749					if ($triggers[$id]['priority'] >= $max_severity) {
1750						$drawtype = $triggers[$id]['drawtype'];
1751						$color = get_color($im, $triggers[$id]['color']);
1752						$max_severity = $triggers[$id]['priority'];
1753					}
1754				}
1755			}
1756		}
1757
1758		$label = $link['label'];
1759
1760		$label = str_replace("\r", '', $label);
1761		$strings = explode("\n", $label);
1762
1763		$box_width = 0;
1764		$box_height = 0;
1765
1766		foreach ($strings as $snum => $str) {
1767			$strings[$snum] = $resolveMacros ? CMacrosResolverHelper::resolveMapLabelMacros($str) : $str;
1768		}
1769
1770		foreach ($strings as $str) {
1771			$dims = imageTextSize(8, 0, $str);
1772
1773			$box_width = ($box_width > $dims['width']) ? $box_width : $dims['width'];
1774			$box_height += $dims['height'] + 2;
1775		}
1776
1777		$boxX_left = round(($x1 + $x2) / 2 - ($box_width / 2) - 6);
1778		$boxX_right = round(($x1 + $x2) / 2 + ($box_width / 2) + 6);
1779
1780		$boxY_top = round(($y1 + $y2) / 2 - ($box_height / 2) - 4);
1781		$boxY_bottom = round(($y1 + $y2) / 2 + ($box_height / 2) + 2);
1782
1783		switch ($drawtype) {
1784			case MAP_LINK_DRAWTYPE_DASHED_LINE:
1785			case MAP_LINK_DRAWTYPE_DOT:
1786				dashedRectangle($im, $boxX_left, $boxY_top, $boxX_right, $boxY_bottom, $color);
1787				break;
1788			case MAP_LINK_DRAWTYPE_BOLD_LINE:
1789				imagerectangle($im, $boxX_left - 1, $boxY_top - 1, $boxX_right + 1, $boxY_bottom + 1, $color);
1790				// break; is not ne
1791			case MAP_LINK_DRAWTYPE_LINE:
1792			default:
1793				imagerectangle($im, $boxX_left, $boxY_top, $boxX_right, $boxY_bottom, $color);
1794		}
1795
1796		imagefilledrectangle($im, $boxX_left + 1, $boxY_top + 1, $boxX_right - 1, $boxY_bottom - 1,
1797			get_color($im, $graphtheme['backgroundcolor'])
1798		);
1799
1800		$color = get_color($im, $graphtheme['textcolor']);
1801
1802		$increasey = 4;
1803		foreach ($strings as $str) {
1804			$dims = imageTextSize(8, 0, $str);
1805
1806			$labelx = ($x1 + $x2) / 2 - ($dims['width'] / 2);
1807			$labely = $boxY_top + $increasey;
1808
1809			imagetext($im, 8, 0, $labelx, $labely + $dims['height'], $color, $str);
1810
1811			$increasey += $dims['height'] + 2;
1812		}
1813	}
1814}
1815
1816function drawMapLabels(&$im, $map, $mapInfo, $resolveMacros, $graphtheme) {
1817	if ($map['label_type'] == MAP_LABEL_TYPE_NOTHING && $map['label_format'] == SYSMAP_LABEL_ADVANCED_OFF) {
1818		return;
1819	}
1820
1821	$selements = $map['selements'];
1822	$allStrings = '';
1823	$labelLines = [];
1824	$statusLines = [];
1825
1826	foreach ($selements as $sid => $selement) {
1827		if (isset($selement['elementsubtype']) && $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
1828			unset($selements[$sid]);
1829		}
1830	}
1831
1832	// set label type and custom label text for all selements
1833	foreach ($selements as $selementId => $selement) {
1834		$selements[$selementId]['label_type'] = $map['label_type'];
1835
1836		if ($map['label_format'] == SYSMAP_LABEL_ADVANCED_OFF) {
1837			continue;
1838		}
1839
1840		switch ($selement['elementtype']) {
1841			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
1842				$selements[$selementId]['label_type'] = $map['label_type_hostgroup'];
1843				if ($map['label_type_hostgroup'] == MAP_LABEL_TYPE_CUSTOM) {
1844					$selements[$selementId]['label'] = $map['label_string_hostgroup'];
1845				}
1846				break;
1847
1848			case SYSMAP_ELEMENT_TYPE_HOST:
1849				$selements[$selementId]['label_type'] = $map['label_type_host'];
1850				if ($map['label_type_host'] == MAP_LABEL_TYPE_CUSTOM) {
1851					$selements[$selementId]['label'] = $map['label_string_host'];
1852				}
1853				break;
1854
1855			case SYSMAP_ELEMENT_TYPE_TRIGGER:
1856				$selements[$selementId]['label_type'] = $map['label_type_trigger'];
1857				if ($map['label_type_trigger'] == MAP_LABEL_TYPE_CUSTOM) {
1858					$selements[$selementId]['label'] = $map['label_string_trigger'];
1859				}
1860				break;
1861
1862			case SYSMAP_ELEMENT_TYPE_MAP:
1863				$selements[$selementId]['label_type'] = $map['label_type_map'];
1864				if ($map['label_type_map'] == MAP_LABEL_TYPE_CUSTOM) {
1865					$selements[$selementId]['label'] = $map['label_string_map'];
1866				}
1867				break;
1868
1869			case SYSMAP_ELEMENT_TYPE_IMAGE:
1870				$selements[$selementId]['label_type'] = $map['label_type_image'];
1871				if ($map['label_type_image'] == MAP_LABEL_TYPE_CUSTOM) {
1872					$selements[$selementId]['label'] = $map['label_string_image'];
1873				}
1874				break;
1875		}
1876	}
1877
1878	foreach ($selements as $selementId => $selement) {
1879		if (!isset($labelLines[$selementId])) {
1880			$labelLines[$selementId] = [];
1881		}
1882		if (!isset($statusLines[$selementId])) {
1883			$statusLines[$selementId] = [];
1884		}
1885
1886		$msg = $resolveMacros ? CMacrosResolverHelper::resolveMapLabelMacrosAll($selement) : $selement['label'];
1887
1888		$allStrings .= $msg;
1889		$msgs = explode("\n", $msg);
1890		foreach ($msgs as $msg) {
1891			$labelLines[$selementId][] = ['msg' => $msg];
1892		}
1893
1894		$elementInfo = $mapInfo[$selementId];
1895
1896		foreach (['problem', 'unack', 'maintenance', 'ok', 'status'] as $caption) {
1897			if (!isset($elementInfo['info'][$caption]) || zbx_empty($elementInfo['info'][$caption]['msg'])) {
1898				continue;
1899			}
1900
1901			$statusLines[$selementId][] = [
1902				'msg' => $elementInfo['info'][$caption]['msg'],
1903				'color' => $elementInfo['info'][$caption]['color']
1904			];
1905
1906			$allStrings .= $elementInfo['info'][$caption]['msg'];
1907		}
1908	}
1909
1910	$allLabelsSize = imageTextSize(8, 0, str_replace("\r", '', str_replace("\n", '', $allStrings)));
1911	$labelFontHeight = $allLabelsSize['height'];
1912	$labelFontBaseline = $allLabelsSize['baseline'];
1913
1914	$elementsHostIds = [];
1915	foreach ($selements as $selement) {
1916		if ($selement['label_type'] != MAP_LABEL_TYPE_IP) {
1917			continue;
1918		}
1919		if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
1920			$elementsHostIds[] = $selement['elementid'];
1921		}
1922	}
1923
1924	if (!empty($elementsHostIds)) {
1925		$mapHosts = API::Host()->get([
1926			'hostids' => $elementsHostIds,
1927			'output' => ['hostid'],
1928			'selectInterfaces' => API_OUTPUT_EXTEND
1929		]);
1930		$mapHosts = zbx_toHash($mapHosts, 'hostid');
1931	}
1932
1933	// draw
1934	foreach ($selements as $selementId => $selement) {
1935		if (empty($selement) || $selement['label_type'] == MAP_LABEL_TYPE_NOTHING) {
1936			continue;
1937		}
1938
1939		$elementInfo = $mapInfo[$selementId];
1940
1941		$hl_color = null;
1942		$st_color = null;
1943
1944		if (!isset($_REQUEST['noselements']) && ($map['highlight'] % 2) == SYSMAP_HIGHLIGHT_ON) {
1945			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_ON) {
1946				$hl_color = true;
1947			}
1948			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_MAINTENANCE) {
1949				$st_color = true;
1950			}
1951			if ($elementInfo['icon_type'] == SYSMAP_ELEMENT_ICON_DISABLED) {
1952				$st_color = true;
1953			}
1954		}
1955
1956		if (in_array($selement['elementtype'], [SYSMAP_ELEMENT_TYPE_HOST_GROUP, SYSMAP_ELEMENT_TYPE_MAP])
1957				&& !is_null($hl_color)) {
1958			$st_color = null;
1959		}
1960		elseif (!is_null($st_color)) {
1961			$hl_color = null;
1962		}
1963
1964		$labelLocation = (is_null($selement['label_location']) || $selement['label_location'] < 0)
1965			? $map['label_location']
1966			: $selement['label_location'];
1967
1968		$label = [];
1969
1970		if ($selement['label_type'] == MAP_LABEL_TYPE_IP && $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
1971			$interface = reset($mapHosts[$selement['elementid']]['interfaces']);
1972
1973			$label[] = ['msg' => $interface['ip']];
1974			$label = array_merge($label, $statusLines[$selementId]);
1975		}
1976		elseif ($selement['label_type'] == MAP_LABEL_TYPE_STATUS) {
1977			$label = $statusLines[$selementId];
1978		}
1979		elseif ($selement['label_type'] == MAP_LABEL_TYPE_NAME) {
1980			$label[] = ['msg' => $elementInfo['name']];
1981			$label = array_merge($label, $statusLines[$selementId]);
1982		}
1983		else {
1984			$label = array_merge($labelLines[$selementId], $statusLines[$selementId]);
1985		}
1986
1987		if (empty($label)) {
1988			continue;
1989		}
1990
1991		$w = 0;
1992		foreach ($label as $str) {
1993			$dims = imageTextSize(8, 0, $str['msg']);
1994			$w = max($w, $dims['width']);
1995		}
1996
1997		$h = count($label) * $labelFontHeight;
1998		$x = $selement['x'];
1999		$y = $selement['y'];
2000
2001		$image = get_png_by_selement($elementInfo);
2002		$iconX = imagesx($image);
2003		$iconY = imagesy($image);
2004
2005		if (!is_null($hl_color)) {
2006			$icon_hl = 14;
2007		}
2008		elseif (!is_null($st_color)) {
2009			$icon_hl = 6;
2010		}
2011		else {
2012			$icon_hl = 2;
2013		}
2014
2015		switch ($labelLocation) {
2016			case MAP_LABEL_LOC_TOP:
2017				$y_rec = $y - $icon_hl - $h - 6;
2018				$x_rec = $x + $iconX / 2 - $w / 2;
2019				break;
2020
2021			case MAP_LABEL_LOC_LEFT:
2022				$y_rec = $y - $h / 2 + $iconY / 2;
2023				$x_rec = $x - $icon_hl - $w;
2024				break;
2025
2026			case MAP_LABEL_LOC_RIGHT:
2027				$y_rec = $y - $h / 2 + $iconY / 2;
2028				$x_rec = $x + $iconX + $icon_hl;
2029				break;
2030
2031			case MAP_LABEL_LOC_BOTTOM:
2032			default:
2033				$y_rec = $y + $iconY + $icon_hl;
2034				$x_rec = $x + $iconX / 2 - $w / 2;
2035		}
2036
2037		$increasey = 12;
2038		foreach ($label as $line) {
2039			if (zbx_empty($line['msg'])) {
2040				continue;
2041			}
2042
2043			$str = str_replace("\r", '', $line['msg']);
2044			$color = isset($line['color']) ? $line['color'] : $graphtheme['textcolor'];
2045
2046			$dims = imageTextSize(8, 0, $str);
2047
2048			if ($labelLocation == MAP_LABEL_LOC_TOP || $labelLocation == MAP_LABEL_LOC_BOTTOM) {
2049				$x_label = $x + ceil($iconX / 2) - ceil($dims['width'] / 2);
2050			}
2051			elseif ($labelLocation == MAP_LABEL_LOC_LEFT) {
2052				$x_label = $x_rec + $w - $dims['width'];
2053			}
2054			else {
2055				$x_label = $x_rec;
2056			}
2057
2058			imagefilledrectangle(
2059				$im,
2060				$x_label - 1, $y_rec + $increasey - $labelFontHeight + $labelFontBaseline,
2061				$x_label + $dims['width'] + 1, $y_rec + $increasey + $labelFontBaseline,
2062				get_color($im, $graphtheme['backgroundcolor'])
2063			);
2064			imagetext($im, 8, 0, $x_label, $y_rec + $increasey, get_color($im, $color), $str);
2065
2066			$increasey += $labelFontHeight + 1;
2067		}
2068	}
2069}
2070
2071/**
2072 * For each host group which is area for hosts virtual elements as hosts from that host group are created
2073 *
2074 * @param array $map
2075 * @return array areas with area coordinates and selementids
2076 */
2077function populateFromMapAreas(array &$map) {
2078	$areas = [];
2079
2080	foreach ($map['selements'] as $selement) {
2081		if ($selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
2082			$area = ['selementids' => []];
2083
2084			$origSelement = $selement;
2085
2086			$hosts = API::host()->get([
2087				'groupids' => $selement['elementid'],
2088				'sortfield' => 'name',
2089				'output' => ['hostid'],
2090				'nopermissions' => true,
2091				'preservekeys' => true
2092			]);
2093			$hostsCount = count($hosts);
2094
2095			if ($hostsCount == 0) {
2096				continue;
2097			}
2098
2099			if ($selement['areatype'] == SYSMAP_ELEMENT_AREA_TYPE_CUSTOM) {
2100				$area['width'] = $selement['width'];
2101				$area['height'] = $selement['height'];
2102				$area['x'] = $selement['x'];
2103				$area['y'] = $selement['y'];
2104			}
2105			else {
2106				$area['width'] = $map['width'];
2107				$area['height'] = $map['height'];
2108				$area['x'] = 0;
2109				$area['y'] = 0;
2110			}
2111
2112			foreach ($hosts as $host) {
2113				$selement['elementtype'] = SYSMAP_ELEMENT_TYPE_HOST;
2114				$selement['elementsubtype'] = SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP;
2115				$selement['elementid'] = $host['hostid'];
2116
2117				$newSelementid = rand(1, 9999999);
2118				while (isset($map['selements'][$newSelementid])) {
2119					$newSelementid += 1;
2120				};
2121				$selement['selementid'] = $newSelementid;
2122
2123				$area['selementids'][$newSelementid] = $newSelementid;
2124				$map['selements'][$newSelementid] = $selement;
2125			}
2126
2127			$areas[] = $area;
2128
2129			foreach ($map['links'] as $link) {
2130				// do not multiply links between two areas
2131				if ($map['selements'][$link['selementid1']]['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS
2132						&& $map['selements'][$link['selementid2']]['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS) {
2133					continue;
2134				}
2135
2136				$idNumber = null;
2137				if ($link['selementid1'] == $origSelement['selementid']) {
2138					$idNumber = 'selementid1';
2139				}
2140				elseif ($link['selementid2'] == $origSelement['selementid']) {
2141					$idNumber = 'selementid2';
2142				}
2143
2144				if ($idNumber) {
2145					foreach ($area['selementids'] as $newSelementid) {
2146						$newLinkid = rand(1, 9999999);
2147						while (isset($map['links'][$newLinkid])) {
2148							$newLinkid += 1;
2149						};
2150
2151						$link['linkid'] = $newLinkid;
2152						$link[$idNumber] = $newSelementid;
2153						$map['links'][$newLinkid] = $link;
2154					}
2155				}
2156			}
2157		}
2158	}
2159
2160	return $areas;
2161}
2162
2163/**
2164 * Calculates coordinates from elements inside areas
2165 *
2166 * @param array $map
2167 * @param array $areas
2168 * @param array $mapInfo
2169 *
2170 * @return void
2171 */
2172function processAreasCoordinates(array &$map, array $areas, array $mapInfo) {
2173	foreach ($areas as $area) {
2174		$rowPlaceCount = ceil(sqrt(count($area['selementids'])));
2175
2176		// offset from area borders
2177		$area['x'] += 5;
2178		$area['y'] += 5;
2179		$area['width'] -= 5;
2180		$area['height'] -= 5;
2181
2182		$xOffset = floor($area['width'] / $rowPlaceCount);
2183		$yOffset = floor($area['height'] / $rowPlaceCount);
2184
2185		$colNum = 0;
2186		$rowNum = 0;
2187		// some offset is required so that icon highlights are not drawn outside area
2188		$borderOffset = 20;
2189		foreach ($area['selementids'] as $selementId) {
2190			$selement = $map['selements'][$selementId];
2191
2192			$image = get_png_by_selement($mapInfo[$selementId]);
2193			$iconX = imagesx($image);
2194			$iconY = imagesy($image);
2195
2196			$labelLocation = (is_null($selement['label_location']) || ($selement['label_location'] < 0))
2197				? $map['label_location'] : $selement['label_location'];
2198			switch ($labelLocation) {
2199				case MAP_LABEL_LOC_TOP:
2200					$newX = $area['x'] + ($xOffset / 2) - ($iconX / 2);
2201					$newY = $area['y'] + $yOffset - $iconY - ($iconY >= $iconX ? 0 : abs($iconX - $iconY) / 2) - $borderOffset;
2202					break;
2203				case MAP_LABEL_LOC_LEFT:
2204					$newX = $area['x'] + $xOffset - $iconX - $borderOffset;
2205					$newY = $area['y'] + ($yOffset / 2) - ($iconY / 2);
2206					break;
2207				case MAP_LABEL_LOC_RIGHT:
2208					$newX = $area['x'] + $borderOffset;
2209					$newY = $area['y'] + ($yOffset / 2) - ($iconY / 2);
2210					break;
2211				case MAP_LABEL_LOC_BOTTOM:
2212					$newX = $area['x'] + ($xOffset / 2) - ($iconX / 2);
2213					$newY = $area['y'] + abs($iconX - $iconY) / 2 + $borderOffset;
2214					break;
2215			}
2216
2217			$map['selements'][$selementId]['x'] = $newX + ($colNum * $xOffset);
2218			$map['selements'][$selementId]['y'] = $newY + ($rowNum * $yOffset);
2219
2220			$colNum++;
2221			if ($colNum == $rowPlaceCount) {
2222				$colNum = 0;
2223				$rowNum++;
2224			}
2225		}
2226	}
2227}
2228
2229/**
2230 * Calculates area connector point on area perimeter
2231 *
2232 * @param int $ax      x area coordinate
2233 * @param int $ay      y area coordinate
2234 * @param int $aWidth  area width
2235 * @param int $aHeight area height
2236 * @param int $x2      x coordinate of connector second element
2237 * @param int $y2      y coordinate of connector second element
2238 *
2239 * @return array contains two values, x and y coordinates of new area connector point
2240 */
2241function calculateMapAreaLinkCoord($ax, $ay, $aWidth, $aHeight, $x2, $y2) {
2242	$dY = abs($y2 - $ay);
2243	$dX = abs($x2 - $ax);
2244
2245	$halfHeight = $aHeight / 2;
2246	$halfWidth = $aWidth / 2;
2247
2248	if ($dY == 0) {
2249		$ay = $y2;
2250		$ax = ($x2 < $ax) ? $ax - $halfWidth : $ax + $halfWidth;
2251	}
2252	elseif ($dX == 0) {
2253		$ay = ($y2 > $ay) ? $ay + $halfHeight : $ay - $halfHeight;
2254		$ax = $x2;
2255	}
2256	else {
2257		$koef = $halfHeight / $dY;
2258
2259		$c = $dX * $koef;
2260
2261		// if point is further than area diagonal, we should use calculations with width instead of height
2262		if (($halfHeight / $c) > ($halfHeight / $halfWidth)) {
2263			$ay = ($y2 > $ay) ? $ay + $halfHeight : $ay - $halfHeight;
2264			$ax = ($x2 < $ax) ? $ax - $c : $ax + $c;
2265		}
2266		else {
2267			$koef = $halfWidth / $dX;
2268
2269			$c = $dY * $koef;
2270
2271			$ay = ($y2 > $ay) ? $ay + $c : $ay - $c;
2272			$ax = ($x2 < $ax) ? $ax - $halfWidth : $ax + $halfWidth;
2273		}
2274	}
2275
2276	return [$ax, $ay];
2277}
2278
2279/**
2280 * Get icon id by mapping.
2281 *
2282 * @param array $iconMap
2283 * @param array $inventory
2284 *
2285 * @return int
2286 */
2287function getIconByMapping($iconMap, $inventory) {
2288	if (!empty($inventory['inventory'])) {
2289		$inventories = getHostInventories();
2290
2291		foreach ($iconMap['mappings'] as $mapping) {
2292			try {
2293				$expr = new CGlobalRegexp($mapping['expression']);
2294				if ($expr->match($inventory['inventory'][$inventories[$mapping['inventory_link']]['db_field']])) {
2295					return $mapping['iconid'];
2296				}
2297			}
2298			catch(Exception $e) {
2299				continue;
2300			}
2301		}
2302	}
2303
2304	return $iconMap['default_iconid'];
2305}
2306
2307/**
2308 * Get parent maps for current map.
2309 *
2310 * @param int $sysmapid
2311 *
2312 * @return array
2313 */
2314function get_parent_sysmaps($sysmapid) {
2315	$db_sysmaps_elements = DBselect(
2316		'SELECT DISTINCT se.sysmapid'.
2317		' FROM sysmaps_elements se'.
2318		' WHERE '.dbConditionInt('se.elementtype', [SYSMAP_ELEMENT_TYPE_MAP]).
2319			' AND '.dbConditionInt('se.elementid', [$sysmapid])
2320	);
2321
2322	$sysmapids = [];
2323
2324	while ($db_sysmaps_element = DBfetch($db_sysmaps_elements)) {
2325		$sysmapids[] = $db_sysmaps_element['sysmapid'];
2326	}
2327
2328	if ($sysmapids) {
2329		$sysmaps = API::Map()->get([
2330			'output' => ['sysmapid', 'name'],
2331			'sysmapids' => $sysmapids
2332		]);
2333
2334		CArrayHelper::sort($sysmaps, ['name']);
2335
2336		return $sysmaps;
2337	}
2338
2339	return [];
2340}
2341
2342/**
2343 * Function extends link triggers adding boolean property 'monitored'.
2344 *
2345 * @param array $map
2346 */
2347function resolveMapLinksTriggersState(&$map) {
2348	$triggerids = [];
2349	foreach ($map['links'] as $link) {
2350		$triggerids = array_merge($triggerids, zbx_objectValues($link['linktriggers'], 'triggerid'));
2351	}
2352	$monitored_triggers = API::Trigger()->get([
2353		'output' => [],
2354		'triggerids' => $triggerids,
2355		'monitored' => true,
2356		'nopermissions' => true,
2357		'preservekeys' => true
2358	]);
2359
2360	foreach ($map['links'] as $linkid => $link) {
2361		foreach ($link['linktriggers'] as $link_triggerid => $link_trigger) {
2362			$map['links'][$linkid]['linktriggers'][$link_triggerid]['monitored']
2363				= array_key_exists($link_trigger['triggerid'], $monitored_triggers);
2364		}
2365	}
2366}
2367