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
22/**
23 * Class containing methods for operations with maps.
24 */
25class CMap extends CMapElement {
26
27	protected $tableName = 'sysmaps';
28	protected $tableAlias = 's';
29	protected $sortColumns = ['name', 'width', 'height'];
30
31	private $defOptions = [
32		'sysmapids'					=> null,
33		'userids'					=> null,
34		'editable'					=> false,
35		'nopermissions'				=> null,
36		// filter
37		'filter'					=> null,
38		'search'					=> null,
39		'searchByAny'				=> null,
40		'startSearch'				=> false,
41		'excludeSearch'				=> false,
42		'searchWildcardsEnabled'	=> null,
43		// output
44		'output'					=> API_OUTPUT_EXTEND,
45		'selectSelements'			=> null,
46		'selectShapes'				=> null,
47		'selectLines'				=> null,
48		'selectLinks'				=> null,
49		'selectIconMap'				=> null,
50		'selectUrls'				=> null,
51		'selectUsers'				=> null,
52		'selectUserGroups'			=> null,
53		'countOutput'				=> false,
54		'expandUrls' 				=> null,
55		'preservekeys'				=> false,
56		'sortfield'					=> '',
57		'sortorder'					=> '',
58		'limit'						=> null
59	];
60
61	/**
62	 * Get map data.
63	 *
64	 * @param array  $options
65	 * @param array  $options['sysmapids']					Map IDs.
66	 * @param bool	 $options['output']						List of map parameters to return.
67	 * @param array  $options['selectSelements']			List of map element properties to return.
68	 * @param array  $options['selectShapes']				List of map shape properties to return.
69	 * @param array  $options['selectLines']				List of map line properties to return.
70	 * @param array  $options['selectLinks']				List of map link properties to return.
71	 * @param array  $options['selectIconMap']				List of map icon map properties to return.
72	 * @param array  $options['selectUrls']					List of map URL properties to return.
73	 * @param array  $options['selectUsers']				List of users that the map is shared with.
74	 * @param array  $options['selectUserGroups']			List of user groups that the map is shared with.
75	 * @param bool	 $options['countOutput']				Return the count of records, instead of actual results.
76	 * @param array  $options['userids']					Map owner user IDs.
77	 * @param bool   $options['editable']					Return with read-write permission only. Ignored for
78	 *														SuperAdmins.
79	 * @param bool	 $options['nopermissions']				Return requested maps even if user has no permissions to
80	 *														them.
81	 * @param array  $options['filter']						List of field and exactly matched value pairs by which maps
82	 *														need to be filtered.
83	 * @param array  $options['search']						List of field-value pairs by which maps need to be searched.
84	 * @param array  $options['expandUrls']					Adds global map URLs to the corresponding map elements and
85	 *														expands macros in all map element URLs.
86	 * @param bool	 $options['searchByAny']
87	 * @param bool	 $options['startSearch']
88	 * @param bool	 $options['excludeSearch']
89	 * @param bool	 $options['searchWildcardsEnabled']
90	 * @param array  $options['preservekeys']				Use IDs as keys in the resulting array.
91	 * @param int    $options['limit']						Limit selection.
92	 * @param string $options['sortorder']
93	 * @param string $options['sortfield']
94	 *
95	 * @return array|integer Requested map data as array or the count of retrieved objects, if the countOutput
96	 *						 parameter has been used.
97	 */
98	public function get(array $options = []) {
99		$options = zbx_array_merge($this->defOptions, $options);
100
101		$limit = $options['limit'];
102		$options['limit'] = null;
103
104		if ($options['countOutput']) {
105			$count_output = true;
106			$options['output'] = ['sysmapid'];
107			$options['countOutput'] = false;
108		}
109		else {
110			$count_output = false;
111		}
112
113		$result = $this->getMaps($options);
114
115		if ($result && self::$userData['type'] != USER_TYPE_SUPER_ADMIN && !$options['nopermissions']) {
116			$sysmapids = array_flip($this->checkPermissions(array_keys($result), (bool) $options['editable']));
117
118			foreach ($result as $sysmapid => $foo) {
119				if (!array_key_exists($sysmapid, $sysmapids)) {
120					unset($result[$sysmapid]);
121				}
122			}
123		}
124
125		if ($count_output) {
126			return (string) count($result);
127		}
128
129		if ($limit !== null) {
130			$result = array_slice($result, 0, $limit, true);
131		}
132
133		if ($result) {
134			$result = $this->addRelatedObjects($options, $result);
135		}
136
137		// removing keys (hash -> array)
138		if (!$options['preservekeys']) {
139			$result = zbx_cleanHashes($result);
140		}
141
142		return $result;
143	}
144
145	/**
146	 * Returns maps without checking permissions to the elements.
147	 */
148	private function getMaps(array $options) {
149		$sql_parts = [
150			'select'	=> ['sysmaps' => 's.sysmapid'],
151			'from'		=> ['sysmaps' => 'sysmaps s'],
152			'where'		=> [],
153			'order'		=> [],
154			'limit'		=> null
155		];
156
157		// Editable + permission check.
158		if (self::$userData['type'] < USER_TYPE_ZABBIX_ADMIN && !$options['nopermissions']) {
159			$public_maps = '';
160
161			if ($options['editable']) {
162				$permission = PERM_READ_WRITE;
163			}
164			else {
165				$permission = PERM_READ;
166				$public_maps = ' OR s.private='.PUBLIC_SHARING;
167			}
168
169			$user_groups = getUserGroupsByUserId(self::$userData['userid']);
170
171			$sql_parts['where'][] = '(EXISTS ('.
172					'SELECT NULL'.
173					' FROM sysmap_user su'.
174					' WHERE s.sysmapid=su.sysmapid'.
175						' AND su.userid='.self::$userData['userid'].
176						' AND su.permission>='.$permission.
177				')'.
178				' OR EXISTS ('.
179					'SELECT NULL'.
180					' FROM sysmap_usrgrp sg'.
181					' WHERE s.sysmapid=sg.sysmapid'.
182						' AND '.dbConditionInt('sg.usrgrpid', $user_groups).
183						' AND sg.permission>='.$permission.
184				')'.
185				' OR s.userid='.self::$userData['userid'].
186				$public_maps.
187			')';
188		}
189
190		// sysmapids
191		if ($options['sysmapids'] !== null) {
192			zbx_value2array($options['sysmapids']);
193			$sql_parts['where']['sysmapid'] = dbConditionInt('s.sysmapid', $options['sysmapids']);
194		}
195
196		// userids
197		if ($options['userids'] !== null) {
198			zbx_value2array($options['userids']);
199
200			$sql_parts['where'][] = dbConditionInt('s.userid', $options['userids']);
201		}
202
203		// search
204		if ($options['search'] !== null) {
205			zbx_db_search('sysmaps s', $options, $sql_parts);
206		}
207
208		// filter
209		if ($options['filter'] !== null) {
210			$this->dbFilter('sysmaps s', $options, $sql_parts);
211		}
212
213		// limit
214		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
215			$sql_parts['limit'] = $options['limit'];
216		}
217
218		$result = [];
219
220		$sql_parts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts);
221		$sql_parts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sql_parts);
222		$sysmaps = DBselect(self::createSelectQueryFromParts($sql_parts), $sql_parts['limit']);
223
224		while ($sysmap = DBfetch($sysmaps)) {
225			$result[$sysmap['sysmapid']] = $sysmap;
226		}
227
228		return $result;
229	}
230
231	/**
232	 * Returns maps with selected permission level.
233	 *
234	 * @param array $sysmapids
235	 * @param bool  $editable
236	 *
237	 * @return array
238	 */
239	private function checkPermissions(array $sysmapids, $editable) {
240		$sysmaps_r = [];
241		foreach ($sysmapids as $sysmapid) {
242			$sysmaps_r[$sysmapid] = true;
243		}
244
245		$selement_maps = [];
246
247		// Populating the map tree $selement_maps and list of shared maps $sysmaps_r.
248		do {
249			$selements = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_MAP);
250
251			$sysmapids = [];
252
253			foreach ($selements as $sysmapid => $selement) {
254				if (!array_key_exists($sysmapid, $sysmaps_r)) {
255					$sysmapids[$sysmapid] = true;
256				}
257			}
258
259			$sysmapids = array_keys($sysmapids);
260			$selement_maps += $selements;
261
262			if ($sysmapids) {
263				$db_sysmaps = $this->getMaps([
264					'output' => [],
265					'sysmapids' => $sysmapids,
266					'preservekeys' => true
267				] + $this->defOptions);
268
269				foreach ($sysmapids as $i => $sysmapid) {
270					if (array_key_exists($sysmapid, $db_sysmaps)) {
271						$sysmaps_r[$sysmapid] = true;
272					}
273					else {
274						unset($sysmapids[$i]);
275					}
276				}
277			}
278		}
279		while ($sysmapids);
280
281		$sysmaps_rw = $editable ? $sysmaps_r : [];
282
283		foreach ($sysmaps_r as &$sysmap_r) {
284			$sysmap_r = ['permission' => PERM_NONE, 'has_elements' => false];
285		}
286		unset($sysmap_r);
287
288		self::setHasElements($sysmaps_r, $selement_maps);
289
290		// Setting PERM_READ permission for maps with at least one image.
291		$selement_images = self::getSelements(array_keys($sysmaps_r), SYSMAP_ELEMENT_TYPE_IMAGE);
292		self::setMapPermissions($sysmaps_r, $selement_images, [0 => []], $selement_maps);
293		self::setHasElements($sysmaps_r, $selement_images);
294
295		$sysmapids = self::getSysmapIds($sysmaps_r, $sysmaps_rw);
296
297		// Check permissions to the host groups.
298		if ($sysmapids) {
299			$selement_groups = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_HOST_GROUP);
300
301			$db_groups = API::HostGroup()->get([
302				'output' => [],
303				'groupids' => array_keys($selement_groups),
304				'preservekeys' => true
305			]);
306
307			if ($editable) {
308				self::unsetMapsByElements($sysmaps_rw, $selement_groups, $db_groups);
309			}
310			self::setMapPermissions($sysmaps_r, $selement_groups, $db_groups, $selement_maps);
311			self::setHasElements($sysmaps_r, $selement_groups);
312
313			$sysmapids = self::getSysmapIds($sysmaps_r, $sysmaps_rw);
314		}
315
316		// Check permissions to the hosts.
317		if ($sysmapids) {
318			$selement_hosts = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_HOST);
319
320			$db_hosts = API::Host()->get([
321				'output' => [],
322				'hostids' => array_keys($selement_hosts),
323				'preservekeys' => true
324			]);
325
326			if ($editable) {
327				self::unsetMapsByElements($sysmaps_rw, $selement_hosts, $db_hosts);
328			}
329			self::setMapPermissions($sysmaps_r, $selement_hosts, $db_hosts, $selement_maps);
330			self::setHasElements($sysmaps_r, $selement_hosts);
331
332			$sysmapids = self::getSysmapIds($sysmaps_r, $sysmaps_rw);
333		}
334
335		// Check permissions to the triggers.
336		if ($sysmapids) {
337			$selement_triggers = self::getSelements($sysmapids, SYSMAP_ELEMENT_TYPE_TRIGGER);
338			$link_triggers = self::getLinkTriggers($sysmapids);
339
340			$db_triggers = API::Trigger()->get([
341				'output' => [],
342				'triggerids' => array_keys($selement_triggers + $link_triggers),
343				'preservekeys' => true
344			]);
345
346			if ($editable) {
347				self::unsetMapsByElements($sysmaps_rw, $selement_triggers, $db_triggers);
348				self::unsetMapsByElements($sysmaps_rw, $link_triggers, $db_triggers);
349			}
350			self::setMapPermissions($sysmaps_r, $selement_triggers, $db_triggers, $selement_maps);
351			self::setMapPermissions($sysmaps_r, $link_triggers, $db_triggers, $selement_maps);
352			self::setHasElements($sysmaps_r, $selement_triggers);
353			self::setHasElements($sysmaps_r, $link_triggers);
354		}
355
356		foreach ($sysmaps_r as $sysmapid => $sysmap_r) {
357			if (!$sysmap_r['has_elements']) {
358				self::setMapPermission($sysmaps_r, $selement_maps, $sysmapid);
359			}
360		}
361
362		foreach ($sysmaps_r as $sysmapid => $sysmap_r) {
363			if ($sysmap_r['permission'] == PERM_NONE) {
364				unset($sysmaps_r[$sysmapid]);
365			}
366		}
367
368		if ($editable) {
369			self::unsetMapsByTree($sysmaps_rw, $sysmaps_r, $selement_maps);
370		}
371
372		return array_keys($editable ? $sysmaps_rw : $sysmaps_r);
373	}
374
375	/**
376	 * Returns map elements for selected maps.
377	 */
378	private static function getSelements(array $sysmapids, $elementtype) {
379		$selements = [];
380
381		switch ($elementtype) {
382			case SYSMAP_ELEMENT_TYPE_IMAGE:
383				$sql = 'SELECT se.sysmapid,0 AS elementid'.
384					' FROM sysmaps_elements se'.
385					' WHERE '.dbConditionInt('se.sysmapid', $sysmapids).
386						' AND '.dbConditionInt('se.elementtype', [$elementtype]);
387				break;
388
389			case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
390			case SYSMAP_ELEMENT_TYPE_HOST:
391			case SYSMAP_ELEMENT_TYPE_MAP:
392				$sql = 'SELECT se.sysmapid,se.elementid'.
393					' FROM sysmaps_elements se'.
394					' WHERE '.dbConditionInt('se.sysmapid', $sysmapids).
395						' AND '.dbConditionInt('se.elementtype', [$elementtype]);
396				break;
397
398			case SYSMAP_ELEMENT_TYPE_TRIGGER:
399				$sql = 'SELECT se.sysmapid,st.triggerid AS elementid'.
400					' FROM sysmaps_elements se,sysmap_element_trigger st'.
401					' WHERE se.selementid=st.selementid'.
402						' AND '.dbConditionInt('se.sysmapid', $sysmapids).
403						' AND '.dbConditionInt('se.elementtype', [$elementtype]);
404				break;
405		}
406		$db_selements = DBSelect($sql);
407
408		while ($db_selement = DBfetch($db_selements)) {
409			$selements[$db_selement['elementid']][] = ['sysmapid' => $db_selement['sysmapid']];
410		}
411
412		return $selements;
413	}
414
415	/**
416	 * Returns map links for selected maps.
417	 */
418	private static function getLinkTriggers(array $sysmapids) {
419		$link_triggers = [];
420
421		$db_links = DBSelect(
422			'SELECT sl.sysmapid,slt.triggerid'.
423			' FROM sysmaps_links sl,sysmaps_link_triggers slt'.
424			' WHERE sl.linkid=slt.linkid'.
425				' AND '.dbConditionInt('sl.sysmapid', $sysmapids)
426		);
427
428		while ($db_link = DBfetch($db_links)) {
429			$link_triggers[$db_link['triggerid']][] = ['sysmapid' => $db_link['sysmapid']];
430		}
431
432		return $link_triggers;
433	}
434
435	/**
436	 * Removes all inaccessible maps by map tree.
437	 *
438	 * @param array $sysmaps_rw[<sysmapids>]                  The list of writable maps.
439	 * @param array $sysmaps_r[<sysmapids>]                   The list of readable maps.
440	 * @param array $selement_maps                            The map tree.
441	 * @param array $selement_maps[<sysmapid>][]['sysmapid']  Parent map ID.
442	 */
443	private static function unsetMapsByTree(array &$sysmaps_rw, array $sysmaps_r, array $selement_maps) {
444		foreach ($selement_maps as $child_sysmapid => $selements) {
445			if (!array_key_exists($child_sysmapid, $sysmaps_r)) {
446				foreach ($selements as $selement) {
447					unset($sysmaps_rw[$selement['sysmapid']]);
448				}
449			}
450		}
451	}
452
453	/**
454	 * Removes all inaccessible maps by inacessible elements.
455	 *
456	 * @param array $sysmaps_rw[<sysmapids>]              The list of writable maps.
457	 * @param array $elements                             The map elements.
458	 * @param array $elements[<elementid>][]['sysmapid']  Map ID.
459	 * @param array $db_elements                          The list of readable elements.
460	 * @param array $db_elements[<elementid>]
461	 */
462	private static function unsetMapsByElements(array &$sysmaps_rw, array $elements, array $db_elements) {
463		foreach ($elements as $elementid => $selements) {
464			if (!array_key_exists($elementid, $db_elements)) {
465				foreach ($selements as $selement) {
466					unset($sysmaps_rw[$selement['sysmapid']]);
467				}
468			}
469		}
470	}
471
472	/**
473	 * Set PERM_READ permission for map and all parent maps.
474	 *
475	 * @param array  $sysmaps_r[<sysmapids>]                   The list of readable maps.
476	 * @param array  $selement_maps                            The map elements.
477	 * @param array  $selement_maps[<sysmapid>][]['sysmapid']  Map ID.
478	 * @param string $sysmapid
479	 */
480	private static function setMapPermission(array &$sysmaps_r, array $selement_maps, $sysmapid) {
481		if (array_key_exists($sysmapid, $selement_maps)) {
482			foreach ($selement_maps[$sysmapid] as $selement) {
483				self::setMapPermission($sysmaps_r, $selement_maps, $selement['sysmapid']);
484			}
485		}
486		$sysmaps_r[$sysmapid]['permission'] = PERM_READ;
487	}
488
489	/**
490	 * Setting PERM_READ permissions for maps with at least one available element.
491	 *
492	 * @param array $sysmaps_r[<sysmapids>]                   The list of readable maps.
493	 * @param array $elements                                 The map elements.
494	 * @param array $elements[<elementid>][]['sysmapid']      Map ID.
495	 * @param array $db_elements                              The list of readable elements.
496	 * @param array $db_elements[<elementid>]
497	 * @param array $selement_maps                            The map elements.
498	 * @param array $selement_maps[<sysmapid>][]['sysmapid']  Map ID.
499	 */
500	private static function setMapPermissions(array &$sysmaps_r, array $elements, array $db_elements,
501			array $selement_maps) {
502		foreach ($elements as $elementid => $selements) {
503			if (array_key_exists($elementid, $db_elements)) {
504				foreach ($selements as $selement) {
505					self::setMapPermission($sysmaps_r, $selement_maps, $selement['sysmapid']);
506				}
507			}
508		}
509	}
510
511	/**
512	 * Setting "has_elements" flag for maps.
513	 *
514	 * @param array $sysmaps_r[<sysmapids>]                   The list of readable maps.
515	 * @param array $elements                                 The map elements.
516	 * @param array $elements[<elementid>]
517	 */
518	private static function setHasElements(array &$sysmaps_r, array $elements) {
519		foreach ($elements as $elementid => $selements) {
520			foreach ($selements as $selement) {
521				$sysmaps_r[$selement['sysmapid']]['has_elements'] = true;
522			}
523		}
524	}
525
526	/**
527	 * Returns map ids which will be checked for permissions.
528	 *
529	 * @param array $sysmaps_r
530	 * @param array $sysmaps_r[<sysmapid>]['permission']
531	 * @param array $sysmaps_rw
532	 * @param array $sysmaps_rw[<sysmapid>]
533	 */
534	private static function getSysmapIds(array $sysmaps_r, array $sysmaps_rw) {
535		$sysmapids = $sysmaps_rw;
536
537		foreach ($sysmaps_r as $sysmapid => $sysmap) {
538			if ($sysmap['permission'] == PERM_NONE) {
539				$sysmapids[$sysmapid] = true;
540			}
541		}
542
543		return array_keys($sysmapids);
544	}
545
546	/**
547	 * Validates the input parameters for the delete() method.
548	 *
549	 * @param array $sysmapids
550	 *
551	 * @throws APIException if the input is invalid.
552	 */
553	protected function validateDelete(array $sysmapids) {
554		if (!$sysmapids) {
555			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
556		}
557
558		$db_maps = $this->get([
559			'output' => ['sysmapid'],
560			'sysmapids' => $sysmapids,
561			'editable' => true,
562			'preservekeys' => true
563		]);
564
565		foreach ($sysmapids as $sysmapid) {
566			if (!array_key_exists($sysmapid, $db_maps)) {
567				self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
568			}
569		}
570	}
571
572	/**
573	 * Validate the input parameters for the create() method.
574	 *
575	 * @param array $maps		maps data array
576	 *
577	 * @throws APIException if the input is invalid.
578	 */
579	protected function validateCreate(array $maps) {
580		if (!$maps) {
581			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
582		}
583
584		$user_data = self::$userData;
585
586		$map_db_fields = [
587			'name' => null,
588			'width' => null,
589			'height' => null,
590			'urls' => [],
591			'selements' => [],
592			'links' => []
593		];
594
595		// Validate mandatory fields and map name.
596		foreach ($maps as $map) {
597			if (!check_db_fields($map_db_fields, $map)) {
598				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect fields for sysmap.'));
599			}
600		}
601
602		// Check for duplicate names.
603		$duplicate = CArrayHelper::findDuplicate($maps, 'name');
604		if ($duplicate) {
605			self::exception(ZBX_API_ERROR_PARAMETERS,
606				_s('Duplicate "name" value "%1$s" for map.', $duplicate['name'])
607			);
608		}
609
610		// Check if map already exists.
611		$db_maps = $this->get([
612			'output' => ['name'],
613			'filter' => ['name' => zbx_objectValues($maps, 'name')],
614			'nopermissions' => true,
615			'limit' => 1
616		]);
617
618		if ($db_maps) {
619			self::exception(ZBX_API_ERROR_PARAMETERS, _s('Map "%1$s" already exists.', $db_maps[0]['name']));
620		}
621
622		$private_validator = new CLimitedSetValidator([
623			'values' => [PUBLIC_SHARING, PRIVATE_SHARING]
624		]);
625
626		$permission_validator = new CLimitedSetValidator([
627			'values' => [PERM_READ, PERM_READ_WRITE]
628		]);
629
630		$show_suppressed_types = [ZBX_PROBLEM_SUPPRESSED_FALSE, ZBX_PROBLEM_SUPPRESSED_TRUE];
631		$show_suppressed_validator = new CLimitedSetValidator(['values' => $show_suppressed_types]);
632
633		$expandproblem_types = [SYSMAP_PROBLEMS_NUMBER, SYSMAP_SINGLE_PROBLEM, SYSMAP_PROBLEMS_NUMBER_CRITICAL];
634		$expandproblem_validator = new CLimitedSetValidator(['values' => $expandproblem_types]);
635
636		// Continue to check 2 more mandatory fields and other optional fields.
637		foreach ($maps as $map) {
638			// Check mandatory fields "width" and "height".
639			if ($map['width'] > 65535 || $map['width'] < 1) {
640				self::exception(ZBX_API_ERROR_PARAMETERS,
641					_s('Incorrect "width" value for map "%1$s".', $map['name'])
642				);
643			}
644
645			if ($map['height'] > 65535 || $map['height'] < 1) {
646				self::exception(ZBX_API_ERROR_PARAMETERS,
647					_s('Incorrect "height" value for map "%1$s".', $map['name'])
648				);
649			}
650
651			// Check if owner can be set.
652			if (array_key_exists('userid', $map)) {
653				if ($map['userid'] === '' || $map['userid'] === null || $map['userid'] === false) {
654					self::exception(ZBX_API_ERROR_PARAMETERS, _('Map owner cannot be empty.'));
655				}
656				elseif ($map['userid'] != $user_data['userid'] && $user_data['type'] != USER_TYPE_SUPER_ADMIN
657						&& $user_data['type'] != USER_TYPE_ZABBIX_ADMIN) {
658					self::exception(ZBX_API_ERROR_PARAMETERS, _('Only administrators can set map owner.'));
659				}
660			}
661
662			// Check for invalid "private" values.
663			if (array_key_exists('private', $map)) {
664				if (!$private_validator->validate($map['private'])) {
665					self::exception(ZBX_API_ERROR_PARAMETERS,
666						_s('Incorrect "private" value "%1$s" for map "%2$s".', $map['private'], $map['name'])
667					);
668				}
669			}
670
671			// Check for invalid "show_suppressed" values.
672			if (array_key_exists('show_suppressed', $map)
673					&& !$show_suppressed_validator->validate($map['show_suppressed'])) {
674				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
675					'show_suppressed', _s('value must be one of %1$s', implode(', ', $show_suppressed_types))
676				));
677			}
678
679			if (array_key_exists('expandproblem', $map) && !$expandproblem_validator->validate($map['expandproblem'])) {
680				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'expandproblem',
681					_s('value must be one of %1$s', implode(', ', $expandproblem_types))
682				));
683			}
684
685			$userids = [];
686
687			// Map user shares.
688			if (array_key_exists('users', $map)) {
689				if (!is_array($map['users'])) {
690					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
691				}
692
693				$required_fields = ['userid', 'permission'];
694
695				foreach ($map['users'] as $share) {
696					// Check required parameters.
697					$missing_keys = array_diff($required_fields, array_keys($share));
698
699					if ($missing_keys) {
700						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
701							'User sharing is missing parameters: %1$s for map "%2$s".',
702							implode(', ', $missing_keys),
703							$map['name']
704						));
705					}
706					else {
707						foreach ($required_fields as $field) {
708							if ($share[$field] === '' || $share[$field] === null) {
709								self::exception(ZBX_API_ERROR_PARAMETERS, _s(
710									'Sharing option "%1$s" is missing a value for map "%2$s".',
711									$field,
712									$map['name']
713								));
714							}
715						}
716					}
717
718					if (!$permission_validator->validate($share['permission'])) {
719						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
720							'Incorrect "permission" value "%1$s" in users for map "%2$s".',
721							$share['permission'],
722							$map['name']
723						));
724					}
725
726					if (array_key_exists('private', $map) && $map['private'] == PUBLIC_SHARING
727							&& $share['permission'] == PERM_READ) {
728						self::exception(ZBX_API_ERROR_PARAMETERS,
729							_s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name'])
730						);
731					}
732
733					if (array_key_exists($share['userid'], $userids)) {
734						self::exception(ZBX_API_ERROR_PARAMETERS,
735							_s('Duplicate userid "%1$s" in users for map "%2$s".', $share['userid'], $map['name'])
736						);
737					}
738
739					$userids[$share['userid']] = $share['userid'];
740				}
741			}
742
743			if (array_key_exists('userid', $map) && $map['userid']) {
744				$userids[$map['userid']] = $map['userid'];
745			}
746
747			// Users validation.
748			if ($userids) {
749				$db_users = API::User()->get([
750					'userids' => $userids,
751					'countOutput' => true
752				]);
753
754				if (count($userids) != $db_users) {
755					self::exception(ZBX_API_ERROR_PARAMETERS,
756						_s('Incorrect user ID specified for map "%1$s".', $map['name'])
757					);
758				}
759			}
760
761			// Map user group shares.
762			if (array_key_exists('userGroups', $map)) {
763				if (!is_array($map['userGroups'])) {
764					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
765				}
766
767				$shared_user_groupids = [];
768				$required_fields = ['usrgrpid', 'permission'];
769
770				foreach ($map['userGroups'] as $share) {
771					// Check required parameters.
772					$missing_keys = array_diff($required_fields, array_keys($share));
773
774					if ($missing_keys) {
775						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
776							'User group sharing is missing parameters: %1$s for map "%2$s".',
777							implode(', ', $missing_keys),
778							$map['name']
779						));
780					}
781					else {
782						foreach ($required_fields as $field) {
783							if ($share[$field] === '' || $share[$field] === null) {
784								self::exception(ZBX_API_ERROR_PARAMETERS, _s(
785									'Field "%1$s" is missing a value for map "%2$s".',
786									$field,
787									$map['name']
788								));
789							}
790						}
791					}
792
793					if (!$permission_validator->validate($share['permission'])) {
794						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
795							'Incorrect "permission" value "%1$s" in user groups for map "%2$s".',
796							$share['permission'],
797							$map['name']
798						));
799					}
800
801					if (array_key_exists('private', $map) && $map['private'] == PUBLIC_SHARING
802							&& $share['permission'] == PERM_READ) {
803						self::exception(ZBX_API_ERROR_PARAMETERS,
804							_s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name'])
805						);
806					}
807
808					if (array_key_exists($share['usrgrpid'], $shared_user_groupids)) {
809						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
810							'Duplicate usrgrpid "%1$s" in user groups for map "%2$s".',
811							$share['usrgrpid'],
812							$map['name']
813						));
814					}
815
816					$shared_user_groupids[$share['usrgrpid']] = $share['usrgrpid'];
817				}
818
819				if ($shared_user_groupids) {
820					$db_user_groups = API::UserGroup()->get([
821						'usrgrpids' => $shared_user_groupids,
822						'countOutput' => true
823					]);
824
825					if (count($shared_user_groupids) != $db_user_groups) {
826						self::exception(ZBX_API_ERROR_PARAMETERS,
827							_s('Incorrect user group ID specified for map "%1$s".', $map['name'])
828						);
829					}
830				}
831
832				unset($shared_user_groupids);
833			}
834
835			// Map labels.
836			$map_labels = ['label_type' => ['typeName' => _('icon')]];
837
838			if (array_key_exists('label_format', $map) && $map['label_format'] == SYSMAP_LABEL_ADVANCED_ON) {
839				$map_labels['label_type_hostgroup'] = [
840					'string' => 'label_string_hostgroup',
841					'typeName' => _('host group')
842				];
843				$map_labels['label_type_host'] = [
844					'string' => 'label_string_host',
845					'typeName' => _('host')
846				];
847				$map_labels['label_type_trigger'] = [
848					'string' => 'label_string_trigger',
849					'typeName' => _('trigger')
850				];
851				$map_labels['label_type_map'] = [
852					'string' => 'label_string_map',
853					'typeName' => _('map')
854				];
855				$map_labels['label_type_image'] = [
856					'string' => 'label_string_image',
857					'typeName' => _('image')
858				];
859			}
860
861			foreach ($map_labels as $label_name => $label_data) {
862				if (!array_key_exists($label_name, $map)) {
863					continue;
864				}
865
866				if (sysmapElementLabel($map[$label_name]) === false) {
867					self::exception(ZBX_API_ERROR_PARAMETERS,
868						_s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name'])
869					);
870				}
871
872				if ($map[$label_name] == MAP_LABEL_TYPE_CUSTOM) {
873					if (!array_key_exists('string', $label_data)) {
874						self::exception(ZBX_API_ERROR_PARAMETERS,
875							_s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name'])
876						);
877					}
878
879					if (!array_key_exists($label_data['string'], $map) || zbx_empty($map[$label_data['string']])) {
880						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
881								'Custom label for map "%2$s" elements of type "%1$s" may not be empty.',
882								$label_data['typeName'],
883								$map['name']
884							)
885						);
886					}
887				}
888
889				if ($label_name == 'label_type_image' && $map[$label_name] == MAP_LABEL_TYPE_STATUS) {
890					self::exception(ZBX_API_ERROR_PARAMETERS,
891						_s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name'])
892					);
893				}
894
895				if ($label_name === 'label_type' || $label_name === 'label_type_host') {
896					continue;
897				}
898
899				if ($map[$label_name] == MAP_LABEL_TYPE_IP) {
900					self::exception(ZBX_API_ERROR_PARAMETERS,
901						_s('Incorrect %1$s label type value for map "%2$s".', $label_data['typeName'], $map['name'])
902					);
903				}
904			}
905
906			// Validating grid options.
907			$possibleGridSizes = [20, 40, 50, 75, 100];
908
909			// Grid size.
910			if (array_key_exists('grid_size', $map) && !in_array($map['grid_size'], $possibleGridSizes)) {
911				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
912					'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s".',
913					$map['grid_size'],
914					implode('", "', $possibleGridSizes)
915				));
916			}
917
918			// Grid auto align.
919			if (array_key_exists('grid_align', $map) && $map['grid_align'] != SYSMAP_GRID_ALIGN_ON
920					&& $map['grid_align'] != SYSMAP_GRID_ALIGN_OFF) {
921				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
922					'Value "%1$s" is invalid for parameter "grid_align". Choices are: "%2$s" and "%3$s"',
923					$map['grid_align'],
924					SYSMAP_GRID_ALIGN_ON,
925					SYSMAP_GRID_ALIGN_OFF
926				));
927			}
928
929			// Grid show.
930			if (array_key_exists('grid_show', $map) && $map['grid_show'] != SYSMAP_GRID_SHOW_ON
931					&& $map['grid_show'] != SYSMAP_GRID_SHOW_OFF) {
932				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
933					'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s" and "%3$s".',
934					$map['grid_show'],
935					SYSMAP_GRID_SHOW_ON,
936					SYSMAP_GRID_SHOW_OFF
937				));
938			}
939
940			// Urls.
941			if (array_key_exists('urls', $map) && $map['urls']) {
942				$url_names = zbx_toHash($map['urls'], 'name');
943
944				foreach ($map['urls'] as $url) {
945					if ($url['name'] === '' || $url['url'] === '') {
946						self::exception(ZBX_API_ERROR_PARAMETERS,
947							_s('URL should have both "name" and "url" fields for map "%1$s".', $map['name'])
948						);
949					}
950
951					if (!array_key_exists($url['name'], $url_names)) {
952						self::exception(ZBX_API_ERROR_PARAMETERS,
953							_s('URL name should be unique for map "%1$s".', $map['name'])
954						);
955					}
956
957					$url_validate_options = ['allow_user_macro' => false];
958					if ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
959						$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_HOST;
960					}
961					elseif ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
962						$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_TRIGGER;
963					}
964					else {
965						$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_NONE;
966					}
967
968					if (!CHtmlUrlValidator::validate($url['url'], $url_validate_options)) {
969						self::exception(ZBX_API_ERROR_PARAMETERS, _('Wrong value for url field.'));
970					}
971
972					unset($url_names[$url['name']]);
973				}
974			}
975
976			if (array_key_exists('selements', $map)) {
977				if (!is_array($map['selements'])) {
978					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
979				}
980				elseif (!CMapHelper::checkSelementPermissions($map['selements'])) {
981					self::exception(ZBX_API_ERROR_PERMISSIONS,
982						_('No permissions to referred object or it does not exist!')
983					);
984				}
985			}
986
987			// Map selement links.
988			if (array_key_exists('links', $map) && $map['links']) {
989				$selementids = zbx_objectValues($map['selements'], 'selementid');
990
991				foreach ($map['links'] as $link) {
992					if (!in_array($link['selementid1'], $selementids)) {
993						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
994							'Link selementid1 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".',
995							$link['selementid1'],
996							$map['name']
997						));
998					}
999
1000					if (!in_array($link['selementid2'], $selementids)) {
1001						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1002							'Link selementid2 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".',
1003							$link['selementid2'],
1004							$map['name']
1005						));
1006					}
1007				}
1008			}
1009		}
1010	}
1011
1012	/**
1013	 * Validate the input parameters for the update() method.
1014	 *
1015	 * @param array $maps			maps data array
1016	 * @param array $db_maps		db maps data array
1017	 *
1018	 * @throws APIException if the input is invalid.
1019	 */
1020	protected function validateUpdate(array $maps, array $db_maps) {
1021		if (!$maps) {
1022			self::exception(ZBX_API_ERROR_PARAMETERS, _('Empty input parameter.'));
1023		}
1024
1025		$user_data = self::$userData;
1026
1027		// Validate given IDs.
1028		$this->checkObjectIds($maps, 'sysmapid',
1029			_('No "%1$s" given for map.'),
1030			_('Empty map ID.'),
1031			_('Incorrect map ID.')
1032		);
1033
1034		$check_names = [];
1035
1036		foreach ($maps as $map) {
1037			// Check if this map exists and user has write permissions.
1038			if (!array_key_exists($map['sysmapid'], $db_maps)) {
1039				self::exception(ZBX_API_ERROR_PERMISSIONS,
1040					_('No permissions to referred object or it does not exist!')
1041				);
1042			}
1043
1044			// Validate "name" field.
1045			if (array_key_exists('name', $map)) {
1046				if (is_array($map['name'])) {
1047					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1048				}
1049				elseif ($map['name'] === '' || $map['name'] === null || $map['name'] === false) {
1050					self::exception(ZBX_API_ERROR_PARAMETERS, _('Map name cannot be empty.'));
1051				}
1052
1053				if ($db_maps[$map['sysmapid']]['name'] !== $map['name']) {
1054					$check_names[] = $map;
1055				}
1056			}
1057		}
1058
1059		if ($check_names) {
1060			// Check for duplicate names.
1061			$duplicate = CArrayHelper::findDuplicate($check_names, 'name');
1062			if ($duplicate) {
1063				self::exception(ZBX_API_ERROR_PARAMETERS,
1064					_s('Duplicate "name" value "%1$s" for map.', $duplicate['name'])
1065				);
1066			}
1067
1068			$db_map_names = $this->get([
1069				'output' => ['sysmapid', 'name'],
1070				'filter' => ['name' => zbx_objectValues($check_names, 'name')],
1071				'nopermissions' => true
1072			]);
1073			$db_map_names = zbx_toHash($db_map_names, 'name');
1074
1075			// Check for existing names.
1076			foreach ($check_names as $map) {
1077				if (array_key_exists($map['name'], $db_map_names)
1078						&& bccomp($db_map_names[$map['name']]['sysmapid'], $map['sysmapid']) != 0) {
1079					self::exception(ZBX_API_ERROR_PARAMETERS,
1080						_s('Map "%1$s" already exists.', $map['name'])
1081					);
1082				}
1083			}
1084		}
1085
1086		$private_validator = new CLimitedSetValidator([
1087			'values' => [PUBLIC_SHARING, PRIVATE_SHARING]
1088		]);
1089
1090		$permission_validator = new CLimitedSetValidator([
1091			'values' => [PERM_READ, PERM_READ_WRITE]
1092		]);
1093
1094		$show_suppressed_types = [ZBX_PROBLEM_SUPPRESSED_FALSE, ZBX_PROBLEM_SUPPRESSED_TRUE];
1095		$show_suppressed_validator = new CLimitedSetValidator(['values' => $show_suppressed_types]);
1096
1097		$expandproblem_types = [SYSMAP_PROBLEMS_NUMBER, SYSMAP_SINGLE_PROBLEM, SYSMAP_PROBLEMS_NUMBER_CRITICAL];
1098		$expandproblem_validator = new CLimitedSetValidator(['values' => $expandproblem_types]);
1099
1100		foreach ($maps as $map) {
1101			// Check if owner can be set.
1102			if (array_key_exists('userid', $map)) {
1103				if ($map['userid'] === '' || $map['userid'] === null || $map['userid'] === false) {
1104					self::exception(ZBX_API_ERROR_PARAMETERS, _('Map owner cannot be empty.'));
1105				}
1106				elseif ($map['userid'] != $user_data['userid'] && $user_data['type'] != USER_TYPE_SUPER_ADMIN
1107						&& $user_data['type'] != USER_TYPE_ZABBIX_ADMIN) {
1108					self::exception(ZBX_API_ERROR_PARAMETERS, _('Only administrators can set map owner.'));
1109				}
1110			}
1111
1112			// Unset extra field.
1113			unset($db_maps[$map['sysmapid']]['userid']);
1114
1115			$map = array_merge($db_maps[$map['sysmapid']], $map);
1116
1117			// Check "width" and "height" fields.
1118			if ($map['width'] > 65535 || $map['width'] < 1) {
1119				self::exception(ZBX_API_ERROR_PARAMETERS,
1120					_s('Incorrect "width" value for map "%1$s".', $map['name'])
1121				);
1122			}
1123
1124			if ($map['height'] > 65535 || $map['height'] < 1) {
1125				self::exception(ZBX_API_ERROR_PARAMETERS,
1126					_s('Incorrect "height" value for map "%1$s".', $map['name'])
1127				);
1128			}
1129
1130			if (!$private_validator->validate($map['private'])) {
1131				self::exception(ZBX_API_ERROR_PARAMETERS,
1132					_s('Incorrect "private" value "%1$s" for map "%2$s".', $map['private'], $map['name'])
1133				);
1134			}
1135
1136			// Check for invalid "show_suppressed" values.
1137			if (array_key_exists('show_suppressed', $map)
1138					&& !$show_suppressed_validator->validate($map['show_suppressed'])) {
1139				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
1140					'show_suppressed', _s('value must be one of %1$s', implode(', ', $show_suppressed_types))
1141				));
1142			}
1143
1144			if (array_key_exists('expandproblem', $map) && !$expandproblem_validator->validate($map['expandproblem'])) {
1145				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'expandproblem',
1146					_s('value must be one of %1$s', implode(', ', $expandproblem_types))
1147				));
1148			}
1149
1150			$userids = [];
1151
1152			// Map user shares.
1153			if (array_key_exists('users', $map)) {
1154				if (!is_array($map['users'])) {
1155					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1156				}
1157
1158				$required_fields = ['userid', 'permission'];
1159
1160				foreach ($map['users'] as $share) {
1161					// Check required parameters.
1162					$missing_keys = array_diff($required_fields, array_keys($share));
1163
1164					if ($missing_keys) {
1165						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1166							'User sharing is missing parameters: %1$s for map "%2$s".',
1167							implode(', ', $missing_keys),
1168							$map['name']
1169						));
1170					}
1171					else {
1172						foreach ($required_fields as $field) {
1173							if ($share[$field] === '' || $share[$field] === null) {
1174								self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1175									'Sharing option "%1$s" is missing a value for map "%2$s".',
1176									$field,
1177									$map['name']
1178								));
1179							}
1180						}
1181					}
1182
1183					if (!$permission_validator->validate($share['permission'])) {
1184						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1185							'Incorrect "permission" value "%1$s" in users for map "%2$s".',
1186							$share['permission'],
1187							$map['name']
1188						));
1189					}
1190
1191					if ($map['private'] == PUBLIC_SHARING && $share['permission'] == PERM_READ) {
1192						self::exception(ZBX_API_ERROR_PARAMETERS,
1193							_s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name'])
1194						);
1195					}
1196
1197					if (array_key_exists($share['userid'], $userids)) {
1198						self::exception(ZBX_API_ERROR_PARAMETERS,
1199							_s('Duplicate userid "%1$s" in users for map "%2$s".', $share['userid'], $map['name'])
1200						);
1201					}
1202
1203					$userids[$share['userid']] = $share['userid'];
1204				}
1205			}
1206
1207			if (array_key_exists('userid', $map) && $map['userid']) {
1208				$userids[$map['userid']] = $map['userid'];
1209			}
1210
1211			// Users validation.
1212			if ($userids) {
1213				$db_users = API::User()->get([
1214					'userids' => $userids,
1215					'countOutput' => true
1216				]);
1217
1218				if (count($userids) != $db_users) {
1219					self::exception(ZBX_API_ERROR_PARAMETERS,
1220						_s('Incorrect user ID specified for map "%1$s".', $map['name'])
1221					);
1222				}
1223			}
1224
1225			// Map user group shares.
1226			if (array_key_exists('userGroups', $map)) {
1227				if (!is_array($map['userGroups'])) {
1228					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1229				}
1230
1231				$shared_user_groupids = [];
1232				$required_fields = ['usrgrpid', 'permission'];
1233
1234				foreach ($map['userGroups'] as $share) {
1235					// Check required parameters.
1236					$missing_keys = array_diff($required_fields, array_keys($share));
1237
1238					if ($missing_keys) {
1239						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1240							'User group sharing is missing parameters: %1$s for map "%2$s".',
1241							implode(', ', $missing_keys),
1242							$map['name'])
1243						);
1244					}
1245					else {
1246						foreach ($required_fields as $field) {
1247							if ($share[$field] === '' || $share[$field] === null) {
1248								self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1249									'Sharing option "%1$s" is missing a value for map "%2$s".',
1250									$field,
1251									$map['name']
1252								));
1253							}
1254						}
1255					}
1256
1257					if (!$permission_validator->validate($share['permission'])) {
1258						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1259							'Incorrect "permission" value "%1$s" in user groups for map "%2$s".',
1260							$share['permission'],
1261							$map['name']
1262						));
1263					}
1264
1265					if ($map['private'] == PUBLIC_SHARING && $share['permission'] == PERM_READ) {
1266						self::exception(ZBX_API_ERROR_PARAMETERS,
1267							_s('Map "%1$s" is public and read-only sharing is disallowed.', $map['name'])
1268						);
1269					}
1270
1271					if (array_key_exists($share['usrgrpid'], $shared_user_groupids)) {
1272						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1273							'Duplicate usrgrpid "%1$s" in user groups for map "%2$s".',
1274							$share['usrgrpid'],
1275							$map['name']
1276						));
1277					}
1278
1279					$shared_user_groupids[$share['usrgrpid']] = $share['usrgrpid'];
1280				}
1281
1282				if ($shared_user_groupids) {
1283					$db_user_groups = API::UserGroup()->get([
1284						'usrgrpids' => $shared_user_groupids,
1285						'countOutput' => true
1286					]);
1287
1288					if (count($shared_user_groupids) != $db_user_groups) {
1289						self::exception(ZBX_API_ERROR_PARAMETERS,
1290							_s('Incorrect user group ID specified for map "%1$s".', $map['name'])
1291						);
1292					}
1293				}
1294
1295				unset($shared_user_groupids);
1296			}
1297
1298			// Map labels.
1299			$map_labels = ['label_type' => ['typeName' => _('icon')]];
1300
1301			if (array_key_exists('label_format', $map)
1302					&& $map['label_format'] == SYSMAP_LABEL_ADVANCED_ON) {
1303				$map_labels['label_type_hostgroup'] = [
1304					'string' => 'label_string_hostgroup',
1305					'typeName' => _('host group')
1306				];
1307				$map_labels['label_type_host'] = [
1308					'string' => 'label_string_host',
1309					'typeName' => _('host')
1310				];
1311				$map_labels['label_type_trigger'] = [
1312					'string' => 'label_string_trigger',
1313					'typeName' => _('trigger')
1314				];
1315				$map_labels['label_type_map'] = [
1316					'string' => 'label_string_map',
1317					'typeName' => _('map')
1318				];
1319				$map_labels['label_type_image'] = [
1320					'string' => 'label_string_image',
1321					'typeName' => _('image')
1322				];
1323			}
1324
1325			foreach ($map_labels as $label_name => $labelData) {
1326				if (!array_key_exists($label_name, $map)) {
1327					continue;
1328				}
1329
1330				if (sysmapElementLabel($map[$label_name]) === false) {
1331					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1332						'Incorrect %1$s label type value for map "%2$s".',
1333						$labelData['typeName'],
1334						$map['name']
1335					));
1336				}
1337
1338				if ($map[$label_name] == MAP_LABEL_TYPE_CUSTOM) {
1339					if (!array_key_exists('string', $labelData)) {
1340						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1341							'Incorrect %1$s label type value for map "%2$s".',
1342							$labelData['typeName'],
1343							$map['name']
1344						));
1345					}
1346
1347					if (!array_key_exists($labelData['string'], $map) || zbx_empty($map[$labelData['string']])) {
1348						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1349							'Custom label for map "%2$s" elements of type "%1$s" may not be empty.',
1350							$labelData['typeName'],
1351							$map['name']
1352						));
1353					}
1354				}
1355
1356				if ($label_name === 'label_type_image' && $map[$label_name] == MAP_LABEL_TYPE_STATUS) {
1357					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1358						'Incorrect %1$s label type value for map "%2$s".',
1359						$labelData['typeName'],
1360						$map['name']
1361					));
1362				}
1363
1364				if ($label_name === 'label_type' || $label_name === 'label_type_host') {
1365					continue;
1366				}
1367
1368				if ($map[$label_name] == MAP_LABEL_TYPE_IP) {
1369					self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1370						'Incorrect %1$s label type value for map "%2$s".',
1371						$labelData['typeName'],
1372						$map['name']
1373					));
1374				}
1375			}
1376
1377			// Validating grid options.
1378			$possibleGridSizes = [20, 40, 50, 75, 100];
1379
1380			// Grid size.
1381			if (array_key_exists('grid_size', $map) && !in_array($map['grid_size'], $possibleGridSizes)) {
1382				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1383					'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s".',
1384					$map['grid_size'],
1385					implode('", "', $possibleGridSizes)
1386				));
1387			}
1388
1389			// Grid auto align.
1390			if (array_key_exists('grid_align', $map) && $map['grid_align'] != SYSMAP_GRID_ALIGN_ON
1391					&& $map['grid_align'] != SYSMAP_GRID_ALIGN_OFF) {
1392				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1393					'Value "%1$s" is invalid for parameter "grid_align". Choices are: "%2$s" and "%3$s"',
1394					$map['grid_align'],
1395					SYSMAP_GRID_ALIGN_ON,
1396					SYSMAP_GRID_ALIGN_OFF
1397				));
1398			}
1399
1400			// Grid show.
1401			if (array_key_exists('grid_show', $map) && $map['grid_show'] != SYSMAP_GRID_SHOW_ON
1402					&& $map['grid_show'] != SYSMAP_GRID_SHOW_OFF) {
1403				self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1404					'Value "%1$s" is invalid for parameter "grid_show". Choices are: "%2$s" and "%3$s".',
1405					$map['grid_show'],
1406					SYSMAP_GRID_SHOW_ON,
1407					SYSMAP_GRID_SHOW_OFF
1408				));
1409			}
1410
1411			// Urls.
1412			if (array_key_exists('urls', $map) && !empty($map['urls'])) {
1413				$urlNames = zbx_toHash($map['urls'], 'name');
1414
1415				foreach ($map['urls'] as $url) {
1416					if ($url['name'] === '' || $url['url'] === '') {
1417						self::exception(ZBX_API_ERROR_PARAMETERS,
1418							_s('URL should have both "name" and "url" fields for map "%1$s".', $map['name'])
1419						);
1420					}
1421
1422					if (!array_key_exists($url['name'], $urlNames)) {
1423						self::exception(ZBX_API_ERROR_PARAMETERS,
1424							_s('URL name should be unique for map "%1$s".', $map['name'])
1425						);
1426					}
1427
1428					$url_validate_options = ['allow_user_macro' => false];
1429					if ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
1430						$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_HOST;
1431					}
1432					elseif ($url['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
1433						$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_TRIGGER;
1434					}
1435					else {
1436						$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_NONE;
1437					}
1438
1439					if (!CHtmlUrlValidator::validate($url['url'], $url_validate_options)) {
1440						self::exception(ZBX_API_ERROR_PARAMETERS, _('Wrong value for url field.'));
1441					}
1442
1443					unset($urlNames[$url['name']]);
1444				}
1445			}
1446
1447			if (array_key_exists('selements', $map) && !is_array($map['selements'])) {
1448				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
1449			}
1450
1451			// Map selement links.
1452			if (array_key_exists('links', $map) && $map['links']) {
1453				$selementids = zbx_objectValues($map['selements'], 'selementid');
1454
1455				foreach ($map['links'] as $link) {
1456					if (!in_array($link['selementid1'], $selementids)) {
1457						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1458							'Link selementid1 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".',
1459							$link['selementid1'],
1460							$map['name']
1461						));
1462					}
1463
1464					if (!in_array($link['selementid2'], $selementids)) {
1465						self::exception(ZBX_API_ERROR_PARAMETERS, _s(
1466							'Link selementid2 field is pointing to a nonexistent map selement ID "%1$s" for map "%2$s".',
1467							$link['selementid2'],
1468							$map['name']
1469						));
1470					}
1471				}
1472			}
1473		}
1474
1475		// Validate circular reference.
1476		foreach ($maps as &$map) {
1477			$map = array_merge($db_maps[$map['sysmapid']], $map);
1478			$this->cref_maps[$map['sysmapid']] = $map;
1479		}
1480		unset($map);
1481
1482		$this->validateCircularReference($maps);
1483	}
1484
1485	/**
1486	 * Hash of maps data for circular reference validation. Map id is used as key.
1487	 *
1488	 * @var array
1489	 */
1490	protected $cref_maps;
1491
1492	/**
1493	 * Validate maps for circular reference.
1494	 *
1495	 * @param array $maps   Array of maps to be validated for circular reference.
1496	 *
1497	 * @throws APIException if input is invalid.
1498	 */
1499	protected function validateCircularReference(array $maps) {
1500		foreach ($maps as $map) {
1501			if (!array_key_exists('selements', $map) || !$map['selements']) {
1502				continue;
1503			}
1504			$cref_mapids = array_key_exists('sysmapid', $map) ? [$map['sysmapid']] : [];
1505
1506			foreach ($map['selements'] as $selement) {
1507				if (!$this->validateCircularReferenceRecursive($selement, $cref_mapids)) {
1508					self::exception(ZBX_API_ERROR_PARAMETERS,
1509						_s('Cannot add map element of the map "%1$s" due to circular reference.', $map['name'])
1510					);
1511				}
1512			}
1513		}
1514	}
1515
1516	/**
1517	 * Recursive map element circular reference validation.
1518	 *
1519	 * @param array $selement       Map selement data array.
1520	 * @param array $cref_mapids    Array of map ids for current recursion step.
1521	 *
1522	 * @return bool
1523	 */
1524	protected function validateCircularReferenceRecursive(array $selement, &$cref_mapids) {
1525		if ($selement['elementtype'] != SYSMAP_ELEMENT_TYPE_MAP) {
1526			return true;
1527		}
1528
1529		$sysmapid = $selement['elements'][0]['sysmapid'];
1530
1531		if ($sysmapid !== null && !array_key_exists($sysmapid, $this->cref_maps)) {
1532			$db_maps = DB::select($this->tableName, [
1533				'output' => ['name'],
1534				'filter' => ['sysmapid' => $sysmapid]
1535			]);
1536
1537			if ($db_maps) {
1538				$db_map = $db_maps[0];
1539				$db_map['selements'] = [];
1540				$selements = DB::select('sysmaps_elements', [
1541					'output' => ['elementid'],
1542					'filter' => [
1543						'sysmapid' => $sysmapid,
1544						'elementtype' => SYSMAP_ELEMENT_TYPE_MAP
1545					]
1546				]);
1547
1548				foreach ($selements as $selement) {
1549					$db_map['selements'][] = [
1550						'elementtype' => SYSMAP_ELEMENT_TYPE_MAP,
1551						'elements' => [
1552							['sysmapid' => $selement['elementid']]
1553						]
1554					];
1555				}
1556
1557				$this->cref_maps[$sysmapid] = $db_map;
1558				unset($selements);
1559			}
1560			else {
1561				self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
1562			}
1563		}
1564
1565		if (in_array($sysmapid, $cref_mapids)) {
1566			$cref_mapids[] = $sysmapid;
1567			return false;
1568		}
1569
1570		// Find maps that reference the current element, and if one has selements, check all of them recursively.
1571		if ($sysmapid !== null && array_key_exists('selements', $this->cref_maps[$sysmapid])
1572				&& is_array($this->cref_maps[$sysmapid]['selements'])) {
1573			$cref_mapids[] = $sysmapid;
1574
1575			foreach ($this->cref_maps[$sysmapid]['selements'] as $selement) {
1576				if (!$this->validateCircularReferenceRecursive($selement, $cref_mapids)) {
1577					return false;
1578				}
1579			}
1580
1581			array_pop($cref_mapids);
1582		}
1583
1584		return true;
1585	}
1586
1587	/**
1588	 * Add map.
1589	 *
1590	 * @param array  $maps
1591	 * @param string $maps['name']
1592	 * @param array  $maps['width']
1593	 * @param int    $maps['height']
1594	 * @param string $maps['backgroundid']
1595	 * @param string $maps['highlight']
1596	 * @param array  $maps['label_type']
1597	 * @param int    $maps['label_location']
1598	 * @param int    $maps['grid_size']			size of one grid cell. 100 refers to 100x100 and so on.
1599	 * @param int    $maps['grid_show']			does grid need to be shown. Constants: SYSMAP_GRID_SHOW_ON / SYSMAP_GRID_SHOW_OFF
1600	 * @param int    $maps['grid_align']		do elements need to be aligned to the grid. Constants: SYSMAP_GRID_ALIGN_ON / SYSMAP_GRID_ALIGN_OFF
1601	 *
1602	 * @return array
1603	 */
1604	public function create($maps) {
1605		$maps = zbx_toArray($maps);
1606
1607		$this->validateCreate($maps);
1608
1609		foreach ($maps as &$map) {
1610			$map['userid'] = array_key_exists('userid', $map) ? $map['userid'] : self::$userData['userid'];
1611		}
1612		unset($map);
1613
1614		$sysmapids = DB::insert('sysmaps', $maps);
1615
1616		$shared_users = [];
1617		$shared_user_groups = [];
1618		$urls = [];
1619		$shapes = [];
1620		$selements = [];
1621		$links = [];
1622		$api_shape_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [
1623			'type' =>				['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [SYSMAP_SHAPE_TYPE_RECTANGLE, SYSMAP_SHAPE_TYPE_ELLIPSE])],
1624			'x' =>					['type' => API_INT32],
1625			'y' =>					['type' => API_INT32],
1626			'width' =>				['type' => API_INT32],
1627			'height' =>				['type' => API_INT32],
1628			'font' =>				['type' => API_INT32, 'in' => '0:12'],
1629			'font_size' =>			['type' => API_INT32, 'in' => '1:250'],
1630			'text_halign' =>		['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_HALIGN_CENTER, SYSMAP_SHAPE_LABEL_HALIGN_LEFT, SYSMAP_SHAPE_LABEL_HALIGN_RIGHT])],
1631			'text_valign' =>		['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_VALIGN_MIDDLE, SYSMAP_SHAPE_LABEL_VALIGN_TOP, SYSMAP_SHAPE_LABEL_VALIGN_BOTTOM])],
1632			'border_type' =>		['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])],
1633			'border_width' =>		['type' => API_INT32, 'in' => '0:50'],
1634			'border_color' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')],
1635			'background_color' =>	['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'background_color')],
1636			'font_color' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'font_color')],
1637			'text' =>				['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'text')],
1638			'zindex' =>				['type' => API_INT32]
1639		]];
1640		$api_line_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [
1641			'x1' =>					['type' => API_INT32],
1642			'y1' =>					['type' => API_INT32],
1643			'x2' =>					['type' => API_INT32],
1644			'y2' =>					['type' => API_INT32],
1645			'line_type' =>			['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])],
1646			'line_width' =>			['type' => API_INT32, 'in' => '0:50'],
1647			'line_color' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')],
1648			'zindex' =>				['type' => API_INT32]
1649		]];
1650		$default_shape_width = DB::getDefault('sysmap_shape', 'width');
1651		$default_shape_height = DB::getDefault('sysmap_shape', 'height');
1652
1653		foreach ($sysmapids as $key => $sysmapid) {
1654			// Map user shares.
1655			if (array_key_exists('users', $maps[$key])) {
1656				foreach ($maps[$key]['users'] as $user) {
1657					$shared_users[] = [
1658						'sysmapid' => $sysmapid,
1659						'userid' => $user['userid'],
1660						'permission' => $user['permission']
1661					];
1662				}
1663			}
1664
1665			// Map user group shares.
1666			if (array_key_exists('userGroups', $maps[$key])) {
1667				foreach ($maps[$key]['userGroups'] as $user_group) {
1668					$shared_user_groups[] = [
1669						'sysmapid' => $sysmapid,
1670						'usrgrpid' => $user_group['usrgrpid'],
1671						'permission' => $user_group['permission']
1672					];
1673				}
1674			}
1675
1676			if (array_key_exists('urls', $maps[$key])) {
1677				foreach ($maps[$key]['urls'] as $url) {
1678					$url['sysmapid'] = $sysmapid;
1679					$urls[] = $url;
1680				}
1681			}
1682
1683			if (array_key_exists('selements', $maps[$key])) {
1684				foreach ($maps[$key]['selements'] as $snum => $selement) {
1685					$maps[$key]['selements'][$snum]['sysmapid'] = $sysmapid;
1686				}
1687
1688				$selements = array_merge($selements, $maps[$key]['selements']);
1689			}
1690
1691			if (array_key_exists('shapes', $maps[$key])) {
1692				$path = '/'.($key + 1).'/shape';
1693				$api_shape_rules['fields']['x']['in'] = '0:'.$maps[$key]['width'];
1694				$api_shape_rules['fields']['y']['in'] = '0:'.$maps[$key]['height'];
1695				$api_shape_rules['fields']['width']['in'] = '1:'.$maps[$key]['width'];
1696				$api_shape_rules['fields']['height']['in'] = '1:'.$maps[$key]['height'];
1697
1698				foreach ($maps[$key]['shapes'] as &$shape) {
1699					$shape['width'] = array_key_exists('width', $shape) ? $shape['width'] : $default_shape_width;
1700					$shape['height'] = array_key_exists('height', $shape) ? $shape['height'] : $default_shape_height;
1701				}
1702				unset($shape);
1703
1704				if (!CApiInputValidator::validate($api_shape_rules, $maps[$key]['shapes'], $path, $error)) {
1705					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
1706				}
1707
1708				foreach ($maps[$key]['shapes'] as $snum => $shape) {
1709					$maps[$key]['shapes'][$snum]['sysmapid'] = $sysmapid;
1710				}
1711
1712				$shapes = array_merge($shapes, $maps[$key]['shapes']);
1713			}
1714
1715			if (array_key_exists('lines', $maps[$key])) {
1716				$path = '/'.($key + 1).'/line';
1717				$api_line_rules['fields']['x1']['in'] = '0:'.$maps[$key]['width'];
1718				$api_line_rules['fields']['y1']['in'] = '0:'.$maps[$key]['height'];
1719				$api_line_rules['fields']['x2']['in'] = '0:'.$maps[$key]['width'];
1720				$api_line_rules['fields']['y2']['in'] = '0:'.$maps[$key]['height'];
1721
1722				foreach ($maps[$key]['lines'] as &$line) {
1723					$line['x2'] = array_key_exists('x2', $line) ? $line['x2'] : $default_shape_width;
1724					$line['y2'] = array_key_exists('y2', $line) ? $line['y2'] : $default_shape_height;
1725				}
1726				unset($line);
1727
1728				if (!CApiInputValidator::validate($api_line_rules, $maps[$key]['lines'], $path, $error)) {
1729					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
1730				}
1731
1732				foreach ($maps[$key]['lines'] as $line) {
1733					$shape = CMapHelper::convertLineToShape($line);
1734					$shape['sysmapid'] = $sysmapid;
1735					$shapes[] = $shape;
1736				}
1737			}
1738
1739			if (array_key_exists('links', $maps[$key])) {
1740				foreach ($maps[$key]['links'] as $lnum => $link) {
1741					$maps[$key]['links'][$lnum]['sysmapid'] = $sysmapid;
1742				}
1743
1744				$links = array_merge($links, $maps[$key]['links']);
1745			}
1746		}
1747
1748		DB::insert('sysmap_user', $shared_users);
1749		DB::insert('sysmap_usrgrp', $shared_user_groups);
1750		DB::insert('sysmap_url', $urls);
1751
1752		if ($selements) {
1753			$selementids = $this->createSelements($selements);
1754
1755			if ($links) {
1756				$map_virt_selements = [];
1757				foreach ($selementids['selementids'] as $key => $selementid) {
1758					$map_virt_selements[$selements[$key]['selementid']] = $selementid;
1759				}
1760
1761				foreach ($links as $key => $link) {
1762					$links[$key]['selementid1'] = $map_virt_selements[$link['selementid1']];
1763					$links[$key]['selementid2'] = $map_virt_selements[$link['selementid2']];
1764				}
1765				unset($map_virt_selements);
1766
1767				$linkids = $this->createLinks($links);
1768
1769				$link_triggers = [];
1770				foreach ($linkids['linkids'] as $key => $linkId) {
1771					if (!array_key_exists('linktriggers', $links[$key])) {
1772						continue;
1773					}
1774
1775					foreach ($links[$key]['linktriggers'] as $link_trigger) {
1776						$link_trigger['linkid'] = $linkId;
1777						$link_triggers[] = $link_trigger;
1778					}
1779				}
1780
1781				if ($link_triggers) {
1782					$this->createLinkTriggers($link_triggers);
1783				}
1784			}
1785		}
1786
1787		if ($shapes) {
1788			$this->createShapes($shapes);
1789		}
1790
1791		return ['sysmapids' => $sysmapids];
1792	}
1793
1794	/**
1795	 * Update map.
1796	 *
1797	 * @param array  $maps						multidimensional array with Hosts data
1798	 * @param string $maps['sysmapid']
1799	 * @param string $maps['name']
1800	 * @param array  $maps['width']
1801	 * @param int    $maps['height']
1802	 * @param string $maps['backgroundid']
1803	 * @param array  $maps['label_type']
1804	 * @param int    $maps['label_location']
1805	 * @param int    $maps['grid_size']			size of one grid cell. 100 refers to 100x100 and so on.
1806	 * @param int    $maps['grid_show']			does grid need to be shown. Constants: SYSMAP_GRID_SHOW_ON / SYSMAP_GRID_SHOW_OFF
1807	 * @param int    $maps['grid_align']		do elements need to be aligned to the grid. Constants: SYSMAP_GRID_ALIGN_ON / SYSMAP_GRID_ALIGN_OFF
1808	 *
1809	 * @return array
1810	 */
1811	public function update(array $maps) {
1812		$maps = zbx_toArray($maps);
1813		$sysmapids = zbx_objectValues($maps, 'sysmapid');
1814
1815		$db_maps = $this->get([
1816			'output' => API_OUTPUT_EXTEND,
1817			'sysmapids' => zbx_objectValues($maps, 'sysmapid'),
1818			'selectLinks' => API_OUTPUT_EXTEND,
1819			'selectSelements' => API_OUTPUT_EXTEND,
1820			'selectShapes' => ['sysmap_shapeid', 'type', 'x', 'y', 'width', 'height', 'text', 'font', 'font_size',
1821				'font_color', 'text_halign', 'text_valign', 'border_type', 'border_width', 'border_color',
1822				'background_color', 'zindex'
1823			],
1824			'selectLines' => ['sysmap_shapeid', 'x1', 'y1', 'x2', 'y2', 'line_type', 'line_width', 'line_color',
1825				'zindex'
1826			],
1827			'selectUrls' => ['sysmapid', 'sysmapurlid', 'name', 'url', 'elementtype'],
1828			'selectUsers' => ['sysmapuserid', 'sysmapid', 'userid', 'permission'],
1829			'selectUserGroups' => ['sysmapusrgrpid', 'sysmapid', 'usrgrpid', 'permission'],
1830			'editable' => true,
1831			'preservekeys' => true
1832		]);
1833
1834		$this->validateUpdate($maps, $db_maps);
1835
1836		$update_maps = [];
1837		$url_ids_to_delete = [];
1838		$urls_to_update = [];
1839		$urls_to_add = [];
1840		$selements_to_delete = [];
1841		$selements_to_update = [];
1842		$selements_to_add = [];
1843		$shapes_to_delete = [];
1844		$shapes_to_update = [];
1845		$shapes_to_add = [];
1846		$links_to_delete = [];
1847		$links_to_update = [];
1848		$links_to_add = [];
1849		$shared_userids_to_delete = [];
1850		$shared_users_to_update = [];
1851		$shared_users_to_add = [];
1852		$shared_user_groupids_to_delete = [];
1853		$shared_user_groups_to_update = [];
1854		$shared_user_groups_to_add = [];
1855		$api_shape_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [
1856			'sysmap_shapeid' =>		['type' => API_ID],
1857			'type' =>				['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_TYPE_RECTANGLE, SYSMAP_SHAPE_TYPE_ELLIPSE])],
1858			'x' =>					['type' => API_INT32],
1859			'y' =>					['type' => API_INT32],
1860			'width' =>				['type' => API_INT32],
1861			'height' =>				['type' => API_INT32],
1862			'font' =>				['type' => API_INT32, 'in' => '0:12'],
1863			'font_size' =>			['type' => API_INT32, 'in' => '1:250'],
1864			'text_halign' =>		['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_HALIGN_CENTER, SYSMAP_SHAPE_LABEL_HALIGN_LEFT, SYSMAP_SHAPE_LABEL_HALIGN_RIGHT])],
1865			'text_valign' =>		['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_LABEL_VALIGN_MIDDLE, SYSMAP_SHAPE_LABEL_VALIGN_TOP, SYSMAP_SHAPE_LABEL_VALIGN_BOTTOM])],
1866			'border_type' =>		['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])],
1867			'border_width' =>		['type' => API_INT32, 'in' => '0:50'],
1868			'border_color' =>		['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')],
1869			'background_color' =>	['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'background_color')],
1870			'font_color' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'font_color')],
1871			'text' =>				['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'text')],
1872			'zindex' =>				['type' => API_INT32]
1873		]];
1874		$api_line_rules = ['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'fields' => [
1875			'sysmap_shapeid' =>		['type' => API_ID],
1876			'x1' =>					['type' => API_INT32],
1877			'y1' =>					['type' => API_INT32],
1878			'x2' =>					['type' => API_INT32],
1879			'y2' =>					['type' => API_INT32],
1880			'line_type' =>			['type' => API_INT32, 'in' => implode(',', [SYSMAP_SHAPE_BORDER_TYPE_NONE, SYSMAP_SHAPE_BORDER_TYPE_SOLID, SYSMAP_SHAPE_BORDER_TYPE_DOTTED, SYSMAP_SHAPE_BORDER_TYPE_DASHED])],
1881			'line_width' =>			['type' => API_INT32, 'in' => '0:50'],
1882			'line_color' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('sysmap_shape', 'border_color')],
1883			'zindex' =>				['type' => API_INT32]
1884		]];
1885		$default_shape_width = DB::getDefault('sysmap_shape', 'width');
1886		$default_shape_height = DB::getDefault('sysmap_shape', 'height');
1887
1888		foreach ($maps as $index => $map) {
1889			$update_maps[] = [
1890				'values' => $map,
1891				'where' => ['sysmapid' => $map['sysmapid']]
1892			];
1893
1894			$db_map = $db_maps[$map['sysmapid']];
1895
1896			// Map user shares.
1897			if (array_key_exists('users', $map)) {
1898				$user_shares_diff = zbx_array_diff($map['users'], $db_map['users'], 'userid');
1899
1900				foreach ($user_shares_diff['both'] as $update_user_share) {
1901					$shared_users_to_update[] = [
1902						'values' => $update_user_share,
1903						'where' => ['userid' => $update_user_share['userid'], 'sysmapid' => $map['sysmapid']]
1904					];
1905				}
1906
1907				foreach ($user_shares_diff['first'] as $new_shared_user) {
1908					$new_shared_user['sysmapid'] = $map['sysmapid'];
1909					$shared_users_to_add[] = $new_shared_user;
1910				}
1911
1912				$shared_userids_to_delete = array_merge($shared_userids_to_delete,
1913					zbx_objectValues($user_shares_diff['second'], 'sysmapuserid')
1914				);
1915			}
1916
1917			// Map user group shares.
1918			if (array_key_exists('userGroups', $map)) {
1919				$user_group_shares_diff = zbx_array_diff($map['userGroups'], $db_map['userGroups'],
1920					'usrgrpid'
1921				);
1922
1923				foreach ($user_group_shares_diff['both'] as $update_user_share) {
1924					$shared_user_groups_to_update[] = [
1925						'values' => $update_user_share,
1926						'where' => ['usrgrpid' => $update_user_share['usrgrpid'], 'sysmapid' => $map['sysmapid']]
1927					];
1928				}
1929
1930				foreach ($user_group_shares_diff['first'] as $new_shared_user_group) {
1931					$new_shared_user_group['sysmapid'] = $map['sysmapid'];
1932					$shared_user_groups_to_add[] = $new_shared_user_group;
1933				}
1934
1935				$shared_user_groupids_to_delete = array_merge($shared_user_groupids_to_delete,
1936					zbx_objectValues($user_group_shares_diff['second'], 'sysmapusrgrpid')
1937				);
1938			}
1939
1940			// Urls.
1941			if (array_key_exists('urls', $map)) {
1942				$url_diff = zbx_array_diff($map['urls'], $db_map['urls'], 'name');
1943
1944				foreach ($url_diff['both'] as $updateUrl) {
1945					$urls_to_update[] = [
1946						'values' => $updateUrl,
1947						'where' => ['name' => $updateUrl['name'], 'sysmapid' => $map['sysmapid']]
1948					];
1949				}
1950
1951				foreach ($url_diff['first'] as $new_url) {
1952					$new_url['sysmapid'] = $map['sysmapid'];
1953					$urls_to_add[] = $new_url;
1954				}
1955
1956				$url_ids_to_delete = array_merge($url_ids_to_delete,
1957					zbx_objectValues($url_diff['second'], 'sysmapurlid')
1958				);
1959			}
1960
1961			// Map elements.
1962			if (array_key_exists('selements', $map)) {
1963				$selement_diff = zbx_array_diff($map['selements'], $db_map['selements'], 'selementid');
1964
1965				// We need sysmapid for add operations.
1966				foreach ($selement_diff['first'] as $new_selement) {
1967					$new_selement['sysmapid'] = $map['sysmapid'];
1968					$selements_to_add[] = $new_selement;
1969				}
1970
1971				foreach ($selement_diff['both'] as &$selement) {
1972					$selement['sysmapid'] = $map['sysmapid'];
1973				}
1974				unset($selement);
1975
1976				$selements_to_update = array_merge($selements_to_update, $selement_diff['both']);
1977				$selements_to_delete = array_merge($selements_to_delete, $selement_diff['second']);
1978			}
1979
1980			$map_width = array_key_exists('width', $map) ? $map['width'] : $db_map['width'];
1981			$map_height = array_key_exists('height', $map) ? $map['height'] : $db_map['height'];
1982
1983			// Map shapes.
1984			if (array_key_exists('shapes', $map)) {
1985				$map['shapes'] = array_values($map['shapes']);
1986
1987				foreach ($map['shapes'] as &$shape) {
1988					$shape['width'] = array_key_exists('width', $shape) ? $shape['width'] : $default_shape_width;
1989					$shape['height'] = array_key_exists('height', $shape) ? $shape['height'] : $default_shape_height;
1990				}
1991				unset($shape);
1992
1993				$shape_diff = zbx_array_diff($map['shapes'], $db_map['shapes'], 'sysmap_shapeid');
1994
1995				$path = '/'.($index + 1).'/shape';
1996				foreach ($shape_diff['first'] as $new_shape) {
1997					if (array_key_exists('sysmap_shapeid', $new_shape)) {
1998						self::exception(ZBX_API_ERROR_PARAMETERS,
1999							_('No permissions to referred object or it does not exist!')
2000						);
2001					}
2002				}
2003
2004				unset($api_shape_rules['fields']['sysmap_shapeid']);
2005				$api_shape_rules['fields']['type']['flags'] = API_REQUIRED;
2006				$api_shape_rules['fields']['x']['in'] = '0:'.$map_width;
2007				$api_shape_rules['fields']['y']['in'] = '0:'.$map_height;
2008				$api_shape_rules['fields']['width']['in'] = '1:'.$map_width;
2009				$api_shape_rules['fields']['height']['in'] = '1:'.$map_height;
2010
2011				if (!CApiInputValidator::validate($api_shape_rules, $shape_diff['first'], $path, $error)) {
2012					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
2013				}
2014
2015				$api_shape_rules['fields']['sysmap_shapeid'] = ['type' => API_ID, 'flags' => API_REQUIRED];
2016				$api_shape_rules['fields']['type']['flags'] = 0;
2017				if (!CApiInputValidator::validate($api_shape_rules, $shape_diff['both'], $path, $error)) {
2018					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
2019				}
2020
2021				$shapes_to_update = array_merge($shapes_to_update, $shape_diff['both']);
2022				$shapes_to_delete = array_merge($shapes_to_delete, $shape_diff['second']);
2023
2024				// We need sysmapid for add operations.
2025				foreach ($shape_diff['first'] as $new_shape) {
2026					$new_shape['sysmapid'] = $map['sysmapid'];
2027					$shapes_to_add[] = $new_shape;
2028				}
2029			}
2030
2031			if (array_key_exists('lines', $map)) {
2032				$map['lines'] = array_values($map['lines']);
2033				$shapes = [];
2034
2035				$api_line_rules['fields']['x1']['in'] = '0:'.$map_width;
2036				$api_line_rules['fields']['y1']['in'] = '0:'.$map_height;
2037				$api_line_rules['fields']['x2']['in'] = '0:'.$map_width;
2038				$api_line_rules['fields']['y2']['in'] = '0:'.$map_height;
2039
2040				$path = '/'.($index + 1).'/line';
2041
2042				foreach ($map['lines'] as &$line) {
2043					$line['x2'] = array_key_exists('x2', $line) ? $line['x2'] : $default_shape_width;
2044					$line['y2'] = array_key_exists('y2', $line) ? $line['y2'] : $default_shape_height;
2045				}
2046				unset($line);
2047
2048				if (!CApiInputValidator::validate($api_line_rules, $map['lines'], $path, $error)) {
2049					self::exception(ZBX_API_ERROR_PARAMETERS, $error);
2050				}
2051
2052				foreach ($map['lines'] as $line) {
2053					$shapes[] = CMapHelper::convertLineToShape($line);
2054				}
2055
2056				$line_diff = zbx_array_diff($shapes, $db_map['lines'], 'sysmap_shapeid');
2057
2058				foreach ($line_diff['first'] as $new_line) {
2059					if (array_key_exists('sysmap_shapeid', $new_line)) {
2060						self::exception(ZBX_API_ERROR_PARAMETERS,
2061							_('No permissions to referred object or it does not exist!')
2062						);
2063					}
2064				}
2065
2066				$shapes_to_update = array_merge($shapes_to_update, $line_diff['both']);
2067				$shapes_to_delete = array_merge($shapes_to_delete, $line_diff['second']);
2068
2069				// We need sysmapid for add operations.
2070				foreach ($line_diff['first'] as $new_shape) {
2071					$new_shape['sysmapid'] = $map['sysmapid'];
2072					$shapes_to_add[] = $new_shape;
2073				}
2074			}
2075
2076			// Links.
2077			if (array_key_exists('links', $map)) {
2078				$link_diff = zbx_array_diff($map['links'], $db_map['links'], 'linkid');
2079
2080				// We need sysmapId for add operations.
2081				foreach ($link_diff['first'] as $newLink) {
2082					$newLink['sysmapid'] = $map['sysmapid'];
2083
2084					$links_to_add[] = $newLink;
2085				}
2086
2087				$links_to_update = array_merge($links_to_update, $link_diff['both']);
2088				$links_to_delete = array_merge($links_to_delete, $link_diff['second']);
2089			}
2090		}
2091
2092		DB::update('sysmaps', $update_maps);
2093
2094		// User shares.
2095		DB::insert('sysmap_user', $shared_users_to_add);
2096		DB::update('sysmap_user', $shared_users_to_update);
2097
2098		if ($shared_userids_to_delete) {
2099			DB::delete('sysmap_user', ['sysmapuserid' => $shared_userids_to_delete]);
2100		}
2101
2102		// User group shares.
2103		DB::insert('sysmap_usrgrp', $shared_user_groups_to_add);
2104		DB::update('sysmap_usrgrp', $shared_user_groups_to_update);
2105
2106		if ($shared_user_groupids_to_delete) {
2107			DB::delete('sysmap_usrgrp', ['sysmapusrgrpid' => $shared_user_groupids_to_delete]);
2108		}
2109
2110		// Urls.
2111		DB::insert('sysmap_url', $urls_to_add);
2112		DB::update('sysmap_url', $urls_to_update);
2113
2114		if ($url_ids_to_delete) {
2115			DB::delete('sysmap_url', ['sysmapurlid' => $url_ids_to_delete]);
2116		}
2117
2118		// Selements.
2119		$new_selementids = ['selementids' => []];
2120		if ($selements_to_add) {
2121			$new_selementids = $this->createSelements($selements_to_add);
2122		}
2123
2124		if ($selements_to_update) {
2125			$this->updateSelements($selements_to_update);
2126		}
2127
2128		if ($selements_to_delete) {
2129			$this->deleteSelements($selements_to_delete);
2130		}
2131
2132		if ($shapes_to_add) {
2133			$this->createShapes($shapes_to_add);
2134		}
2135
2136		if ($shapes_to_update) {
2137			$this->updateShapes($shapes_to_update);
2138		}
2139
2140		if ($shapes_to_delete) {
2141			$this->deleteShapes($shapes_to_delete);
2142		}
2143
2144		// Links.
2145		if ($links_to_add || $links_to_update) {
2146			$selements_names = [];
2147			foreach ($new_selementids['selementids'] as $key => $selementId) {
2148				$selements_names[$selements_to_add[$key]['selementid']] = $selementId;
2149			}
2150
2151			foreach ($selements_to_update as $selement) {
2152				$selements_names[$selement['selementid']] = $selement['selementid'];
2153			}
2154
2155			foreach ($links_to_add as $key => $link) {
2156				if (array_key_exists($link['selementid1'], $selements_names)) {
2157					$links_to_add[$key]['selementid1'] = $selements_names[$link['selementid1']];
2158				}
2159				if (array_key_exists($link['selementid2'], $selements_names)) {
2160					$links_to_add[$key]['selementid2'] = $selements_names[$link['selementid2']];
2161				}
2162			}
2163
2164			foreach ($links_to_update as $key => $link) {
2165				if (array_key_exists($link['selementid1'], $selements_names)) {
2166					$links_to_update[$key]['selementid1'] = $selements_names[$link['selementid1']];
2167				}
2168				if (array_key_exists($link['selementid2'], $selements_names)) {
2169					$links_to_update[$key]['selementid2'] = $selements_names[$link['selementid2']];
2170				}
2171			}
2172
2173			unset($selements_names);
2174		}
2175
2176		$new_linkids = ['linkids' => []];
2177		$update_linkids = ['linkids' => []];
2178
2179		if ($links_to_add) {
2180			$new_linkids = $this->createLinks($links_to_add);
2181		}
2182
2183		if ($links_to_update) {
2184			$update_linkids = $this->updateLinks($links_to_update);
2185		}
2186
2187		if ($links_to_delete) {
2188			$this->deleteLinks($links_to_delete);
2189		}
2190
2191		// Link triggers.
2192		$link_triggers_to_delete = [];
2193		$link_triggers_to_update = [];
2194		$link_triggers_to_add = [];
2195
2196		foreach ($new_linkids['linkids'] as $key => $linkid) {
2197			if (!array_key_exists('linktriggers', $links_to_add[$key])) {
2198				continue;
2199			}
2200
2201			foreach ($links_to_add[$key]['linktriggers'] as $link_trigger) {
2202				$link_trigger['linkid'] = $linkid;
2203				$link_triggers_to_add[] = $link_trigger;
2204			}
2205		}
2206
2207		$db_links = [];
2208
2209		$link_trigger_resource = DBselect(
2210			'SELECT slt.* FROM sysmaps_link_triggers slt WHERE '.dbConditionInt('slt.linkid', $update_linkids['linkids'])
2211		);
2212		while ($db_link_trigger = DBfetch($link_trigger_resource)) {
2213			zbx_subarray_push($db_links, $db_link_trigger['linkid'], $db_link_trigger);
2214		}
2215
2216		foreach ($update_linkids['linkids'] as $key => $linkid) {
2217			if (!array_key_exists('linktriggers', $links_to_update[$key])) {
2218				continue;
2219			}
2220
2221			$db_link_triggers = array_key_exists($linkid, $db_links) ? $db_links[$linkid] : [];
2222			$db_link_triggers_diff = zbx_array_diff($links_to_update[$key]['linktriggers'],
2223				$db_link_triggers, 'linktriggerid'
2224			);
2225
2226			foreach ($db_link_triggers_diff['first'] as $new_link_trigger) {
2227				$new_link_trigger['linkid'] = $linkid;
2228				$link_triggers_to_add[] = $new_link_trigger;
2229			}
2230
2231			$link_triggers_to_update = array_merge($link_triggers_to_update, $db_link_triggers_diff['both']);
2232			$link_triggers_to_delete = array_merge($link_triggers_to_delete, $db_link_triggers_diff['second']);
2233		}
2234
2235		if ($link_triggers_to_delete) {
2236			$this->deleteLinkTriggers($link_triggers_to_delete);
2237		}
2238
2239		if ($link_triggers_to_add) {
2240			$this->createLinkTriggers($link_triggers_to_add);
2241		}
2242
2243		if ($link_triggers_to_update) {
2244			$this->updateLinkTriggers($link_triggers_to_update);
2245		}
2246
2247		return ['sysmapids' => $sysmapids];
2248	}
2249
2250	/**
2251	 * Delete Map.
2252	 *
2253	 * @param array $sysmapids
2254	 *
2255	 * @return array
2256	 */
2257	public function delete(array $sysmapids) {
2258		$this->validateDelete($sysmapids);
2259
2260		DB::delete('sysmaps_elements', [
2261			'elementid' => $sysmapids,
2262			'elementtype' => SYSMAP_ELEMENT_TYPE_MAP
2263		]);
2264		DB::delete('screens_items', [
2265			'resourceid' => $sysmapids,
2266			'resourcetype' => SCREEN_RESOURCE_MAP
2267		]);
2268		DB::delete('profiles', [
2269			'idx' => 'web.maps.sysmapid',
2270			'value_id' => $sysmapids
2271		]);
2272		DB::delete('profiles', [
2273			'idx' => 'web.favorite.sysmapids',
2274			'source' => 'sysmapid',
2275			'value_id' => $sysmapids
2276		]);
2277		DB::delete('sysmaps', ['sysmapid' => $sysmapids]);
2278
2279		return ['sysmapids' => $sysmapids];
2280	}
2281
2282	protected function addRelatedObjects(array $options, array $result) {
2283		$result = parent::addRelatedObjects($options, $result);
2284
2285		$sysmapIds = array_keys($result);
2286
2287		// adding elements
2288		if ($options['selectSelements'] !== null && $options['selectSelements'] != API_OUTPUT_COUNT) {
2289			$selements = API::getApiService()->select('sysmaps_elements', [
2290				'output' => $this->outputExtend($options['selectSelements'], ['selementid', 'sysmapid', 'elementtype',
2291					'elementid', 'elementsubtype'
2292				]),
2293				'filter' => ['sysmapid' => $sysmapIds],
2294				'preservekeys' => true
2295			]);
2296			$relation_map = $this->createRelationMap($selements, 'sysmapid', 'selementid');
2297
2298			if ($this->outputIsRequested('elements', $options['selectSelements']) && $selements) {
2299				foreach ($selements as &$selement) {
2300					$selement['elements'] = [];
2301				}
2302				unset($selement);
2303
2304				$selement_triggers = DBselect(
2305					'SELECT st.selementid,st.triggerid,st.selement_triggerid'.
2306					' FROM sysmap_element_trigger st,triggers tr'.
2307					' WHERE '.dbConditionInt('st.selementid', array_keys($selements)).
2308						' AND st.triggerid=tr.triggerid'.
2309					' ORDER BY tr.priority DESC,st.selement_triggerid'
2310				);
2311				while ($selement_trigger = DBfetch($selement_triggers)) {
2312					$selements[$selement_trigger['selementid']]['elements'][] = [
2313						'triggerid' => $selement_trigger['triggerid']
2314					];
2315					if ($selements[$selement_trigger['selementid']]['elementid'] == 0) {
2316						$selements[$selement_trigger['selementid']]['elementid'] = $selement_trigger['triggerid'];
2317					}
2318				}
2319
2320				$single_element_types = [SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP,
2321					SYSMAP_ELEMENT_TYPE_HOST_GROUP
2322				];
2323
2324				foreach ($selements as &$selement) {
2325					if (in_array($selement['elementtype'], $single_element_types)) {
2326						switch ($selement['elementtype']) {
2327							case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
2328								$field = 'groupid';
2329								break;
2330
2331							case SYSMAP_ELEMENT_TYPE_HOST:
2332								$field = 'hostid';
2333								break;
2334
2335							case SYSMAP_ELEMENT_TYPE_MAP:
2336								$field = 'sysmapid';
2337								break;
2338						}
2339						$selement['elements'][] = [$field => $selement['elementid']];
2340					}
2341				}
2342				unset($selement);
2343			}
2344
2345			// add selement URLs
2346			if ($this->outputIsRequested('urls', $options['selectSelements'])) {
2347				foreach ($selements as &$selement) {
2348					$selement['urls'] = [];
2349				}
2350				unset($selement);
2351
2352				if (!is_null($options['expandUrls'])) {
2353					$dbMapUrls = DBselect(
2354						'SELECT su.sysmapurlid,su.sysmapid,su.name,su.url,su.elementtype'.
2355						' FROM sysmap_url su'.
2356						' WHERE '.dbConditionInt('su.sysmapid', $sysmapIds)
2357					);
2358					while ($mapUrl = DBfetch($dbMapUrls)) {
2359						foreach ($selements as $snum => $selement) {
2360							if (bccomp($selement['sysmapid'], $mapUrl['sysmapid']) == 0
2361									&& (($selement['elementtype'] == $mapUrl['elementtype']
2362											&& $selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP
2363											)
2364											|| ($selement['elementsubtype'] == SYSMAP_ELEMENT_SUBTYPE_HOST_GROUP_ELEMENTS
2365													&& $mapUrl['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST)
2366										)) {
2367								$selements[$snum]['urls'][] = $mapUrl;
2368							}
2369						}
2370					}
2371				}
2372
2373				$dbSelementUrls = DBselect(
2374					'SELECT seu.sysmapelementurlid,seu.selementid,seu.name,seu.url'.
2375					' FROM sysmap_element_url seu'.
2376					' WHERE '.dbConditionInt('seu.selementid', array_keys($selements))
2377				);
2378				while ($selementUrl = DBfetch($dbSelementUrls)) {
2379					$selements[$selementUrl['selementid']]['urls'][] = $selementUrl;
2380				}
2381
2382				if (!is_null($options['expandUrls'])) {
2383					$resolve_opt = ['resolve_element_urls' => true];
2384					$selements = CMacrosResolverHelper::resolveMacrosInMapElements($selements, $resolve_opt);
2385				}
2386			}
2387
2388			if ($this->outputIsRequested('permission', $options['selectSelements']) && $selements) {
2389				if ($options['editable']) {
2390					foreach ($selements as &$selement) {
2391						$selement['permission'] = PERM_READ_WRITE;
2392					}
2393					unset($selement);
2394				}
2395				elseif (self::$userData['type'] == USER_TYPE_SUPER_ADMIN) {
2396					foreach ($selements as &$selement) {
2397						$selement['permission'] = PERM_READ;
2398					}
2399					unset($selement);
2400				}
2401				else {
2402					$ids = [
2403						SYSMAP_ELEMENT_TYPE_HOST_GROUP => [],
2404						SYSMAP_ELEMENT_TYPE_HOST => [],
2405						SYSMAP_ELEMENT_TYPE_TRIGGER => [],
2406						SYSMAP_ELEMENT_TYPE_MAP => []
2407					];
2408					$trigger_selementids = [];
2409
2410					foreach ($selements as &$selement) {
2411						switch ($selement['elementtype']) {
2412							case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
2413							case SYSMAP_ELEMENT_TYPE_HOST:
2414							case SYSMAP_ELEMENT_TYPE_MAP:
2415								$ids[$selement['elementtype']][$selement['elementid']][] = $selement['selementid'];
2416								$selement['permission'] = PERM_NONE;
2417								break;
2418
2419							case SYSMAP_ELEMENT_TYPE_TRIGGER:
2420								$trigger_selementids[$selement['selementid']] = true;
2421								$selement['permission'] = PERM_NONE;
2422								break;
2423
2424							case SYSMAP_ELEMENT_TYPE_IMAGE:
2425								$selement['permission'] = PERM_READ;
2426								break;
2427						}
2428					}
2429					unset($selement);
2430
2431					$db[SYSMAP_ELEMENT_TYPE_HOST_GROUP] = $ids[SYSMAP_ELEMENT_TYPE_HOST_GROUP]
2432						? API::HostGroup()->get([
2433							'output' => [],
2434							'groupids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_HOST_GROUP]),
2435							'preservekeys' => true
2436						])
2437						: [];
2438
2439					$db[SYSMAP_ELEMENT_TYPE_HOST] = $ids[SYSMAP_ELEMENT_TYPE_HOST]
2440						? API::Host()->get([
2441							'output' => [],
2442							'hostids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_HOST]),
2443							'preservekeys' => true
2444						])
2445					: [];
2446
2447					$db[SYSMAP_ELEMENT_TYPE_MAP] = $ids[SYSMAP_ELEMENT_TYPE_MAP]
2448						? API::Map()->get([
2449							'output' => [],
2450							'sysmapids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_MAP]),
2451							'preservekeys' => true
2452						])
2453						: [];
2454
2455					if ($trigger_selementids) {
2456						$db_selement_triggers = DBselect(
2457							'SELECT st.selementid,st.triggerid'.
2458							' FROM sysmap_element_trigger st'.
2459							' WHERE '.dbConditionInt('st.selementid', array_keys($trigger_selementids))
2460						);
2461
2462						while ($db_selement_trigger = DBfetch($db_selement_triggers)) {
2463							$ids[SYSMAP_ELEMENT_TYPE_TRIGGER][$db_selement_trigger['triggerid']][] =
2464								$db_selement_trigger['selementid'];
2465						}
2466					}
2467
2468					$db[SYSMAP_ELEMENT_TYPE_TRIGGER] = $ids[SYSMAP_ELEMENT_TYPE_TRIGGER]
2469						? API::Trigger()->get([
2470							'output' => [],
2471							'triggerids' => array_keys($ids[SYSMAP_ELEMENT_TYPE_TRIGGER]),
2472							'preservekeys' => true
2473						])
2474						: [];
2475
2476					foreach ($ids as $elementtype => $elementids) {
2477						foreach ($elementids as $elementid => $selementids) {
2478							if (array_key_exists($elementid, $db[$elementtype])) {
2479								foreach ($selementids as $selementid) {
2480									$selements[$selementid]['permission'] = PERM_READ;
2481								}
2482							}
2483						}
2484					}
2485				}
2486			}
2487
2488			foreach ($selements as &$selement) {
2489				unset($selement['elementid']);
2490			}
2491			unset($selement);
2492
2493			$selements = $this->unsetExtraFields($selements,
2494				['sysmapid', 'selementid', 'elementtype', 'elementsubtype'],
2495				$options['selectSelements']
2496			);
2497			$result = $relation_map->mapMany($result, $selements, 'selements');
2498		}
2499
2500		$shape_types = [];
2501		if ($options['selectShapes'] !== null && $options['selectShapes'] != API_OUTPUT_COUNT) {
2502			$shape_types = [SYSMAP_SHAPE_TYPE_RECTANGLE, SYSMAP_SHAPE_TYPE_ELLIPSE];
2503		}
2504
2505		if ($options['selectLines'] !== null && $options['selectLines'] != API_OUTPUT_COUNT) {
2506			$shape_types[] = SYSMAP_SHAPE_TYPE_LINE;
2507		}
2508
2509		// Adding shapes.
2510		if ($shape_types) {
2511			$fields = API_OUTPUT_EXTEND;
2512
2513			if ($options['selectShapes'] != API_OUTPUT_EXTEND && $options['selectLines'] != API_OUTPUT_EXTEND) {
2514				$fields = ['sysmap_shapeid', 'sysmapid', 'type'];
2515				$mapping = [
2516					'x1' => 'x',
2517					'y1' => 'y',
2518					'x2' => 'width',
2519					'y2' =>	'height',
2520					'line_type' => 'border_type',
2521					'line_width' => 'border_width',
2522					'line_color' => 'border_color'
2523				];
2524
2525				if (is_array($options['selectLines'])) {
2526					foreach ($mapping as $source_field => $target_field) {
2527						if (in_array($source_field, $options['selectLines'])) {
2528							$fields[] = $target_field;
2529						}
2530					}
2531				}
2532
2533				if (is_array($options['selectShapes'])) {
2534					$fields = array_merge($fields, $options['selectShapes']);
2535				}
2536			}
2537
2538			$db_shapes = API::getApiService()->select('sysmap_shape', [
2539				'output' => $fields,
2540				'filter' => ['sysmapid' => $sysmapIds, 'type' => $shape_types],
2541				'preservekeys' => true
2542			]);
2543
2544			$shapes = [];
2545			$lines = [];
2546			foreach ($db_shapes as $key => $db_shape) {
2547				if ($db_shape['type'] == SYSMAP_SHAPE_TYPE_LINE) {
2548					$lines[$key] = CMapHelper::convertShapeToLine($db_shape);
2549				}
2550				else {
2551					$shapes[$key] = $db_shape;
2552				}
2553			}
2554
2555			$relation_map = $this->createRelationMap($db_shapes, 'sysmapid', 'sysmap_shapeid');
2556
2557			if ($options['selectShapes'] !== null && $options['selectShapes'] != API_OUTPUT_COUNT) {
2558				$shapes = $this->unsetExtraFields($shapes, ['sysmap_shapeid', 'type', 'x', 'y', 'width', 'height',
2559					'text', 'font', 'font_size', 'font_color', 'text_halign', 'text_valign', 'border_type',
2560					'border_width', 'border_color', 'background_color', 'zindex'
2561				], $options['selectShapes']);
2562				$shapes = $this->unsetExtraFields($shapes, ['sysmapid'], null);
2563
2564				$result = $relation_map->mapMany($result, $shapes, 'shapes');
2565			}
2566
2567			if ($options['selectLines'] !== null && $options['selectLines'] != API_OUTPUT_COUNT) {
2568				$lines = $this->unsetExtraFields($lines, ['sysmap_shapeid', 'x1', 'x2', 'y1', 'y2', 'line_type',
2569					'line_width', 'line_color', 'zindex'
2570				], $options['selectLines']);
2571				$lines = $this->unsetExtraFields($lines, ['sysmapid', 'type'], null);
2572
2573				$result = $relation_map->mapMany($result, $lines, 'lines');
2574			}
2575		}
2576
2577		// adding icon maps
2578		if ($options['selectIconMap'] !== null && $options['selectIconMap'] != API_OUTPUT_COUNT) {
2579			$iconMaps = API::getApiService()->select($this->tableName(), [
2580				'output' => ['sysmapid', 'iconmapid'],
2581				'filter' => ['sysmapid' => $sysmapIds]
2582			]);
2583
2584			$relation_map = $this->createRelationMap($iconMaps, 'sysmapid', 'iconmapid');
2585
2586			$iconMaps = API::IconMap()->get([
2587				'output' => $this->outputExtend($options['selectIconMap'], ['iconmapid']),
2588				'iconmapids' => zbx_objectValues($iconMaps, 'iconmapid'),
2589				'preservekeys' => true
2590			]);
2591
2592			$iconMaps = $this->unsetExtraFields($iconMaps, ['iconmapid'], $options['selectIconMap']);
2593
2594			$result = $relation_map->mapOne($result, $iconMaps, 'iconmap');
2595		}
2596
2597		// adding links
2598		if ($options['selectLinks'] !== null && $options['selectLinks'] != API_OUTPUT_COUNT) {
2599			$links = API::getApiService()->select('sysmaps_links', [
2600				'output' => $this->outputExtend($options['selectLinks'], ['sysmapid', 'linkid']),
2601				'filter' => ['sysmapid' => $sysmapIds],
2602				'preservekeys' => true
2603			]);
2604			$relation_map = $this->createRelationMap($links, 'sysmapid', 'linkid');
2605
2606			// add link triggers
2607			if ($this->outputIsRequested('linktriggers', $options['selectLinks'])) {
2608				$linkTriggers = DBFetchArrayAssoc(DBselect(
2609					'SELECT DISTINCT slt.*'.
2610					' FROM sysmaps_link_triggers slt'.
2611					' WHERE '.dbConditionInt('slt.linkid', $relation_map->getRelatedIds())
2612				), 'linktriggerid');
2613				$linkTriggerRelationMap = $this->createRelationMap($linkTriggers, 'linkid', 'linktriggerid');
2614				$links = $linkTriggerRelationMap->mapMany($links, $linkTriggers, 'linktriggers');
2615			}
2616
2617			if ($this->outputIsRequested('permission', $options['selectLinks']) && $links) {
2618				if ($options['editable']) {
2619					foreach ($links as &$link) {
2620						$link['permission'] = PERM_READ_WRITE;
2621					}
2622					unset($link);
2623				}
2624				elseif (self::$userData['type'] == USER_TYPE_SUPER_ADMIN) {
2625					foreach ($links as &$link) {
2626						$link['permission'] = PERM_READ;
2627					}
2628					unset($link);
2629				}
2630				else {
2631					$db_link_triggers = DBselect(
2632						'SELECT slt.linkid,slt.triggerid'.
2633						' FROM sysmaps_link_triggers slt'.
2634						' WHERE '.dbConditionInt('slt.linkid', array_keys($links))
2635					);
2636
2637					$triggerids = [];
2638					$has_triggers = [];
2639
2640					while ($db_link_trigger = DBfetch($db_link_triggers)) {
2641						$triggerids[$db_link_trigger['triggerid']][] = $db_link_trigger['linkid'];
2642						$has_triggers[$db_link_trigger['linkid']] = true;
2643					}
2644
2645					foreach ($links as &$link) {
2646						$link['permission'] = array_key_exists($link['linkid'], $has_triggers) ? PERM_NONE : PERM_READ;
2647					}
2648					unset($link);
2649
2650					$db_triggers = $triggerids
2651						? API::Trigger()->get([
2652							'output' => [],
2653							'triggerids' => array_keys($triggerids),
2654							'preservekeys' => true
2655						])
2656						: [];
2657
2658					foreach ($triggerids as $triggerid => $linkids) {
2659						if (array_key_exists($triggerid, $db_triggers)) {
2660							foreach ($linkids as $linkid) {
2661								$links[$linkid]['permission'] = PERM_READ;
2662							}
2663						}
2664					}
2665				}
2666			}
2667
2668			$links = $this->unsetExtraFields($links, ['sysmapid', 'linkid'], $options['selectLinks']);
2669			$result = $relation_map->mapMany($result, $links, 'links');
2670		}
2671
2672		// adding urls
2673		if ($options['selectUrls'] !== null && $options['selectUrls'] != API_OUTPUT_COUNT) {
2674			$links = API::getApiService()->select('sysmap_url', [
2675				'output' => $this->outputExtend($options['selectUrls'], ['sysmapid', 'sysmapurlid']),
2676				'filter' => ['sysmapid' => $sysmapIds],
2677				'preservekeys' => true
2678			]);
2679			$relation_map = $this->createRelationMap($links, 'sysmapid', 'sysmapurlid');
2680
2681			$links = $this->unsetExtraFields($links, ['sysmapid', 'sysmapurlid'], $options['selectUrls']);
2682			$result = $relation_map->mapMany($result, $links, 'urls');
2683		}
2684
2685		// Adding user shares.
2686		if ($options['selectUsers'] !== null && $options['selectUsers'] != API_OUTPUT_COUNT) {
2687			$userids = [];
2688			$relation_map = $this->createRelationMap($result, 'sysmapid', 'userid', 'sysmap_user');
2689			$related_ids = $relation_map->getRelatedIds();
2690
2691			if ($related_ids) {
2692				// Get all allowed users.
2693				$users = API::User()->get([
2694					'output' => ['userid'],
2695					'userids' => $related_ids,
2696					'preservekeys' => true
2697				]);
2698
2699				$userids = zbx_objectValues($users, 'userid');
2700			}
2701
2702			if ($userids) {
2703				$users = API::getApiService()->select('sysmap_user', [
2704					'output' => $this->outputExtend($options['selectUsers'], ['sysmapid', 'userid']),
2705					'filter' => ['sysmapid' => $sysmapIds, 'userid' => $userids],
2706					'preservekeys' => true
2707				]);
2708
2709				$relation_map = $this->createRelationMap($users, 'sysmapid', 'sysmapuserid');
2710
2711				$users = $this->unsetExtraFields($users, ['sysmapuserid', 'userid', 'permission'],
2712					$options['selectUsers']
2713				);
2714
2715				foreach ($users as &$user) {
2716					unset($user['sysmapid']);
2717				}
2718				unset($user);
2719
2720				$result = $relation_map->mapMany($result, $users, 'users');
2721			}
2722			else {
2723				foreach ($result as &$row) {
2724					$row['users'] = [];
2725				}
2726				unset($row);
2727			}
2728		}
2729
2730		// Adding user group shares.
2731		if ($options['selectUserGroups'] !== null && $options['selectUserGroups'] != API_OUTPUT_COUNT) {
2732			$groupids = [];
2733			$relation_map = $this->createRelationMap($result, 'sysmapid', 'usrgrpid', 'sysmap_usrgrp');
2734			$related_ids = $relation_map->getRelatedIds();
2735
2736			if ($related_ids) {
2737				// Get all allowed groups.
2738				$groups = API::UserGroup()->get([
2739					'output' => ['usrgrpid'],
2740					'usrgrpids' => $related_ids,
2741					'preservekeys' => true
2742				]);
2743
2744				$groupids = zbx_objectValues($groups, 'usrgrpid');
2745			}
2746
2747			if ($groupids) {
2748				$user_groups = API::getApiService()->select('sysmap_usrgrp', [
2749					'output' => $this->outputExtend($options['selectUserGroups'], ['sysmapid', 'usrgrpid']),
2750					'filter' => ['sysmapid' => $sysmapIds, 'usrgrpid' => $groupids],
2751					'preservekeys' => true
2752				]);
2753
2754				$relation_map = $this->createRelationMap($user_groups, 'sysmapid', 'sysmapusrgrpid');
2755
2756				$user_groups = $this->unsetExtraFields($user_groups, ['sysmapusrgrpid', 'usrgrpid', 'permission'],
2757					$options['selectUserGroups']
2758				);
2759
2760				foreach ($user_groups as &$user_group) {
2761					unset($user_group['sysmapid']);
2762				}
2763				unset($user_group);
2764
2765				$result = $relation_map->mapMany($result, $user_groups, 'userGroups');
2766			}
2767			else {
2768				foreach ($result as &$row) {
2769					$row['userGroups'] = [];
2770				}
2771				unset($row);
2772			}
2773		}
2774
2775		return $result;
2776	}
2777}
2778