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