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 map elements.
24 *
25 * @return mixed
26 */
27abstract class CMapElement extends CApiService {
28
29	protected function checkSelementInput(&$selements, $method) {
30		$create = ($method === 'createSelements');
31		$update = ($method === 'updateSelements');
32
33		$element_types = [SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP, SYSMAP_ELEMENT_TYPE_TRIGGER,
34			SYSMAP_ELEMENT_TYPE_HOST_GROUP, SYSMAP_ELEMENT_TYPE_IMAGE
35		];
36
37		$elementtype_validator = new CLimitedSetValidator(['values' => $element_types]);
38
39		if ($update) {
40			$db_selements = $this->fetchSelementsByIds(zbx_objectValues($selements, 'selementid'));
41			$selements = $this->extendFromObjects(zbx_toHash($selements, 'selementid'), $db_selements, ['elementtype', 'elements']);
42		}
43
44		foreach ($selements as &$selement) {
45			if (!is_array($selement)) {
46				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
47			}
48
49			if ($create) {
50				// Check required parameters.
51				$missing_keys = array_diff(['sysmapid', 'elementtype', 'iconid_off'], array_keys($selement));
52
53				if ($missing_keys) {
54					self::exception(ZBX_API_ERROR_PARAMETERS,
55						_s('Map element is missing parameters: %1$s', implode(', ', $missing_keys))
56					);
57				}
58			}
59
60			if (array_key_exists('urls', $selement)) {
61				$url_validate_options = ['allow_user_macro' => false];
62				if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_HOST) {
63					$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_HOST;
64				}
65				elseif ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
66					$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_TRIGGER;
67				}
68				else {
69					$url_validate_options['allow_inventory_macro'] = INVENTORY_URL_MACRO_NONE;
70				}
71
72				foreach ($selement['urls'] as $url_data) {
73					if (!CHtmlUrlValidator::validate($url_data['url'], $url_validate_options)) {
74						self::exception(ZBX_API_ERROR_PARAMETERS, _('Wrong value for url field.'));
75					}
76				}
77			}
78
79			if ($update && array_key_exists('selementid', $selement)
80						&& array_key_exists($selement['selementid'], $db_selements)) {
81				$db_selement = $db_selements[$selement['selementid']];
82			}
83
84			if (!$elementtype_validator->validate($selement['elementtype'])) {
85				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.',
86					'elementtype', _s('value must be one of %1$s', implode(', ', $element_types))
87				));
88			}
89
90			if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_IMAGE) {
91				unset($selement['elements']);
92			}
93			else {
94				if (!array_key_exists('elements', $selement)) {
95					self::exception(ZBX_API_ERROR_PARAMETERS,
96						_s('Map element is missing parameters: %1$s', 'elements')
97					);
98				}
99
100				if (!is_array($selement['elements'])) {
101					self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
102				}
103
104				if (!$selement['elements']) {
105					self::exception(ZBX_API_ERROR_PARAMETERS,
106						_s('Incorrect value for field "%1$s": %2$s.', 'elements', _('cannot be empty'))
107					);
108				}
109			}
110
111			if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
112				foreach ($selement['elements'] as $element) {
113					if (!array_key_exists('triggerid', $element)) {
114						self::exception(ZBX_API_ERROR_PARAMETERS,
115							_s('Map element is missing parameters: %1$s', 'triggerid')
116						);
117					}
118
119					if (is_array($element['triggerid'])) {
120						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
121					}
122					elseif ($element['triggerid'] === '' || $element['triggerid'] === null
123							|| $element['triggerid'] === false) {
124						self::exception(ZBX_API_ERROR_PARAMETERS,
125							_s('Incorrect value for field "%1$s": %2$s.', 'triggerid', _('cannot be empty'))
126						);
127					}
128				}
129			}
130			else {
131				if (array_key_exists('elements', $selement)) {
132					switch ($selement['elementtype']) {
133						case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
134							$field = 'groupid';
135							break;
136
137						case SYSMAP_ELEMENT_TYPE_HOST:
138							$field = 'hostid';
139							break;
140
141						case SYSMAP_ELEMENT_TYPE_MAP:
142							$field = 'sysmapid';
143							break;
144					}
145
146					$elements = reset($selement['elements']);
147
148					if (!is_array($elements)) {
149						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
150					}
151
152					if (!array_key_exists($field, $elements)) {
153						self::exception(ZBX_API_ERROR_PARAMETERS,
154							_s('Map element is missing parameters: %1$s', $field)
155						);
156					}
157
158					if (is_array($elements[$field])) {
159						self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
160					}
161					elseif ($elements[$field] === '' || $elements[$field] === null || $elements[$field] === false) {
162						self::exception(ZBX_API_ERROR_PARAMETERS,
163							_s('Incorrect value for field "%1$s": %2$s.', $field, _('cannot be empty'))
164						);
165					}
166
167					if (count($selement['elements']) > 1) {
168						self::exception(ZBX_API_ERROR_PARAMETERS,
169							_s('Incorrect value for field "%1$s": %2$s.', 'elements', _('incorrect element count'))
170						);
171					}
172				}
173			}
174
175			if (isset($selement['iconid_off']) && $selement['iconid_off'] == 0) {
176				self::exception(ZBX_API_ERROR_PARAMETERS, _s('No icon for map element ""%1$s".',
177					array_key_exists('label', $selement) ? $selement['label'] : ''
178				));
179			}
180
181			if ($create) {
182				$selement['urls'] = array_key_exists('urls', $selement) ? $selement['urls'] : [];
183			}
184		}
185		unset($selement);
186
187		// check permissions to used objects
188		if (!CMapHelper::checkSelementPermissions($selements)) {
189			self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
190		}
191
192		return $update ? $db_selements : true;
193	}
194
195	/**
196	 * Checks that shape color attributes are valid.
197	 *
198	 * @throws APIException if input is invalid.
199	 *
200	 * @param array $shapes			An array of shapes.
201	 */
202	protected function checkShapeInput($shapes) {
203		$color_validator = new CColorValidator();
204		$fields = ['border_color', 'background_color', 'font_color'];
205
206		foreach ($shapes as $shape) {
207			foreach ($fields as $field) {
208				if (array_key_exists($field, $shape) && $shape[$field] !== ''
209						&& !$color_validator->validate($shape[$field])) {
210					self::exception(ZBX_API_ERROR_PARAMETERS, $color_validator->getError());
211				}
212			}
213		}
214	}
215
216	/**
217	 * Returns a hash of map elements with the given IDs. The result also includes URL assigned to the elements.
218	 *
219	 * @param array $selementIds
220	 *
221	 * @return array
222	 */
223	protected function fetchSelementsByIds(array $selementIds) {
224		$selements = API::getApiService()->select('sysmaps_elements', [
225			'output' => API_OUTPUT_EXTEND,
226			'filter' => ['selementid' => $selementIds],
227			'preservekeys' => true
228		]);
229
230		if ($selements) {
231			foreach ($selements as &$selement) {
232				$selement['urls'] = [];
233				$selement['elements'] = [];
234			}
235			unset($selement);
236
237			$selementUrls = API::getApiService()->select('sysmap_element_url', [
238				'output' => API_OUTPUT_EXTEND,
239				'filter' => ['selementid' => $selementIds]
240			]);
241			foreach ($selementUrls as $selementUrl) {
242				$selements[$selementUrl['selementid']]['urls'][] = $selementUrl;
243			}
244
245			$selement_triggers = API::getApiService()->select('sysmap_element_trigger', [
246				'output' => ['selement_triggerid', 'selementid', 'triggerid'],
247				'filter' => ['selementid' => $selementIds]
248			]);
249
250			foreach ($selement_triggers as $selement_trigger) {
251				$selements[$selement_trigger['selementid']]['elements'][] = [
252					'selement_triggerid' => $selement_trigger['selement_triggerid'],
253					'triggerid' => $selement_trigger['triggerid']
254				];
255			}
256
257			$single_element_types = [SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP, SYSMAP_ELEMENT_TYPE_HOST_GROUP];
258			foreach ($selements as &$selement) {
259				if (in_array($selement['elementtype'], $single_element_types)) {
260					switch ($selement['elementtype']) {
261						case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
262							$field = 'groupid';
263							break;
264
265						case SYSMAP_ELEMENT_TYPE_HOST:
266							$field = 'hostid';
267							break;
268
269						case SYSMAP_ELEMENT_TYPE_MAP:
270							$field = 'sysmapid';
271							break;
272					}
273					$selement['elements'][] = [$field => $selement['elementid']];
274				}
275
276				unset($selement['elementid']);
277			}
278			unset($selement);
279		}
280
281		return $selements;
282	}
283
284	protected function checkLinkInput($links, $method) {
285		$update = ($method == 'updateLink');
286		$delete = ($method == 'deleteLink');
287
288		// permissions
289		if ($update || $delete) {
290			$linkDbFields = ['linkid' => null];
291
292			$dbLinks = API::getApiService()->select('sysmap_element_url', [
293				'filter' => ['selementid' => zbx_objectValues($links, 'linkid')],
294				'output' => ['linkid'],
295				'preservekeys' => true
296			]);
297		}
298		else {
299			$linkDbFields = [
300				'sysmapid' => null,
301				'selementid1' => null,
302				'selementid2' => null
303			];
304		}
305
306		$colorValidator = new CColorValidator();
307
308		foreach ($links as $link) {
309			if (!check_db_fields($linkDbFields, $link)) {
310				self::exception(ZBX_API_ERROR_PARAMETERS, _('Wrong fields for map link.'));
311			}
312
313			if (isset($link['color']) && !$colorValidator->validate($link['color'])) {
314				self::exception(ZBX_API_ERROR_PARAMETERS, $colorValidator->getError());
315			}
316
317			if ($update || $delete) {
318				if (!isset($dbLinks[$link['linkid']])) {
319					self::exception(ZBX_API_ERROR_PARAMETERS, _('No permissions to referred object or it does not exist!'));
320				}
321			}
322		}
323
324		return true;
325	}
326
327	/**
328	 * Add element to sysmap.
329	 *
330	 * @param array $elements[0,...]['sysmapid']
331	 * @param array $elements[0,...]['elementid']
332	 * @param array $elements[0,...]['elementtype']
333	 * @param array $elements[0,...]['label']
334	 * @param array $elements[0,...]['x']
335	 * @param array $elements[0,...]['y']
336	 * @param array $elements[0,...]['iconid_off']
337	 * @param array $elements[0,...]['iconid_on']
338	 * @param array $elements[0,...]['iconid_disabled']
339	 * @param array $elements[0,...]['urls'][0,...]
340	 * @param array $elements[0,...]['label_location']
341	 *
342	 * @return array
343	 */
344	protected function createSelements(array $selements) {
345		$selements = zbx_toArray($selements);
346
347		$this->checkSelementInput($selements, __FUNCTION__);
348
349		$single_element_types = [SYSMAP_ELEMENT_TYPE_HOST, SYSMAP_ELEMENT_TYPE_MAP, SYSMAP_ELEMENT_TYPE_HOST_GROUP];
350		foreach ($selements as &$selement) {
351			if (in_array($selement['elementtype'], $single_element_types)) {
352				$selement['elementid'] = reset($selement['elements'][0]);
353			}
354			elseif ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
355				unset($selement['elementid']);
356			}
357		}
358		unset($selement);
359
360		$selementids = DB::insert('sysmaps_elements', $selements);
361
362		$triggerids = [];
363
364		foreach ($selementids as $key => $selementid) {
365			if ($selements[$key]['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
366				foreach ($selements[$key]['elements'] as $element) {
367					$triggerids[$element['triggerid']] = true;
368				}
369			}
370		}
371
372		$db_triggers = API::Trigger()->get([
373			'output' => ['triggerid', 'priority'],
374			'triggerids' => array_keys($triggerids),
375			'preservekeys' => true
376		]);
377
378		$triggers = [];
379
380		foreach ($selementids as $key => $selementid) {
381			if ($selements[$key]['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
382				foreach ($selements[$key]['elements'] as $element) {
383					$priority = $db_triggers[$element['triggerid']]['priority'];
384					$triggers[$selementid][$priority][] = [
385						'selementid' => $selementid,
386						'triggerid' => $element['triggerid']
387					];
388				}
389				krsort($triggers[$selementid]);
390			}
391		}
392
393		$triggers_to_add = [];
394
395		foreach ($triggers as $selement_triggers) {
396			foreach ($selement_triggers as $selement_trigger_priorities) {
397				foreach ($selement_trigger_priorities as $selement_trigger_priority) {
398					$triggers_to_add[] = $selement_trigger_priority;
399				}
400			}
401		}
402
403		DB::insert('sysmap_element_trigger', $triggers_to_add);
404
405		$insertUrls = [];
406
407		foreach ($selementids as $key => $selementid) {
408			foreach ($selements[$key]['urls'] as $url) {
409				$url['selementid'] = $selementid;
410
411				$insertUrls[] = $url;
412			}
413		}
414
415		DB::insert('sysmap_element_url', $insertUrls);
416
417		$this->createSelementsTags($selements, $selementids);
418
419		return ['selementids' => $selementids];
420	}
421
422	/**
423	 * Update element to sysmap.
424	 *
425	 * @param array $elements[0,...]['selementid']
426	 * @param array $elements[0,...]['sysmapid']
427	 * @param array $elements[0,...]['elementid']
428	 * @param array $elements[0,...]['elementtype']
429	 * @param array $elements[0,...]['label']
430	 * @param array $elements[0,...]['x']
431	 * @param array $elements[0,...]['y']
432	 * @param array $elements[0,...]['iconid_off']
433	 * @param array $elements[0,...]['iconid_on']
434	 * @param array $elements[0,...]['iconid_disabled']
435	 * @param array $elements[0,...]['url']
436	 * @param array $elements[0,...]['label_location']
437	 */
438	protected function updateSelements(array $selements) {
439		$selements = zbx_toArray($selements);
440		$selementIds = [];
441
442		$db_selements = $this->checkSelementInput($selements, __FUNCTION__);
443
444		$update = [];
445		$urlsToDelete = [];
446		$urlsToUpdate = [];
447		$urlsToAdd = [];
448		$triggers_to_add = [];
449		$triggers_to_delete = [];
450		$triggerids = [];
451
452		foreach ($selements as &$selement) {
453			$db_selement = $db_selements[$selement['selementid']];
454
455			// Change type from something to trigger.
456			if ($selement['elementtype'] != $db_selement['elementtype']
457					&& $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
458				$selement['elementid'] = 0;
459
460				foreach ($selement['elements'] as $element) {
461					$triggerids[$element['triggerid']] = true;
462				}
463			}
464
465			// Change type from trigger to something.
466			if ($selement['elementtype'] != $db_selement['elementtype']
467					&& $db_selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
468				foreach ($db_selement['elements'] as $db_element) {
469					$triggers_to_delete[] = $db_element['selement_triggerid'];
470				}
471			}
472
473			if ($selement['elementtype'] != SYSMAP_ELEMENT_TYPE_IMAGE
474					&& $selement['elementtype'] != SYSMAP_ELEMENT_TYPE_TRIGGER) {
475				$selement['elementid'] = reset($selement['elements'][0]);
476			}
477
478			$db_elements = $db_selement['elements'];
479
480			foreach ($db_selement['elements'] as &$element) {
481				unset($element['selement_triggerid']);
482			}
483			unset($element);
484
485			if ($selement['elementtype'] == $db_selement['elementtype']
486					&& $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
487				foreach ($db_elements as $element) {
488					$triggers_to_delete[] = $element['selement_triggerid'];
489				}
490
491				foreach ($selement['elements'] as $element) {
492					$triggerids[$element['triggerid']] = true;
493				}
494			}
495
496			$update[] = [
497				'values' => $selement,
498				'where' => ['selementid' => $selement['selementid']]
499			];
500			$selementIds[] = $selement['selementid'];
501
502			if (!isset($selement['urls'])) {
503				continue;
504			}
505
506			$diffUrls = zbx_array_diff($selement['urls'], $db_selement['urls'], 'name');
507
508			// add
509			foreach ($diffUrls['first'] as $newUrl) {
510				$newUrl['selementid'] = $selement['selementid'];
511				$urlsToAdd[] = $newUrl;
512			}
513
514			// update url
515			foreach ($diffUrls['both'] as $updUrl) {
516				$urlsToUpdate[] = [
517					'values' => $updUrl,
518					'where' => [
519						'selementid' => $selement['selementid'],
520						'name' => $updUrl['name']
521					]
522				];
523			}
524
525			// delete url
526			$urlsToDelete = array_merge($urlsToDelete, zbx_objectValues($diffUrls['second'], 'sysmapelementurlid'));
527		}
528		unset($selement);
529
530		$this->updateElementsTags($selements);
531
532		$db_triggers = API::Trigger()->get([
533			'output' => ['triggerid', 'priority'],
534			'triggerids' => array_keys($triggerids),
535			'preservekeys' => true
536		]);
537
538		$triggers = [];
539
540		foreach ($selements as $key => $selement) {
541			if ($selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
542				$selementid = $selement['selementid'];
543
544				foreach ($selement['elements'] as $element) {
545					$priority = $db_triggers[$element['triggerid']]['priority'];
546					$triggers[$selementid][$priority][] = [
547						'selementid' => $selementid,
548						'triggerid' => $element['triggerid']
549					];
550				}
551				krsort($triggers[$selementid]);
552			}
553		}
554
555		$triggers_to_add = [];
556
557		foreach ($triggers as $selement_triggers) {
558			foreach ($selement_triggers as $selement_trigger_priorities) {
559				foreach ($selement_trigger_priorities as $selement_trigger_priority) {
560					$triggers_to_add[] = $selement_trigger_priority;
561				}
562			}
563		}
564
565		DB::update('sysmaps_elements', $update);
566
567		if (!empty($urlsToDelete)) {
568			DB::delete('sysmap_element_url', ['sysmapelementurlid' => $urlsToDelete]);
569		}
570
571		if (!empty($urlsToUpdate)) {
572			DB::update('sysmap_element_url', $urlsToUpdate);
573		}
574
575		if (!empty($urlsToAdd)) {
576			DB::insert('sysmap_element_url', $urlsToAdd);
577		}
578
579		if ($triggers_to_delete) {
580			DB::delete('sysmap_element_trigger', ['selement_triggerid' => $triggers_to_delete]);
581		}
582
583		if ($triggers_to_add) {
584			DB::insert('sysmap_element_trigger', $triggers_to_add);
585		}
586
587		return ['selementids' => $selementIds];
588	}
589
590	/**
591	 * Delete element from map.
592	 *
593	 * @param array $selements							multidimensional array with selement objects
594	 * @param array $selements[0, ...]['selementid']	selementid to delete
595	 */
596	protected function deleteSelements(array $selements) {
597		$selements = zbx_toArray($selements);
598		$selementIds = zbx_objectValues($selements, 'selementid');
599
600		DB::delete('sysmaps_elements', ['selementid' => $selementIds]);
601
602		return $selementIds;
603	}
604
605	/**
606	 * Add shape to sysmap.
607	 *
608	 * @param array $shapes							Multidimensional array with shape properties.
609	 */
610	protected function createShapes(array $shapes) {
611		$shapes = zbx_toArray($shapes);
612
613		$this->checkShapeInput($shapes);
614
615		DB::insert('sysmap_shape', $shapes);
616	}
617
618	/**
619	 * Update shapes to sysmap.
620	 *
621	 * @param array $shapes							Multidimensional array with shape properties.
622	 */
623	protected function updateShapes(array $shapes) {
624		$shapes = zbx_toArray($shapes);
625
626		$this->checkShapeInput($shapes);
627
628		$update = [];
629		foreach ($shapes as $shape) {
630			$shapeid = $shape['sysmap_shapeid'];
631			unset($shape['sysmap_shapeid']);
632
633			if ($shape) {
634				$update[] = [
635					'values' => $shape,
636					'where' => ['sysmap_shapeid' => $shapeid]
637				];
638			}
639		}
640
641		DB::update('sysmap_shape', $update);
642	}
643
644	/**
645	 * Delete shapes from map.
646	 *
647	 * @param array $shapes							Multidimensional array with shape properties.
648	 */
649	protected function deleteShapes(array $shapes) {
650		$shapes = zbx_toArray($shapes);
651		$shapeids = zbx_objectValues($shapes, 'sysmap_shapeid');
652
653		DB::delete('sysmap_shape', ['sysmap_shapeid' => $shapeids]);
654	}
655
656	/**
657	 * Create link.
658	 *
659	 * @param array $links
660	 * @param array $links[0,...]['sysmapid']
661	 * @param array $links[0,...]['selementid1']
662	 * @param array $links[0,...]['selementid2']
663	 * @param array $links[0,...]['drawtype']
664	 * @param array $links[0,...]['color']
665	 *
666	 * @return array
667	 */
668	protected function createLinks(array $links) {
669		$links = zbx_toArray($links);
670
671		$this->checkLinkInput($links, __FUNCTION__);
672
673		$linkIds = DB::insert('sysmaps_links', $links);
674
675		return ['linkids' => $linkIds];
676	}
677
678	protected function updateLinks($links) {
679		$links = zbx_toArray($links);
680
681		$this->checkLinkInput($links, __FUNCTION__);
682
683		$udpateLinks = [];
684		foreach ($links as $link) {
685			$udpateLinks[] = ['values' => $link, 'where' => ['linkid' => $link['linkid']]];
686		}
687
688		DB::update('sysmaps_links', $udpateLinks);
689
690		return ['linkids' => zbx_objectValues($links, 'linkid')];
691	}
692
693	/**
694	 * Delete Link from map.
695	 *
696	 * @param array $links						multidimensional array with link objects
697	 * @param array $links[0, ...]['linkid']	link ID to delete
698	 *
699	 * @return array
700	 */
701	protected function deleteLinks($links) {
702		zbx_value2array($links);
703		$linkIds = zbx_objectValues($links, 'linkid');
704
705		$this->checkLinkInput($links, __FUNCTION__);
706
707		DB::delete('sysmaps_links', ['linkid' => $linkIds]);
708
709		return ['linkids' => $linkIds];
710	}
711
712	/**
713	 * Add link trigger to link (sysmap).
714	 *
715	 * @param array $links[0,...]['linkid']
716	 * @param array $links[0,...]['triggerid']
717	 * @param array $links[0,...]['drawtype']
718	 * @param array $links[0,...]['color']
719	 */
720	protected function createLinkTriggers($linkTriggers) {
721		$linkTriggers = zbx_toArray($linkTriggers);
722
723		$this->validateCreateLinkTriggers($linkTriggers);
724
725		$linkTriggerIds = DB::insert('sysmaps_link_triggers', $linkTriggers);
726
727		return ['linktriggerids' => $linkTriggerIds];
728	}
729
730	protected function validateCreateLinkTriggers(array $linkTriggers) {
731		$linkTriggerDbFields = [
732			'linkid' => null,
733			'triggerid' => null
734		];
735
736		$colorValidator = new CColorValidator();
737
738		foreach ($linkTriggers as $linkTrigger) {
739			if (!check_db_fields($linkTriggerDbFields, $linkTrigger)) {
740				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
741			}
742
743			if (isset($linkTrigger['color']) && !$colorValidator->validate($linkTrigger['color'])) {
744				self::exception(ZBX_API_ERROR_PARAMETERS, $colorValidator->getError());
745			}
746		}
747	}
748
749	protected function updateLinkTriggers($linkTriggers) {
750		$linkTriggers = zbx_toArray($linkTriggers);
751		$this->validateUpdateLinkTriggers($linkTriggers);
752
753		$linkTriggerIds = zbx_objectValues($linkTriggers, 'linktriggerid');
754
755		$updateLinkTriggers = [];
756		foreach ($linkTriggers as $linkTrigger) {
757			$updateLinkTriggers[] = [
758				'values' => $linkTrigger,
759				'where' => ['linktriggerid' => $linkTrigger['linktriggerid']]
760			];
761		}
762
763		DB::update('sysmaps_link_triggers', $updateLinkTriggers);
764
765		return ['linktriggerids' => $linkTriggerIds];
766	}
767
768	protected function validateUpdateLinkTriggers(array $linkTriggers) {
769		$linkTriggerDbFields = ['linktriggerid' => null];
770
771		$colorValidator = new CColorValidator();
772
773		foreach ($linkTriggers as $linkTrigger) {
774			if (!check_db_fields($linkTriggerDbFields, $linkTrigger)) {
775				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
776			}
777
778			if (isset($linkTrigger['color']) && !$colorValidator->validate($linkTrigger['color'])) {
779				self::exception(ZBX_API_ERROR_PARAMETERS, $colorValidator->getError());
780			}
781		}
782	}
783
784	protected function deleteLinkTriggers($linkTriggers) {
785		$linkTriggers = zbx_toArray($linkTriggers);
786		$this->validateDeleteLinkTriggers($linkTriggers);
787
788		$linkTriggerIds = zbx_objectValues($linkTriggers, 'linktriggerid');
789
790		DB::delete('sysmaps_link_triggers', ['linktriggerid' => $linkTriggerIds]);
791
792		return ['linktriggerids' => $linkTriggerIds];
793	}
794
795	protected function validateDeleteLinkTriggers(array $linkTriggers) {
796		$linktriggerDbFields = ['linktriggerid' => null];
797
798		foreach ($linkTriggers as $linkTrigger) {
799			if (!check_db_fields($linktriggerDbFields, $linkTrigger)) {
800				self::exception(ZBX_API_ERROR_PARAMETERS, _('Incorrect arguments passed to function.'));
801			}
802		}
803	}
804
805	/**
806	 * Create map element tags.
807	 *
808	 * @param array  $selements
809	 * @param int    $selements[]['elementtype']
810	 * @param array  $selements[]['tags']
811	 * @param string $selements[]['tags'][]['tag']
812	 * @param string $selements[]['tags'][]['value']
813	 * @param string $selements[]['tags'][]['operator']
814	 * @param array  $selementids
815	 */
816	protected function createSelementsTags(array $selements, array $selementids): void {
817		$new_tags = [];
818		foreach ($selements as $index => $selement) {
819			if (!array_key_exists('tags', $selement)
820					|| ($selement['elementtype'] != SYSMAP_ELEMENT_TYPE_HOST
821						&& $selement['elementtype'] != SYSMAP_ELEMENT_TYPE_HOST_GROUP)) {
822				continue;
823			}
824
825			foreach ($selement['tags'] as $tag_add) {
826				$new_tags[] = ['selementid' => $selementids[$index]] + $tag_add;
827			}
828		}
829
830		if ($new_tags) {
831			DB::insert('sysmaps_element_tag', $new_tags);
832		}
833	}
834
835	/**
836	 * Update map element tags.
837	 *
838	 * @param array  $selements
839	 * @param string $selements[]['selementid']
840	 * @param int    $selements[]['elementtype']
841	 * @param array  $selements[]['tags']
842	 * @param string $selements[]['tags'][]['tag']
843	 * @param string $selements[]['tags'][]['value']
844	 * @param string $selements[]['tags'][]['operator']
845	 */
846	protected function updateElementsTags(array $selements): void {
847		// Select tags from database.
848		$db_tags = DBselect(
849			'SELECT selementtagid, selementid, tag, value, operator'.
850			' FROM sysmaps_element_tag'.
851			' WHERE '.dbConditionInt('selementid', array_column($selements, 'selementid'))
852		);
853
854		array_walk($selements, function (&$selement) {
855			$selement['db_tags'] = [];
856		});
857
858		while ($db_tag = DBfetch($db_tags)) {
859			$selements[$db_tag['selementid']]['db_tags'][] = $db_tag;
860		}
861
862		// Find which tags must be added/deleted.
863		$new_tags = [];
864		$del_tagids = [];
865		foreach ($selements as $selement) {
866			if ($selement['elementtype'] != SYSMAP_ELEMENT_TYPE_HOST
867					&& $selement['elementtype'] != SYSMAP_ELEMENT_TYPE_HOST_GROUP) {
868				$del_tagids = array_merge($del_tagids, array_column($selement['db_tags'], 'selementtagid'));
869				continue;
870			}
871
872			foreach ($selement['db_tags'] as $del_tag_key => $tag_delete) {
873				foreach ($selement['tags'] as $new_tag_key => $tag_add) {
874					if ($tag_delete['tag'] === $tag_add['tag'] && $tag_delete['value'] === $tag_add['value']
875							&& $tag_delete['operator'] === $tag_add['operator']) {
876						unset($selement['db_tags'][$del_tag_key], $selement['tags'][$new_tag_key]);
877						continue 2;
878					}
879				}
880			}
881
882			$del_tagids = array_merge($del_tagids, array_column($selement['db_tags'], 'selementtagid'));
883
884			foreach ($selement['tags'] as $tag_add) {
885				$tag_add['selementid'] = $selement['selementid'];
886				$new_tags[] = $tag_add;
887			}
888		}
889
890		if ($del_tagids) {
891			DB::delete('sysmaps_element_tag', ['selementtagid' => $del_tagids]);
892		}
893		if ($new_tags) {
894			DB::insert('sysmaps_element_tag', $new_tags);
895		}
896	}
897}
898