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 for importing configuration data.
24 */
25class CConfigurationImport {
26
27	/**
28	 * @var CImportDataAdapter
29	 */
30	protected $adapter;
31
32	/**
33	 * @var CImportReferencer
34	 */
35	protected $referencer;
36
37	/**
38	 * @var CImportedObjectContainer
39	 */
40	protected $importedObjectContainer;
41
42	/**
43	 * @var array
44	 */
45	protected $options;
46
47	/**
48	 * @var array with data read from source string
49	 */
50	protected $data;
51
52	/**
53	 * @var array  cached data from the adapter
54	 */
55	protected $formattedData = [];
56
57	/**
58	 * Constructor.
59	 * Source string must be suitable for reader class,
60	 * i.e. if string contains json then reader should be able to read json.
61	 *
62	 * @param array						$options					import options "createMissing", "updateExisting" and "deleteMissing"
63	 * @param CImportReferencer			$referencer					class containing all importable objects
64	 * @param CImportedObjectContainer	$importedObjectContainer	class containing processed host and template IDs
65	 */
66	public function __construct(array $options, CImportReferencer $referencer,
67			CImportedObjectContainer $importedObjectContainer) {
68		$default_options = [
69			'groups' => ['updateExisting' => false, 'createMissing' => false],
70			'hosts' => ['updateExisting' => false, 'createMissing' => false],
71			'templates' => ['updateExisting' => false, 'createMissing' => false],
72			'templateDashboards' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
73			'templateLinkage' => ['createMissing' => false, 'deleteMissing' => false],
74			'items' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
75			'discoveryRules' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
76			'triggers' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
77			'graphs' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
78			'httptests' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
79			'maps' => ['updateExisting' => false, 'createMissing' => false],
80			'images' => ['updateExisting' => false, 'createMissing' => false],
81			'mediaTypes' => ['updateExisting' => false, 'createMissing' => false],
82			'valueMaps' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false]
83		];
84
85		foreach ($default_options as $entity => $rules) {
86			$options[$entity] = array_key_exists($entity, $options)
87				? array_merge($rules, $options[$entity])
88				: $rules;
89		}
90
91		$object_options = (
92			$options['templateLinkage']['createMissing']
93			|| $options['templateLinkage']['deleteMissing']
94			|| $options['items']['updateExisting']
95			|| $options['items']['createMissing']
96			|| $options['items']['deleteMissing']
97			|| $options['discoveryRules']['updateExisting']
98			|| $options['discoveryRules']['createMissing']
99			|| $options['discoveryRules']['deleteMissing']
100			|| $options['triggers']['deleteMissing']
101			|| $options['graphs']['deleteMissing']
102			|| $options['httptests']['updateExisting']
103			|| $options['httptests']['createMissing']
104			|| $options['httptests']['deleteMissing']
105		);
106
107		$options['process_templates'] = (
108			!$options['templates']['updateExisting']
109			&& ($object_options
110				|| $options['templateDashboards']['updateExisting']
111				|| $options['templateDashboards']['createMissing']
112				|| $options['templateDashboards']['deleteMissing']
113			)
114		);
115		$options['process_hosts'] = (!$options['hosts']['updateExisting'] && $object_options);
116
117		$this->options = $options;
118		$this->referencer = $referencer;
119		$this->importedObjectContainer = $importedObjectContainer;
120	}
121
122	/**
123	 * Import configuration data.
124	 *
125	 * @param CImportDataAdapter $adapter an object to provide access to the imported data
126	 *
127	 * @return bool
128	 *
129	 * @throws Exception
130	 */
131	public function import(CImportDataAdapter $adapter): bool {
132		$this->adapter = $adapter;
133
134		// Parse all import for references to resolve them all together with less sql count.
135		$this->gatherReferences();
136
137		$this->processGroups();
138		$this->processTemplates();
139		$this->processHosts();
140
141		// Delete missing objects from processed hosts and templates.
142		$this->deleteMissingHttpTests();
143		$this->deleteMissingTemplateDashboards();
144		$this->deleteMissingDiscoveryRules();
145		$this->deleteMissingTriggers();
146		$this->deleteMissingGraphs();
147		$this->deleteMissingItems();
148
149		// Import objects.
150		$this->processHttpTests();
151		$this->processItems();
152		$this->processTriggers();
153		$this->processDiscoveryRules();
154		$this->processGraphs();
155		$this->processImages();
156		$this->processMaps();
157		$this->processTemplateDashboards();
158		$this->processMediaTypes();
159
160		return true;
161	}
162
163	/**
164	 * Parse all import data and collect references to objects.
165	 * For host objects it collects host names, for items - host name and item key, etc.
166	 * Collected references are added and resolved via the $this->referencer object.
167	 *
168	 * @see CImportReferencer
169	 */
170	protected function gatherReferences(): void {
171		$groups_refs = [];
172		$templates_refs = [];
173		$hosts_refs = [];
174		$items_refs = [];
175		$valuemaps_refs = [];
176		$triggers_refs = [];
177		$graphs_refs = [];
178		$iconmaps_refs = [];
179		$images_refs = [];
180		$maps_refs = [];
181		$template_dashboards_refs = [];
182		$template_macros_refs = [];
183		$host_macros_refs = [];
184		$host_prototype_macros_refs = [];
185		$proxy_refs = [];
186		$host_prototypes_refs = [];
187		$httptests_refs = [];
188		$httpsteps_refs = [];
189
190		foreach ($this->getFormattedGroups() as $group) {
191			$groups_refs[$group['name']] = ['uuid' => $group['uuid']];
192		}
193
194		foreach ($this->getFormattedTemplates() as $template) {
195			$templates_refs[$template['host']] = ['uuid' => $template['uuid']];
196
197			foreach ($template['groups'] as $group) {
198				$groups_refs += [$group['name'] => []];
199			}
200
201			if (array_key_exists('macros', $template)) {
202				foreach ($template['macros'] as $macro) {
203					$template_macros_refs[$template['uuid']][] = $macro['macro'];
204				}
205			}
206
207			if ($template['templates']) {
208				foreach ($template['templates'] as $linked_template) {
209					$templates_refs += [$linked_template['name'] => []];
210				}
211			}
212		}
213
214		foreach ($this->getFormattedHosts() as $host) {
215			$hosts_refs[$host['host']] = [];
216
217			foreach ($host['groups'] as $group) {
218				$groups_refs += [$group['name'] => []];
219			}
220
221			if (array_key_exists('macros', $host)) {
222				foreach ($host['macros'] as $macro) {
223					$host_macros_refs[$host['host']][] = $macro['macro'];
224				}
225			}
226
227			if ($host['templates']) {
228				foreach ($host['templates'] as $linked_template) {
229					$templates_refs += [$linked_template['name'] => []];
230				}
231			}
232
233			if ($host['proxy']) {
234				$proxy_refs[$host['proxy']['name']] = [];
235			}
236		}
237
238		foreach ($this->getFormattedItems() as $host => $items) {
239			foreach ($items as $item) {
240				$items_refs[$host][$item['key_']] = array_key_exists('uuid', $item)
241					? ['uuid' => $item['uuid']]
242					: [];
243
244				if ($item['valuemap']) {
245					$valuemaps_refs[$host][$item['valuemap']['name']] = [];
246				}
247			}
248		}
249
250		foreach ($this->getFormattedDiscoveryRules() as $host => $discovery_rules) {
251			foreach ($discovery_rules as $discovery_rule) {
252				$items_refs[$host][$discovery_rule['key_']] = array_key_exists('uuid', $discovery_rule)
253					? ['uuid' => $discovery_rule['uuid']]
254					: [];
255
256				foreach ($discovery_rule['item_prototypes'] as $item_prototype) {
257					$items_refs[$host][$item_prototype['key_']] = array_key_exists('uuid', $item_prototype)
258						? ['uuid' => $item_prototype['uuid']]
259						: [];
260
261					if (!empty($item_prototype['valuemap'])) {
262						$valuemaps_refs[$host][$item_prototype['valuemap']['name']] = [];
263					}
264				}
265
266				foreach ($discovery_rule['trigger_prototypes'] as $trigger) {
267					$description = $trigger['description'];
268					$expression = $trigger['expression'];
269					$recovery_expression = $trigger['recovery_expression'];
270
271					$triggers_refs[$description][$expression][$recovery_expression] = array_key_exists('uuid', $trigger)
272						? ['uuid' => $trigger['uuid']]
273						: [];
274
275					if (array_key_exists('dependencies', $trigger)) {
276						foreach ($trigger['dependencies'] as $dependency) {
277							$name = $dependency['name'];
278							$expression = $dependency['expression'];
279							$recovery_expression = $dependency['recovery_expression'];
280
281							if (!array_key_exists($name, $triggers_refs)
282									|| !array_key_exists($expression, $triggers_refs[$name])
283									|| !array_key_exists($recovery_expression, $triggers_refs[$name][$expression])) {
284								$triggers_refs[$name][$expression] = [];
285							}
286						}
287					}
288				}
289
290				foreach ($discovery_rule['graph_prototypes'] as $graph) {
291					if ($graph['ymin_item_1']) {
292						$item_host = $graph['ymin_item_1']['host'];
293						$item_key = $graph['ymin_item_1']['key'];
294
295						if (!array_key_exists($item_host, $items_refs)
296								|| !array_key_exists($item_key, $items_refs[$item_host])) {
297							$items_refs[$item_host][$item_key] = [];
298						}
299					}
300
301					if ($graph['ymax_item_1']) {
302						$item_host = $graph['ymax_item_1']['host'];
303						$item_key = $graph['ymax_item_1']['key'];
304
305						if (!array_key_exists($item_host, $items_refs)
306								|| !array_key_exists($item_key, $items_refs[$item_host])) {
307							$items_refs[$item_host][$item_key] = [];
308						}
309					}
310
311					foreach ($graph['gitems'] as $gitem) {
312						$item_host = $gitem['item']['host'];
313						$item_key = $gitem['item']['key'];
314
315						if (!array_key_exists($item_host, $templates_refs)) {
316							$hosts_refs[$item_host] = [];
317						}
318
319						if (!array_key_exists($item_host, $items_refs)
320								|| !array_key_exists($item_key, $items_refs[$item_host])) {
321							$items_refs[$item_host][$item_key] = [];
322						}
323
324						$graphs_refs[$item_host][$graph['name']] = array_key_exists('uuid', $graph)
325							? ['uuid' => $graph['uuid']]
326							: [];
327					}
328				}
329
330				foreach ($discovery_rule['host_prototypes'] as $host_prototype) {
331					if (array_key_exists('uuid', $host_prototype)) {
332						$host_prototypes_refs['uuid'][$host][$discovery_rule['uuid']][] = $host_prototype['uuid'];
333					}
334					else {
335						$host_prototypes_refs['host'][$host][$discovery_rule['key_']][] = $host_prototype['host'];
336					}
337
338					foreach ($host_prototype['group_prototypes'] as $group_prototype) {
339						if (isset($group_prototype['group'])) {
340							$groups_refs += [$group_prototype['group']['name'] => []];
341						}
342					}
343
344					if (array_key_exists('macros', $host_prototype)) {
345						foreach ($host_prototype['macros'] as $macro) {
346							if (array_key_exists('uuid', $host_prototype)) {
347								$host_prototype_macros_refs['uuid'][$host][$discovery_rule['key_']]
348									[$host_prototype['uuid']][] = $macro['macro'];
349							}
350							else {
351								$host_prototype_macros_refs['host'][$host][$discovery_rule['key_']]
352									[$host_prototype['host']][] = $macro['macro'];
353							}
354						}
355					}
356
357					foreach ($host_prototype['templates'] as $template) {
358						$templates_refs += [$template['name'] => []];
359					}
360				}
361
362				if ($discovery_rule['overrides']) {
363					foreach ($discovery_rule['overrides'] as $override) {
364						foreach ($override['operations'] as $operation) {
365							if ($operation['operationobject'] == OPERATION_OBJECT_HOST_PROTOTYPE
366									&& array_key_exists('optemplate', $operation)) {
367								foreach ($operation['optemplate'] as $template) {
368									$templates_refs += [$template['name'] => []];
369								}
370							}
371						}
372					}
373				}
374			}
375		}
376
377		foreach ($this->getFormattedGraphs() as $graph) {
378			if ($graph['ymin_item_1']) {
379				$item_host = $graph['ymin_item_1']['host'];
380				$item_key = $graph['ymin_item_1']['key'];
381
382				if (!array_key_exists($item_host, $templates_refs)) {
383					$hosts_refs[$item_host] = [];
384				}
385
386				if (!array_key_exists($item_host, $items_refs)
387						|| !array_key_exists($item_key, $items_refs[$item_host])) {
388					$items_refs[$item_host][$item_key] = [];
389				}
390			}
391
392			if ($graph['ymax_item_1']) {
393				$item_host = $graph['ymax_item_1']['host'];
394				$item_key = $graph['ymax_item_1']['key'];
395
396				if (!array_key_exists($item_host, $templates_refs)) {
397					$hosts_refs[$item_host] = [];
398				}
399
400				if (!array_key_exists($item_host, $items_refs)
401						|| !array_key_exists($item_key, $items_refs[$item_host])) {
402					$items_refs[$item_host][$item_key] = [];
403				}
404			}
405
406			if (array_key_exists('gitems', $graph) && $graph['gitems']) {
407				foreach ($graph['gitems'] as $gitem) {
408					$item_host = $gitem['item']['host'];
409					$item_key = $gitem['item']['key'];
410
411					if (!array_key_exists($item_host, $templates_refs)) {
412						$hosts_refs[$item_host] = [];
413					}
414
415					if (!array_key_exists($item_host, $items_refs)
416							|| !array_key_exists($item_key, $items_refs[$item_host])) {
417						$items_refs[$item_host][$item_key] = [];
418					}
419
420					$graphs_refs[$gitem['item']['host']][$graph['name']] = array_key_exists('uuid', $graph)
421						? ['uuid' => $graph['uuid']]
422						: [];
423				}
424			}
425		}
426
427		foreach ($this->getFormattedTriggers() as $trigger) {
428			$triggers_refs[$trigger['description']][$trigger['expression']][$trigger['recovery_expression']] =
429				array_key_exists('uuid', $trigger)
430					? ['uuid' => $trigger['uuid']]
431					: [];
432
433			if (array_key_exists('dependencies', $trigger)) {
434				foreach ($trigger['dependencies'] as $dependency) {
435					$name = $dependency['name'];
436					$expression = $dependency['expression'];
437					$recovery_expression = $dependency['recovery_expression'];
438
439					if (!array_key_exists($name, $triggers_refs)
440							|| !array_key_exists($expression, $triggers_refs[$name])
441							|| !array_key_exists($recovery_expression, $triggers_refs[$name][$expression])) {
442						$triggers_refs[$name][$expression][$recovery_expression] = [];
443					}
444				}
445			}
446		}
447
448		foreach ($this->getFormattedMaps() as $map) {
449			$maps_refs[$map['name']] = [];
450
451			if ($map['iconmap'] && array_key_exists('name', $map['iconmap']) && $map['iconmap']['name'] !== '') {
452				$iconmaps_refs[$map['iconmap']['name']] = [];
453			}
454
455			if ($map['background'] && array_key_exists('name', $map['background'])
456					&& $map['background']['name'] !== '') {
457				$images_refs[$map['background']['name']] = [];
458			}
459
460			if (array_key_exists('selements', $map)) {
461				foreach ($map['selements'] as $selement) {
462					switch ($selement['elementtype']) {
463						case SYSMAP_ELEMENT_TYPE_MAP:
464							$maps_refs[$selement['elements'][0]['name']] = [];
465							break;
466
467						case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
468							$groups_refs += [$selement['elements'][0]['name'] => []];
469							break;
470
471						case SYSMAP_ELEMENT_TYPE_HOST:
472							$hosts_refs += [$selement['elements'][0]['host'] => []];
473							break;
474
475						case SYSMAP_ELEMENT_TYPE_TRIGGER:
476							foreach ($selement['elements'] as $element) {
477								$description = $element['description'];
478								$expression = $element['expression'];
479								$recovery_expression = $element['recovery_expression'];
480
481								if (!array_key_exists($description, $triggers_refs)
482										|| !array_key_exists($expression, $triggers_refs[$description])
483										|| !array_key_exists($recovery_expression,
484											$triggers_refs[$description][$expression])) {
485									$triggers_refs[$description][$expression][$recovery_expression] = [];
486								}
487							}
488							break;
489					}
490				}
491			}
492
493			if (array_key_exists('links', $map)) {
494				foreach ($map['links'] as $link) {
495					if (array_key_exists('linktriggers', $link)) {
496						foreach ($link['linktriggers'] as $link_trigger) {
497							$description = $link_trigger['trigger']['description'];
498							$expression = $link_trigger['trigger']['expression'];
499							$recovery_expression = $link_trigger['trigger']['recovery_expression'];
500
501							if (!array_key_exists($description, $triggers_refs)
502									|| !array_key_exists($expression, $triggers_refs[$description])
503									|| !array_key_exists($recovery_expression,
504										$triggers_refs[$description][$expression])) {
505								$triggers_refs[$description][$expression][$recovery_expression] = [];
506							}
507
508						}
509					}
510				}
511			}
512		}
513
514		foreach ($this->getFormattedTemplateDashboards() as $host => $dashboards) {
515			foreach ($dashboards as $dashboard) {
516				$template_dashboards_refs[$dashboard['uuid']] = [];
517
518				if (!$dashboard['pages']) {
519					continue;
520				}
521
522				foreach ($dashboard['pages'] as $dashboard_page) {
523					if (!$dashboard_page['widgets']) {
524						continue;
525					}
526
527					foreach ($dashboard_page['widgets'] as $widget) {
528						foreach ($widget['fields'] as $field) {
529							$value = $field['value'];
530
531							switch ($field['type']) {
532								case ZBX_WIDGET_FIELD_TYPE_ITEM:
533								case ZBX_WIDGET_FIELD_TYPE_ITEM_PROTOTYPE:
534									$templates_refs += [$value['host'] => []];
535
536									if (!array_key_exists($value['host'], $items_refs)
537											|| !array_key_exists($value['key'], $items_refs[$value['host']])) {
538										$items_refs[$value['host']][$value['key']] = [];
539									}
540									break;
541
542								case ZBX_WIDGET_FIELD_TYPE_GRAPH:
543								case ZBX_WIDGET_FIELD_TYPE_GRAPH_PROTOTYPE:
544									$templates_refs += [$value['host'] => []];
545
546									if (!array_key_exists($value['host'], $graphs_refs)
547											|| !array_key_exists($value['name'], $graphs_refs[$value['host']])) {
548										$graphs_refs[$value['host']][$value['name']] = [];
549									}
550									break;
551							}
552						}
553					}
554				}
555			}
556		}
557
558		foreach ($this->getFormattedHttpTests() as $host => $httptests) {
559			foreach ($httptests as $httptest) {
560				$httptests_refs[$host][$httptest['name']] = array_key_exists('uuid', $httptest)
561					? ['uuid' => $httptest['uuid']]
562					: [];
563			}
564		}
565
566		foreach ($this->getFormattedHttpSteps() as $host => $httptests) {
567			foreach ($httptests as $httptest_name => $httpsteps) {
568				foreach ($httpsteps as $httpstep) {
569					$httpsteps_refs[$host][$httptest_name][$httpstep['name']] = [];
570				}
571			}
572		}
573
574		foreach ($this->getFormattedImages() as $image) {
575			$images_refs[$image['name']] = [];
576		}
577
578		$this->referencer->addGroups($groups_refs);
579		$this->referencer->addTemplates($templates_refs);
580		$this->referencer->addHosts($hosts_refs);
581		$this->referencer->addItems($items_refs);
582		$this->referencer->addValuemaps($valuemaps_refs);
583		$this->referencer->addTriggers($triggers_refs);
584		$this->referencer->addGraphs($graphs_refs);
585		$this->referencer->addIconmaps($iconmaps_refs);
586		$this->referencer->addImages($images_refs);
587		$this->referencer->addMaps($maps_refs);
588		$this->referencer->addTemplateDashboards($template_dashboards_refs);
589		$this->referencer->addTemplateMacros($template_macros_refs);
590		$this->referencer->addHostMacros($host_macros_refs);
591		$this->referencer->addHostPrototypeMacros($host_prototype_macros_refs);
592		$this->referencer->addProxies($proxy_refs);
593		$this->referencer->addHostPrototypes($host_prototypes_refs);
594		$this->referencer->addHttpTests($httptests_refs);
595		$this->referencer->addHttpSteps($httpsteps_refs);
596	}
597
598	/**
599	 * Import groups.
600	 */
601	protected function processGroups(): void {
602		if (!$this->options['groups']['createMissing'] && !$this->options['groups']['updateExisting']) {
603			return;
604		}
605
606		$groups_to_create = [];
607		$groups_to_update = [];
608
609		foreach ($this->getFormattedGroups() as $group) {
610			$groupid = $this->referencer->findGroupidByUuid($group['uuid']);
611
612			if ($groupid) {
613				$groups_to_update[] = $group + ['groupid' => $groupid];
614			}
615			else {
616				$groups_to_create[] = $group;
617			}
618		}
619
620		if ($this->options['groups']['updateExisting'] && $groups_to_update) {
621			API::HostGroup()->update(array_map(function($group) {
622				unset($group['uuid']);
623				return $group;
624			}, $groups_to_update));
625
626			foreach ($groups_to_update as $group) {
627				$this->referencer->setDbGroup($group['groupid'], $group);
628			}
629		}
630
631		if ($this->options['groups']['createMissing'] && $groups_to_create) {
632			$created_groups = API::HostGroup()->create($groups_to_create);
633
634			foreach ($created_groups['groupids'] as $index => $groupid) {
635				$this->referencer->setDbGroup($groupid, $groups_to_create[$index]);
636			}
637		}
638	}
639
640	/**
641	 * Import templates.
642	 *
643	 * @throws Exception
644	 */
645	protected function processTemplates(): void {
646		if ($this->options['templates']['updateExisting'] || $this->options['templates']['createMissing']
647				|| $this->options['process_templates']) {
648			$templates = $this->getFormattedTemplates();
649
650			if ($templates) {
651				$template_importer = new CTemplateImporter($this->options, $this->referencer,
652					$this->importedObjectContainer
653				);
654				$template_importer->import($templates);
655
656				// Get list of imported template IDs and add them processed template ID list.
657				$templateids = $template_importer->getProcessedTemplateids();
658				$this->importedObjectContainer->addTemplateIds($templateids);
659			}
660		}
661	}
662
663	/**
664	 * Import hosts.
665	 *
666	 * @throws Exception
667	 */
668	protected function processHosts(): void {
669		if ($this->options['hosts']['updateExisting'] || $this->options['hosts']['createMissing']
670				|| $this->options['process_hosts']) {
671			$hosts = $this->getFormattedHosts();
672
673			if ($hosts) {
674				$host_importer = new CHostImporter($this->options, $this->referencer, $this->importedObjectContainer);
675				$host_importer->import($hosts);
676
677				// Get list of imported host IDs and add them processed host ID list.
678				$hostids = $host_importer->getProcessedHostIds();
679				$this->importedObjectContainer->addHostIds($hostids);
680			}
681		}
682	}
683
684	/**
685	 * Import items.
686	 *
687	 * @throws Exception
688	 */
689	protected function processItems(): void {
690		if (!$this->options['items']['createMissing'] && !$this->options['items']['updateExisting']) {
691			return;
692		}
693
694		$master_item_key = 'master_item';
695		$order_tree = $this->getItemsOrder($master_item_key);
696
697		$items_to_create = [];
698		$items_to_update = [];
699
700		foreach ($this->getFormattedItems() as $host => $items) {
701			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
702
703			if ($hostid === null
704					|| (!$this->importedObjectContainer->isHostProcessed($hostid)
705						&& !$this->importedObjectContainer->isTemplateProcessed($hostid))) {
706				continue;
707			}
708
709			foreach ($order_tree[$host] as $item_key => $level) {
710				$item = $items[$item_key];
711				$item['hostid'] = $hostid;
712
713				if (array_key_exists('interface_ref', $item) && $item['interface_ref']) {
714					$interfaceid = $this->referencer->findInterfaceidByRef($hostid, $item['interface_ref']);
715
716					if ($interfaceid === null) {
717						throw new Exception(_s('Cannot find interface "%1$s" used for item "%2$s" on "%3$s".',
718							$item['interface_ref'], $item['name'], $host
719						));
720					}
721
722					$item['interfaceid'] = $interfaceid;
723				}
724
725				if (array_key_exists('valuemap', $item) && $item['valuemap']) {
726					$valuemapid = $this->referencer->findValuemapidByName($hostid, $item['valuemap']['name']);
727
728					if ($valuemapid === null) {
729						throw new Exception(_s(
730							'Cannot find value map "%1$s" used for item "%2$s" on "%3$s".',
731							$item['valuemap']['name'],
732							$item['name'],
733							$host
734						));
735					}
736
737					$item['valuemapid'] = $valuemapid;
738					unset($item['valuemap']);
739				}
740
741				if ($item['type'] == ITEM_TYPE_DEPENDENT) {
742					if (!array_key_exists('key', $item[$master_item_key])) {
743						throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
744							_('cannot be empty')
745						));
746					}
747
748					$master_itemid = $this->referencer->findItemidByKey($hostid, $item[$master_item_key]['key']);
749
750					if ($master_itemid !== null) {
751						$item['master_itemid'] = $master_itemid;
752						unset($item[$master_item_key]);
753					}
754				}
755				else {
756					unset($item[$master_item_key]);
757				}
758
759				if ($item['type'] == ITEM_TYPE_HTTPAGENT) {
760					$headers = [];
761
762					foreach ($item['headers'] as $header) {
763						$headers[$header['name']] = $header['value'];
764					}
765
766					$item['headers'] = $headers;
767
768					$query_fields = [];
769
770					foreach ($item['query_fields'] as $query_field) {
771						$query_fields[] = [$query_field['name'] => $query_field['value']];
772					}
773
774					$item['query_fields'] = $query_fields;
775				}
776
777				foreach ($item['preprocessing'] as &$preprocessing_step) {
778					$preprocessing_step['params'] = implode("\n", $preprocessing_step['parameters']);
779					unset($preprocessing_step['parameters']);
780				}
781				unset($preprocessing_step);
782
783				$itemid = array_key_exists('uuid', $item)
784					? $this->referencer->findItemidByUuid($item['uuid'])
785					: $this->referencer->findItemidByKey($hostid, $item['key_']);
786
787				if ($itemid !== null) {
788					$item['itemid'] = $itemid;
789
790					if (!array_key_exists($level, $items_to_update)) {
791						$items_to_update[$level] = [];
792					}
793
794					$items_to_update[$level][] = $item;
795				}
796				else {
797					if (!array_key_exists($level, $items_to_create)) {
798						$items_to_create[$level] = [];
799					}
800
801					$items_to_create[$level][] = $item;
802				}
803			}
804		}
805
806		if ($this->options['items']['updateExisting'] && $items_to_update) {
807			$this->updateItemsWithDependency($items_to_update, $master_item_key, API::Item());
808		}
809
810		if ($this->options['items']['createMissing'] && $items_to_create) {
811			$this->createItemsWithDependency($items_to_create, $master_item_key, API::Item());
812		}
813
814		// Refresh items because templated ones can be inherited to host and used in triggers, graphs, etc.
815		$this->referencer->refreshItems();
816	}
817
818	/**
819	 * Create CItem or CItemPrototype with dependency.
820	 *
821	 * @param array $items_by_level              Associative array of entities where key is entity dependency
822	 *                                             level and value is array of entities for this level.
823	 * @param string $master_item_key            Master entity array key in xml parsed data.
824	 * @param CItem|CItemPrototype $api_service  Entity service which is capable to proceed with entity create.
825	 *
826	 * @throws Exception if entity master entity can not be resolved.
827	 */
828	protected function createItemsWithDependency(array $items_by_level, string $master_item_key,
829			CItemGeneral $api_service): void {
830		foreach ($items_by_level as $items_to_create) {
831			foreach ($items_to_create as &$item) {
832				if (array_key_exists($master_item_key, $item)) {
833					$item['master_itemid'] = $this->referencer->findItemidByKey($item['hostid'],
834						$item[$master_item_key]['key']
835					);
836
837					if ($item['master_itemid'] === null) {
838						throw new Exception(_s('Incorrect value for field "%1$s": %2$s.',
839							'master_itemid', _s('value "%1$s" not found', $item[$master_item_key]['key'])
840						));
841					}
842					unset($item[$master_item_key]);
843				}
844			}
845			unset($item);
846
847			$created_items = $api_service->create($items_to_create);
848
849			foreach ($items_to_create as $index => $item) {
850				$this->referencer->setDbItem($created_items['itemids'][$index], $item);
851			}
852		}
853	}
854
855	/**
856	 * Update CItem or CItemPrototype with dependency.
857	 *
858	 * @param array $items_by_level              Associative array of entities where key is entity dependency
859	 *                                             level and value is array of entities for this level.
860	 * @param string $master_item_key            Master entity array key in xml parsed data.
861	 * @param CItem|CItemPrototype $api_service  Entity service which is capable to proceed with entity update.
862	 *
863	 * @throws Exception if entity master entity can not be resolved.
864	 */
865	protected function updateItemsWithDependency(array $items_by_level, string $master_item_key,
866			CItemGeneral $api_service): void {
867		foreach ($items_by_level as $items_to_update) {
868			foreach ($items_to_update as &$item) {
869				if (array_key_exists($master_item_key, $item)) {
870					$item['master_itemid'] = $this->referencer->findItemidByKey($item['hostid'],
871						$item[$master_item_key]['key']
872					);
873
874					if ($item['master_itemid'] === null) {
875						throw new Exception(_s('Incorrect value for field "%1$s": %2$s.',
876							'master_itemid', _s('value "%1$s" not found', $item[$master_item_key]['key'])
877						));
878					}
879					unset($item[$master_item_key]);
880				}
881			}
882			unset($item);
883
884			$updated_items = $api_service->update(array_map(function($item) {
885				unset($item['uuid']);
886				return $item;
887			}, $items_to_update));
888
889			foreach ($items_to_update as $index => $item) {
890				$this->referencer->setDbItem($updated_items['itemids'][$index], $item);
891			}
892		}
893	}
894
895	/**
896	 * Import discovery rules.
897	 *
898	 * @throws Exception
899	 */
900	protected function processDiscoveryRules(): void {
901		if (!$this->options['discoveryRules']['createMissing'] && !$this->options['discoveryRules']['updateExisting']) {
902			return;
903		}
904
905		$master_item_key = 'master_item';
906		$discovery_rules_by_hosts = $this->getFormattedDiscoveryRules();
907
908		if (!$discovery_rules_by_hosts) {
909			return;
910		}
911
912		// Unset rules that are related to hosts we did not process.
913		foreach ($discovery_rules_by_hosts as $host => $discovery_rules) {
914			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
915
916			if ($hostid === null
917					|| (!$this->importedObjectContainer->isHostProcessed($hostid)
918						&& !$this->importedObjectContainer->isTemplateProcessed($hostid))) {
919				unset($discovery_rules_by_hosts[$host]);
920			}
921		}
922
923		if ($this->options['discoveryRules']['updateExisting']) {
924			$this->deleteMissingPrototypes($discovery_rules_by_hosts);
925		}
926
927		$discovery_rules_to_create = [];
928		$discovery_rules_to_update = [];
929
930		foreach ($discovery_rules_by_hosts as $host => $discovery_rules) {
931			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
932
933			foreach ($discovery_rules as $discovery_rule) {
934				$discovery_rule['hostid'] = $hostid;
935
936				$itemid = array_key_exists('uuid', $discovery_rule)
937					? $this->referencer->findItemidByUuid($discovery_rule['uuid'])
938					: $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']);
939
940				unset($discovery_rule['item_prototypes'], $discovery_rule['trigger_prototypes'],
941					$discovery_rule['graph_prototypes'], $discovery_rule['host_prototypes']
942				);
943
944				if (array_key_exists('interface_ref', $discovery_rule) && $discovery_rule['interface_ref']) {
945					$interfaceid = $this->referencer->findInterfaceidByRef($hostid, $discovery_rule['interface_ref']);
946
947					if ($interfaceid === null) {
948						throw new Exception(_s('Cannot find interface "%1$s" used for discovery rule "%2$s" on "%3$s".',
949							$discovery_rule['interface_ref'], $discovery_rule['name'], $host
950						));
951					}
952
953					$discovery_rule['interfaceid'] = $interfaceid;
954				}
955
956				if ($discovery_rule['type'] == ITEM_TYPE_HTTPAGENT) {
957					$headers = [];
958
959					foreach ($discovery_rule['headers'] as $header) {
960						$headers[$header['name']] = $header['value'];
961					}
962
963					$discovery_rule['headers'] = $headers;
964
965					$query_fields = [];
966
967					foreach ($discovery_rule['query_fields'] as $query_field) {
968						$query_fields[] = [$query_field['name'] => $query_field['value']];
969					}
970
971					$discovery_rule['query_fields'] = $query_fields;
972				}
973
974				if ($discovery_rule['type'] == ITEM_TYPE_DEPENDENT) {
975					if (!array_key_exists('key', $discovery_rule[$master_item_key])) {
976						throw new Exception( _s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
977							_('cannot be empty')
978						));
979					}
980
981					$discovery_rule['master_itemid'] = $this->referencer->findItemidByKey($hostid,
982						$discovery_rule[$master_item_key]['key']
983					);
984				}
985
986				unset($discovery_rule[$master_item_key]);
987
988				if ($discovery_rule['overrides']) {
989					foreach ($discovery_rule['overrides'] as &$override) {
990						foreach ($override['operations'] as &$operation) {
991							if ($operation['operationobject'] == OPERATION_OBJECT_HOST_PROTOTYPE
992									&& array_key_exists('optemplate', $operation)) {
993								foreach ($operation['optemplate'] as &$template) {
994									$templateid = $this->referencer->findTemplateidByHost($template['name']);
995
996									if ($templateid === null) {
997										throw new Exception(_s(
998											'Cannot find template "%1$s" for override "%2$s" of discovery rule "%3$s" on "%4$s".',
999											$template['name'],
1000											$override['name'],
1001											$discovery_rule['name'],
1002											$host
1003										));
1004									}
1005
1006									$template['templateid'] = $templateid;
1007									unset($template['name']);
1008								}
1009								unset($template);
1010							}
1011						}
1012						unset($operation);
1013					}
1014					unset($override);
1015				}
1016
1017				foreach ($discovery_rule['preprocessing'] as &$preprocessing_step) {
1018					$preprocessing_step['params'] = implode("\n", $preprocessing_step['parameters']);
1019					unset($preprocessing_step['parameters']);
1020				}
1021				unset($preprocessing_step);
1022
1023				if ($itemid !== null) {
1024					$discovery_rule['itemid'] = $itemid;
1025					unset($discovery_rule['uuid']);
1026					$discovery_rules_to_update[] = $discovery_rule;
1027				}
1028				else {
1029					/*
1030					 * The array key "lld_macro_paths" must exist at this point. It is processed by chain conversion.
1031					 * Unlike discoveryrule.update method, discoveryrule.create does not allow "lld_macro_paths"
1032					 * to be empty.
1033					 */
1034					if (!$discovery_rule['lld_macro_paths']) {
1035						unset($discovery_rule['lld_macro_paths']);
1036					}
1037					$discovery_rules_to_create[] = $discovery_rule;
1038				}
1039			}
1040		}
1041
1042		$processed_discovery_rules = [];
1043
1044		if ($this->options['discoveryRules']['createMissing'] && $discovery_rules_to_create) {
1045			API::DiscoveryRule()->create($discovery_rules_to_create);
1046
1047			foreach ($discovery_rules_to_create as $discovery_rule) {
1048				$processed_discovery_rules[$discovery_rule['hostid']][$discovery_rule['key_']] = 1;
1049			}
1050		}
1051
1052		if ($this->options['discoveryRules']['updateExisting'] && $discovery_rules_to_update) {
1053			API::DiscoveryRule()->update($discovery_rules_to_update);
1054
1055			foreach ($discovery_rules_to_update as $discovery_rule) {
1056				$processed_discovery_rules[$discovery_rule['hostid']][$discovery_rule['key_']] = 1;
1057			}
1058		}
1059
1060		// Refresh discovery rules because templated ones can be inherited to host and used for prototypes.
1061		$this->referencer->refreshItems();
1062
1063		$order_tree = $this->getDiscoveryRulesItemsOrder($master_item_key);
1064
1065		// process prototypes
1066		$item_prototypes_to_update = [];
1067		$item_prototypes_to_create = [];
1068		$host_prototypes_to_update = [];
1069		$host_prototypes_to_create = [];
1070		$ex_group_prototypes_where = [];
1071
1072		foreach ($discovery_rules_by_hosts as $host => $discovery_rules) {
1073			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
1074
1075			foreach ($discovery_rules as $discovery_rule) {
1076				// if rule was not processed we should not create/update any of its prototypes
1077				if (!array_key_exists($discovery_rule['key_'], $processed_discovery_rules[$hostid])) {
1078					continue;
1079				}
1080
1081				$itemid = $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']);
1082
1083				// prototypes
1084				$item_prototypes = $discovery_rule['item_prototypes'] ? $order_tree[$host][$discovery_rule['key_']] : [];
1085
1086				foreach ($item_prototypes as $index => $level) {
1087					$item_prototype = $discovery_rule['item_prototypes'][$index];
1088					$item_prototype['hostid'] = $hostid;
1089
1090					if (array_key_exists('interface_ref', $item_prototype) && $item_prototype['interface_ref']) {
1091						$interfaceid = $this->referencer->findInterfaceidByRef($hostid,
1092							$item_prototype['interface_ref']
1093						);
1094
1095						if ($interfaceid !== null) {
1096							$item_prototype['interfaceid'] = $interfaceid;
1097						}
1098						else {
1099							throw new Exception(_s(
1100								'Cannot find interface "%1$s" used for item prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
1101								$item_prototype['interface_ref'],
1102								$item_prototype['name'],
1103								$discovery_rule['name'],
1104								$host
1105							));
1106						}
1107					}
1108
1109					if ($item_prototype['valuemap']) {
1110						$valuemapid = $this->referencer->findValuemapidByName($hostid,
1111							$item_prototype['valuemap']['name']
1112						);
1113
1114						if ($valuemapid === null) {
1115							throw new Exception(_s(
1116								'Cannot find value map "%1$s" used for item prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
1117								$item_prototype['valuemap']['name'],
1118								$item_prototype['name'],
1119								$discovery_rule['name'],
1120								$host
1121							));
1122						}
1123
1124						$item_prototype['valuemapid'] = $valuemapid;
1125						unset($item_prototype['valuemap']);
1126					}
1127
1128					if ($item_prototype['type'] == ITEM_TYPE_DEPENDENT) {
1129						if (!array_key_exists('key', $item_prototype[$master_item_key])) {
1130							throw new Exception( _s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
1131								_('cannot be empty')
1132							));
1133						}
1134
1135						$master_item_prototypeid = $this->referencer->findItemidByKey($hostid,
1136							$item_prototype[$master_item_key]['key']
1137						);
1138
1139						if ($master_item_prototypeid !== null) {
1140							$item_prototype['master_itemid'] = $master_item_prototypeid;
1141							unset($item_prototype[$master_item_key]);
1142						}
1143					}
1144					else {
1145						unset($item_prototype[$master_item_key]);
1146					}
1147
1148					if ($item_prototype['type'] == ITEM_TYPE_HTTPAGENT) {
1149						$headers = [];
1150
1151						foreach ($item_prototype['headers'] as $header) {
1152							$headers[$header['name']] = $header['value'];
1153						}
1154
1155						$item_prototype['headers'] = $headers;
1156
1157						$query_fields = [];
1158
1159						foreach ($item_prototype['query_fields'] as $query_field) {
1160							$query_fields[] = [$query_field['name'] => $query_field['value']];
1161						}
1162
1163						$item_prototype['query_fields'] = $query_fields;
1164					}
1165
1166					$item_prototypeid = array_key_exists('uuid', $item_prototype)
1167						? $this->referencer->findItemidByUuid($item_prototype['uuid'])
1168						: $this->referencer->findItemidByKey($hostid, $item_prototype['key_']);
1169
1170					$item_prototype['rule'] = [
1171						'hostid' => $hostid,
1172						'key' => $discovery_rule['key_']
1173					];
1174					$item_prototype['ruleid'] = $itemid;
1175
1176					foreach ($item_prototype['preprocessing'] as &$preprocessing_step) {
1177						$preprocessing_step['params'] = implode("\n", $preprocessing_step['parameters']);
1178						unset($preprocessing_step['parameters']);
1179					}
1180					unset($preprocessing_step);
1181
1182					if ($item_prototypeid !== null) {
1183						if (!array_key_exists($level, $item_prototypes_to_update)) {
1184							$item_prototypes_to_update[$level] = [];
1185						}
1186						$item_prototype['itemid'] = $item_prototypeid;
1187						$item_prototypes_to_update[$level][] = $item_prototype;
1188					}
1189					else {
1190						if (!array_key_exists($level, $item_prototypes_to_create)) {
1191							$item_prototypes_to_create[$level] = [];
1192						}
1193						$item_prototypes_to_create[$level][] = $item_prototype;
1194					}
1195				}
1196
1197				foreach ($discovery_rule['host_prototypes'] as $host_prototype) {
1198					// Resolve group prototypes.
1199					$group_links = [];
1200
1201					foreach ($host_prototype['group_links'] as $group_link) {
1202						$groupid = $this->referencer->findGroupidByName($group_link['group']['name']);
1203
1204						if ($groupid === null) {
1205							throw new Exception(_s(
1206								'Cannot find host group "%1$s" for host prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
1207								$group_link['group']['name'],
1208								$host_prototype['name'],
1209								$discovery_rule['name'],
1210								$host
1211							));
1212						}
1213
1214						$group_links[] = ['groupid' => $groupid];
1215					}
1216
1217					$host_prototype['groupLinks'] = $group_links;
1218					$host_prototype['groupPrototypes'] = $host_prototype['group_prototypes'];
1219					unset($host_prototype['group_links'], $host_prototype['group_prototypes']);
1220
1221					// Resolve templates.
1222					$templates = [];
1223
1224					foreach ($host_prototype['templates'] as $template) {
1225						$templateid = $this->referencer->findTemplateidByHost($template['name']);
1226
1227						if ($templateid === null) {
1228							throw new Exception(_s(
1229								'Cannot find template "%1$s" for host prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
1230								$template['name'],
1231								$host_prototype['name'],
1232								$discovery_rule['name'],
1233								$host
1234							));
1235						}
1236
1237						$templates[] = ['templateid' => $templateid];
1238					}
1239
1240					$host_prototype['templates'] = $templates;
1241
1242					$host_prototypeid = array_key_exists('uuid', $host_prototype)
1243						? $this->referencer->findHostPrototypeidByUuid($host_prototype['uuid'])
1244						: $this->referencer->findHostPrototypeidByHost($hostid, $itemid,
1245							$host_prototype['host']
1246						);
1247
1248					if ($host_prototypeid !== null) {
1249						if ($host_prototype['groupPrototypes']) {
1250							$ex_group_prototypes_where[] = '('.dbConditionInt('hostid', [$host_prototypeid]).
1251								' AND '.dbConditionString('name',
1252									array_column($host_prototype['groupPrototypes'], 'name')
1253								).
1254							')';
1255						}
1256
1257						if (array_key_exists('macros', $host_prototype)) {
1258							foreach ($host_prototype['macros'] as &$macro) {
1259								$hostmacroid = $this->referencer->findHostPrototypeMacroid($host_prototypeid,
1260									$macro['macro']
1261								);
1262
1263								if ($hostmacroid !== null) {
1264									$macro['hostmacroid'] = $hostmacroid;
1265								}
1266							}
1267							unset($macro);
1268						}
1269
1270						$host_prototype['hostid'] = $host_prototypeid;
1271						unset($host_prototype['uuid']);
1272						$host_prototypes_to_update[] = $host_prototype;
1273					}
1274					else {
1275						$host_prototype['ruleid'] = $itemid;
1276						$host_prototypes_to_create[] = $host_prototype;
1277					}
1278				}
1279			}
1280		}
1281
1282		// Attach existing host group prototype ids.
1283		if ($ex_group_prototypes_where) {
1284			$db_group_prototypes = DBFetchArray(DBselect(
1285				'SELECT group_prototypeid,name,hostid'.
1286					' FROM group_prototype'.
1287					' WHERE '.implode(' OR ', $ex_group_prototypes_where)
1288			));
1289
1290			if ($db_group_prototypes) {
1291				$groups_hash = [];
1292				foreach ($db_group_prototypes as $group) {
1293					$groups_hash[$group['hostid']][$group['name']] = $group['group_prototypeid'];
1294				}
1295
1296				foreach ($host_prototypes_to_update as &$host_prototype) {
1297					if (!array_key_exists($host_prototype['hostid'], $groups_hash)) {
1298						continue;
1299					}
1300
1301					$hash = $groups_hash[$host_prototype['hostid']];
1302					foreach ($host_prototype['groupPrototypes'] as &$group_prototype) {
1303						if (array_key_exists($group_prototype['name'], $hash)) {
1304							$group_prototype['group_prototypeid'] = $hash[$group_prototype['name']];
1305						}
1306					}
1307					unset($group_prototype, $hash);
1308				}
1309				unset($host_prototype, $groups_hash);
1310			}
1311		}
1312
1313		if ($item_prototypes_to_update) {
1314			ksort($item_prototypes_to_update);
1315			$this->updateItemsWithDependency($item_prototypes_to_update, $master_item_key, API::ItemPrototype());
1316		}
1317
1318		if ($item_prototypes_to_create) {
1319			ksort($item_prototypes_to_create);
1320			$this->createItemsWithDependency($item_prototypes_to_create, $master_item_key, API::ItemPrototype());
1321		}
1322
1323		if ($host_prototypes_to_update) {
1324			API::HostPrototype()->update($host_prototypes_to_update);
1325		}
1326
1327		if ($host_prototypes_to_create) {
1328			API::HostPrototype()->create($host_prototypes_to_create);
1329		}
1330
1331		// Refresh prototypes because templated ones can be inherited to host and used in triggers prototypes or graph
1332		//   prototypes.
1333		$this->referencer->refreshItems();
1334
1335		// First we need to create item prototypes and only then trigger and graph prototypes.
1336		$triggers_to_create = [];
1337		$triggers_to_update = [];
1338		$graphs_to_create = [];
1339		$graphs_to_update = [];
1340
1341		// The list of triggers to process dependencies.
1342		$triggers = [];
1343
1344		foreach ($discovery_rules_by_hosts as $host => $discovery_rules) {
1345			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
1346
1347			foreach ($discovery_rules as $discovery_rule) {
1348				// If rule was not processed we should not create/update any of its prototypes.
1349				if (!array_key_exists($discovery_rule['key_'], $processed_discovery_rules[$hostid])) {
1350					continue;
1351				}
1352
1353				foreach ($discovery_rule['trigger_prototypes'] as $trigger) {
1354					$triggerid = array_key_exists('uuid', $trigger)
1355						? $this->referencer->findTriggeridByUuid($trigger['uuid'])
1356						: $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'],
1357							$trigger['recovery_expression']
1358						);
1359
1360					$triggers[] = $trigger;
1361					unset($trigger['dependencies']);
1362
1363					if ($triggerid !== null) {
1364						$trigger['triggerid'] = $triggerid;
1365						$triggers_to_update[] = $trigger;
1366					}
1367					else {
1368						$triggers_to_create[] = $trigger;
1369					}
1370				}
1371
1372				foreach ($discovery_rule['graph_prototypes'] as $graph) {
1373					if ($graph['ymin_item_1']) {
1374						$hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymin_item_1']['host']);
1375
1376						$itemid = ($hostid !== null)
1377							? $this->referencer->findItemidByKey($hostid, $graph['ymin_item_1']['key'])
1378							: null;
1379
1380						if ($itemid === null) {
1381							throw new Exception(_s(
1382								'Cannot find item "%1$s" on "%2$s" used as the Y axis MIN value for graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".',
1383								$graph['ymin_item_1']['key'],
1384								$graph['ymin_item_1']['host'],
1385								$graph['name'],
1386								$discovery_rule['name'],
1387								$host
1388							));
1389						}
1390
1391						$graph['ymin_itemid'] = $itemid;
1392					}
1393
1394					if ($graph['ymax_item_1']) {
1395						$hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymax_item_1']['host']);
1396
1397						$itemid = ($hostid !== null)
1398							? $this->referencer->findItemidByKey($hostid, $graph['ymax_item_1']['key'])
1399							: null;
1400
1401						if ($itemid === null) {
1402							throw new Exception(_s(
1403								'Cannot find item "%1$s" on "%2$s" used as the Y axis MAX value for graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".',
1404								$graph['ymax_item_1']['key'],
1405								$graph['ymax_item_1']['host'],
1406								$graph['name'],
1407								$discovery_rule['name'],
1408								$host
1409							));
1410						}
1411
1412						$graph['ymax_itemid'] = $itemid;
1413					}
1414
1415					foreach ($graph['gitems'] as &$item) {
1416						$hostid = $this->referencer->findTemplateidOrHostidByHost($item['item']['host']);
1417
1418						$item['itemid'] = ($hostid !== null)
1419							? $this->referencer->findItemidByKey($hostid, $item['item']['key'])
1420							: null;
1421
1422						if ($item['itemid'] === null) {
1423							throw new Exception(_s(
1424								'Cannot find item "%1$s" on "%2$s" used in graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".',
1425								$item['item']['key'],
1426								$item['item']['host'],
1427								$graph['name'],
1428								$discovery_rule['name'],
1429								$host
1430							));
1431						}
1432					}
1433					unset($item);
1434
1435					$graphid = array_key_exists('uuid', $graph)
1436						? $this->referencer->findGraphidByUuid($graph['uuid'])
1437						: $this->referencer->findGraphidByName($hostid, $graph['name']);
1438
1439					if ($graphid !== null) {
1440						$graph['graphid'] = $graphid;
1441						unset($graph['uuid']);
1442						$graphs_to_update[] = $graph;
1443					}
1444					else {
1445						$graphs_to_create[] = $graph;
1446					}
1447				}
1448			}
1449		}
1450
1451		if ($triggers_to_update) {
1452			$updated_triggers = API::TriggerPrototype()->update(array_map(function($trigger) {
1453				unset($trigger['uuid']);
1454				return $trigger;
1455			}, $triggers_to_update));
1456
1457			foreach ($updated_triggers['triggerids'] as $index => $triggerid) {
1458				$trigger = $triggers_to_update[$index];
1459				$this->referencer->setDbTrigger($triggerid, $trigger);
1460			}
1461		}
1462
1463		if ($triggers_to_create) {
1464			$created_triggers = API::TriggerPrototype()->create($triggers_to_create);
1465
1466			foreach ($created_triggers['triggerids'] as $index => $triggerid) {
1467				$trigger = $triggers_to_create[$index];
1468				$this->referencer->setDbTrigger($triggerid, $trigger);
1469			}
1470		}
1471
1472		if ($graphs_to_update) {
1473			API::GraphPrototype()->update($graphs_to_update);
1474			$this->referencer->refreshGraphs();
1475		}
1476
1477		if ($graphs_to_create) {
1478			API::GraphPrototype()->create($graphs_to_create);
1479			$this->referencer->refreshGraphs();
1480		}
1481
1482		$this->processTriggerPrototypeDependencies($triggers);
1483	}
1484
1485	/**
1486	 * Update trigger prototype dependencies.
1487	 *
1488	 * @param array $triggers
1489	 *
1490	 * @throws Exception
1491	 */
1492	protected function processTriggerPrototypeDependencies(array $triggers): void {
1493		$trigger_dependencies = [];
1494
1495		foreach ($triggers as $trigger) {
1496			if (!array_key_exists('dependencies', $trigger)) {
1497				continue;
1498			}
1499
1500			$dependencies = [];
1501			$triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'],
1502				$trigger['recovery_expression']
1503			);
1504
1505			foreach ($trigger['dependencies'] as $dependency) {
1506				$dependent_triggerid = $this->referencer->findTriggeridByName($dependency['name'],
1507					$dependency['expression'], $dependency['recovery_expression']
1508				);
1509
1510				if ($dependent_triggerid === null) {
1511					throw new Exception(_s('Trigger prototype "%1$s" depends on trigger "%2$s", which does not exist.',
1512						$trigger['description'],
1513						$dependency['name']
1514					));
1515				}
1516
1517				$dependencies[] = ['triggerid' => $dependent_triggerid];
1518			}
1519
1520			$trigger_dependencies[] = [
1521				'triggerid' => $triggerid,
1522				'dependencies' => $dependencies
1523			];
1524		}
1525
1526		if ($trigger_dependencies) {
1527			API::TriggerPrototype()->update($trigger_dependencies);
1528		}
1529	}
1530
1531	/**
1532	 * Import web scenarios.
1533	 *
1534	 * @throws APIException
1535	 */
1536	protected function processHttpTests(): void {
1537		if (!$this->options['httptests']['createMissing'] && !$this->options['httptests']['updateExisting']) {
1538			return;
1539		}
1540
1541		$httptests_to_create = [];
1542		$httptests_to_update = [];
1543
1544		foreach ($this->getFormattedHttpTests() as $host => $httptests) {
1545			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
1546
1547			if ($hostid === null
1548					|| (!$this->importedObjectContainer->isHostProcessed($hostid)
1549						&& !$this->importedObjectContainer->isTemplateProcessed($hostid))) {
1550				continue;
1551			}
1552
1553			foreach ($httptests as $httptest) {
1554				$httptestid = array_key_exists('uuid', $httptest)
1555					? $this->referencer->findHttpTestidByUuid($httptest['uuid'])
1556					: $this->referencer->findHttpTestidByName($hostid, $httptest['name']);
1557
1558				if ($httptestid !== null) {
1559					foreach ($httptest['steps'] as &$httpstep) {
1560						$httpstepid = $this->referencer->findHttpStepidByName($hostid, $httptestid, $httpstep['name']);
1561
1562						if ($httpstepid !== null) {
1563							$httpstep['httpstepid'] = $httpstepid;
1564						}
1565					}
1566					unset($httpstep);
1567
1568					$httptest['httptestid'] = $httptestid;
1569					unset($httptest['uuid']);
1570
1571					$httptests_to_update[] = $httptest;
1572				}
1573				else {
1574					$httptest['hostid'] = $hostid;
1575					$httptests_to_create[] = $httptest;
1576				}
1577			}
1578		}
1579
1580		if ($this->options['httptests']['updateExisting'] && $httptests_to_update) {
1581			API::HttpTest()->update($httptests_to_update);
1582		}
1583
1584		if ($this->options['httptests']['createMissing'] && $httptests_to_create) {
1585			API::HttpTest()->create($httptests_to_create);
1586		}
1587
1588		$this->referencer->refreshHttpTests();
1589	}
1590
1591	/**
1592	 * Import graphs.
1593	 *
1594	 * @throws Exception
1595	 */
1596	protected function processGraphs(): void {
1597		if (!$this->options['graphs']['createMissing'] && !$this->options['graphs']['updateExisting']) {
1598			return;
1599		}
1600
1601		$graphs_to_create = [];
1602		$graphs_to_update = [];
1603
1604		foreach ($this->getFormattedGraphs() as $graph) {
1605			if ($graph['ymin_item_1']) {
1606				$hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymin_item_1']['host']);
1607				$itemid = ($hostid !== null)
1608					? $this->referencer->findItemidByKey($hostid, $graph['ymin_item_1']['key'])
1609					: null;
1610
1611				if ($itemid === null) {
1612					throw new Exception(_s(
1613						'Cannot find item "%1$s" on "%2$s" used as the Y axis MIN value for graph "%3$s".',
1614						$graph['ymin_item_1']['key'],
1615						$graph['ymin_item_1']['host'],
1616						$graph['name']
1617					));
1618				}
1619
1620				$graph['ymin_itemid'] = $itemid;
1621			}
1622
1623			if ($graph['ymax_item_1']) {
1624				$hostid = $this->referencer->findTemplateidOrHostidByHost($graph['ymax_item_1']['host']);
1625				$itemid = ($hostid !== null)
1626					? $this->referencer->findItemidByKey($hostid, $graph['ymax_item_1']['key'])
1627					: null;
1628
1629				if ($itemid === null) {
1630					throw new Exception(_s(
1631						'Cannot find item "%1$s" on "%2$s" used as the Y axis MAX value for graph "%3$s".',
1632						$graph['ymax_item_1']['key'],
1633						$graph['ymax_item_1']['host'],
1634						$graph['name']
1635					));
1636				}
1637
1638				$graph['ymax_itemid'] = $itemid;
1639			}
1640
1641			$hostid = null;
1642
1643			foreach ($graph['gitems'] as &$item) {
1644				$hostid = $this->referencer->findTemplateidOrHostidByHost($item['item']['host']);
1645				$item['itemid'] = ($hostid !== null)
1646					? $this->referencer->findItemidByKey($hostid, $item['item']['key'])
1647					: null;
1648
1649				if ($item['itemid'] === null) {
1650					throw new Exception(_s(
1651						'Cannot find item "%1$s" on "%2$s" used in graph "%3$s".',
1652						$item['item']['key'],
1653						$item['item']['host'],
1654						$graph['name']
1655					));
1656				}
1657			}
1658			unset($item);
1659
1660			if ($this->isTemplateGraph($graph)) {
1661				$graphid = $this->referencer->findGraphidByUuid($graph['uuid']);
1662			}
1663			else {
1664				unset($graph['uuid']);
1665				$graphid = $this->referencer->findGraphidByName($hostid, $graph['name']);
1666			}
1667
1668			if ($graphid !== null) {
1669				$graph['graphid'] = $graphid;
1670				unset($graph['uuid']);
1671				$graphs_to_update[] = $graph;
1672			}
1673			else {
1674				$graphs_to_create[] = $graph;
1675			}
1676		}
1677
1678		if ($this->options['graphs']['updateExisting'] && $graphs_to_update) {
1679			API::Graph()->update($graphs_to_update);
1680		}
1681
1682		if ($this->options['graphs']['createMissing'] && $graphs_to_create) {
1683			API::Graph()->create($graphs_to_create);
1684		}
1685
1686		$this->referencer->refreshGraphs();
1687	}
1688
1689	private function isTemplateGraph(array $graph): bool {
1690		if ($graph['ymin_item_1'] && $this->referencer->findTemplateidByHost($graph['ymin_item_1']['host'])) {
1691			return true;
1692		}
1693
1694		if ($graph['ymax_item_1'] && $this->referencer->findTemplateidByHost($graph['ymax_item_1']['host'])) {
1695			return true;
1696		}
1697
1698		if (array_key_exists('gitems', $graph) && $graph['gitems']) {
1699			foreach ($graph['gitems'] as $gitem) {
1700				if ($this->referencer->findTemplateidByHost($gitem['item']['host'])) {
1701					return true;
1702				}
1703			}
1704		}
1705
1706		return false;
1707	}
1708
1709	/**
1710	 * Import triggers.
1711	 *
1712	 * @throws Exception
1713	 */
1714	protected function processTriggers(): void {
1715		if (!$this->options['triggers']['createMissing'] && !$this->options['triggers']['updateExisting']) {
1716			return;
1717		}
1718
1719		$triggers_to_create = [];
1720		$triggers_to_update = [];
1721
1722		$triggers_to_process_dependencies = [];
1723
1724		foreach ($this->getFormattedTriggers() as $trigger) {
1725			$triggerid = null;
1726
1727			$is_template_trigger = $this->isTemplateTrigger($trigger);
1728
1729			if ($is_template_trigger && array_key_exists('uuid', $trigger)) {
1730				$triggerid = $this->referencer->findTriggeridByUuid($trigger['uuid']);
1731			}
1732			elseif (!$is_template_trigger) {
1733				unset($trigger['uuid']);
1734				$triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'],
1735					$trigger['recovery_expression']
1736				);
1737			}
1738
1739			if ($triggerid !== null) {
1740				if ($this->options['triggers']['updateExisting']) {
1741					$triggers_to_process_dependencies[] = $trigger;
1742
1743					$trigger['triggerid'] = $triggerid;
1744					unset($trigger['dependencies'], $trigger['uuid']);
1745					$triggers_to_update[] = $trigger;
1746				}
1747			}
1748			else {
1749				if ($this->options['triggers']['createMissing']) {
1750					$triggers_to_process_dependencies[] = $trigger;
1751
1752					unset($trigger['dependencies']);
1753					$triggers_to_create[] = $trigger;
1754				}
1755			}
1756		}
1757
1758		if ($triggers_to_update) {
1759			API::Trigger()->update($triggers_to_update);
1760		}
1761
1762		if ($triggers_to_create) {
1763			API::Trigger()->create($triggers_to_create);
1764		}
1765
1766		// Refresh triggers because template triggers can be inherited to host and used in maps.
1767		$this->referencer->refreshTriggers();
1768
1769		$this->processTriggerDependencies($triggers_to_process_dependencies);
1770	}
1771
1772	private function isTemplateTrigger(array $trigger): bool {
1773		$expression_parser = new CExpressionParser(['usermacros' => true]);
1774
1775		if ($expression_parser->parse($trigger['expression']) != CParser::PARSE_SUCCESS) {
1776			return false;
1777		}
1778
1779		foreach ($expression_parser->getResult()->getHosts() as $host) {
1780			$host = $this->referencer->findTemplateidByHost($host);
1781
1782			if ($host !== null) {
1783				return true;
1784			}
1785		}
1786
1787		if ($trigger['recovery_expression'] === ''
1788				|| $expression_parser->parse($trigger['recovery_expression']) != CParser::PARSE_SUCCESS) {
1789			return false;
1790		}
1791
1792		foreach ($expression_parser->getResult()->getHosts() as $host) {
1793			$host = $this->referencer->findTemplateidByHost($host);
1794
1795			if ($host !== null) {
1796				return true;
1797			}
1798		}
1799
1800		return false;
1801	}
1802
1803	/**
1804	 * Update trigger dependencies
1805	 *
1806	 * @param array $triggers
1807	 *
1808	 * @throws Exception
1809	 */
1810	protected function processTriggerDependencies(array $triggers): void {
1811		$trigger_dependencies = [];
1812
1813		foreach ($triggers as $trigger) {
1814			if (!array_key_exists('dependencies', $trigger)) {
1815				continue;
1816			}
1817
1818			$triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'],
1819				$trigger['recovery_expression']
1820			);
1821
1822			$dependencies = [];
1823
1824			foreach ($trigger['dependencies'] as $dependency) {
1825				$dependent_triggerid = $this->referencer->findTriggeridByName($dependency['name'],
1826					$dependency['expression'], $dependency['recovery_expression']
1827				);
1828
1829				if ($dependent_triggerid === null) {
1830					throw new Exception(_s('Trigger "%1$s" depends on trigger "%2$s", which does not exist.',
1831						$trigger['description'],
1832						$dependency['name']
1833					));
1834				}
1835
1836				$dependencies[] = ['triggerid' => $dependent_triggerid];
1837			}
1838
1839			$trigger_dependencies[] = [
1840				'triggerid' => $triggerid,
1841				'dependencies' => $dependencies
1842			];
1843		}
1844
1845		if ($trigger_dependencies) {
1846			API::Trigger()->update($trigger_dependencies);
1847		}
1848	}
1849
1850	/**
1851	 * Import images.
1852	 *
1853	 * @throws Exception
1854	 */
1855	protected function processImages(): void {
1856		if (!$this->options['images']['updateExisting'] && !$this->options['images']['createMissing']) {
1857			return;
1858		}
1859
1860		$images_to_import = $this->getFormattedImages();
1861
1862		if (!$images_to_import) {
1863			return;
1864		}
1865
1866		$images_to_update = [];
1867		$images_to_create = [];
1868
1869		foreach ($images_to_import as $image) {
1870			$imageid = $this->referencer->findImageidByName($image['name']);
1871
1872			if ($imageid !== null) {
1873				$image['imageid'] = $imageid;
1874				unset($image['imagetype']);
1875				$images_to_update[] = $image;
1876			}
1877			else {
1878				$images_to_create[] = $image;
1879			}
1880		}
1881
1882		if ($this->options['images']['updateExisting'] && $images_to_update) {
1883			API::Image()->update($images_to_update);
1884		}
1885
1886		if ($this->options['images']['createMissing'] && $images_to_create) {
1887			$created_images = API::Image()->create($images_to_create);
1888
1889			foreach ($images_to_create as $index => $image) {
1890				$this->referencer->setDbImage($created_images['imageids'][$index], $image);
1891			}
1892		}
1893	}
1894
1895	/**
1896	 * Import maps.
1897	 *
1898	 * @throws Exception
1899	 */
1900	protected function processMaps(): void {
1901		if ($this->options['maps']['updateExisting'] || $this->options['maps']['createMissing']) {
1902			$maps = $this->getFormattedMaps();
1903
1904			if ($maps) {
1905				$map_importer = new CMapImporter($this->options, $this->referencer, $this->importedObjectContainer);
1906				$map_importer->import($maps);
1907			}
1908		}
1909	}
1910
1911	/**
1912	 * Import template dashboards.
1913	 */
1914	protected function processTemplateDashboards(): void {
1915		if ($this->options['templateDashboards']['updateExisting']
1916				|| $this->options['templateDashboards']['createMissing']) {
1917			$dashboards = $this->getFormattedTemplateDashboards();
1918
1919			if ($dashboards) {
1920				$dashboard_importer = new CTemplateDashboardImporter($this->options, $this->referencer,
1921					$this->importedObjectContainer
1922				);
1923
1924				$dashboard_importer->import($dashboards);
1925			}
1926		}
1927	}
1928
1929	/**
1930	 * Import media types.
1931	 */
1932	protected function processMediaTypes(): void {
1933		if (!$this->options['mediaTypes']['updateExisting'] && !$this->options['mediaTypes']['createMissing']) {
1934			return;
1935		}
1936
1937		$media_types_to_import = $this->getFormattedMediaTypes();
1938
1939		if (!$media_types_to_import) {
1940			return;
1941		}
1942
1943		$media_types_to_import = zbx_toHash($media_types_to_import, 'name');
1944
1945		$db_media_types = API::MediaType()->get([
1946			'output' => ['mediatypeid', 'name'],
1947			'filter' => ['name' => array_keys($media_types_to_import)]
1948		]);
1949		$db_media_types = zbx_toHash($db_media_types, 'name');
1950
1951		$media_types_to_update = [];
1952		$media_types_to_create = [];
1953
1954		foreach ($media_types_to_import as $name => $media_type) {
1955			if (array_key_exists($name, $db_media_types)) {
1956				$media_type['mediatypeid'] = $db_media_types[$name]['mediatypeid'];
1957				$media_types_to_update[] = $media_type;
1958			}
1959			else {
1960				$media_types_to_create[] = $media_type;
1961			}
1962		}
1963
1964		if ($this->options['mediaTypes']['updateExisting'] && $media_types_to_update) {
1965			API::MediaType()->update($media_types_to_update);
1966		}
1967
1968		if ($this->options['mediaTypes']['createMissing'] && $media_types_to_create) {
1969			API::MediaType()->create($media_types_to_create);
1970		}
1971	}
1972
1973	/**
1974	 * Deletes items from DB that are missing in import file.
1975	 */
1976	protected function deleteMissingItems(): void {
1977		if (!$this->options['items']['deleteMissing']) {
1978			return;
1979		}
1980
1981		$processed_hostids = array_merge(
1982			$this->importedObjectContainer->getHostids(),
1983			$this->importedObjectContainer->getTemplateids()
1984		);
1985
1986		if (!$processed_hostids) {
1987			return;
1988		}
1989
1990		$itemids = [];
1991
1992		foreach ($this->getFormattedItems() as $host => $items) {
1993			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
1994
1995			if ($hostid === null) {
1996				continue;
1997			}
1998
1999			foreach ($items as $item) {
2000				$itemid = array_key_exists('uuid', $item)
2001					? $this->referencer->findItemidByUuid($item['uuid'])
2002					: $this->referencer->findItemidByKey($hostid, $item['key_']);
2003
2004				if ($itemid) {
2005					$itemids[$itemid] = [];
2006				}
2007			}
2008		}
2009
2010		$db_itemids = API::Item()->get([
2011			'output' => ['itemid'],
2012			'hostids' => $processed_hostids,
2013			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL],
2014			'inherited' => false,
2015			'preservekeys' => true,
2016			'nopermissions' => true
2017		]);
2018
2019		$items_to_delete = array_diff_key($db_itemids, $itemids);
2020
2021		if ($items_to_delete) {
2022			API::Item()->delete(array_keys($items_to_delete));
2023		}
2024
2025		$this->referencer->refreshItems();
2026	}
2027
2028	/**
2029	 * Deletes triggers from DB that are missing in import file.
2030	 */
2031	protected function deleteMissingTriggers(): void {
2032		if (!$this->options['triggers']['deleteMissing']) {
2033			return;
2034		}
2035
2036		$processed_hostids = array_merge(
2037			$this->importedObjectContainer->getHostids(),
2038			$this->importedObjectContainer->getTemplateids()
2039		);
2040
2041		if (!$processed_hostids) {
2042			return;
2043		}
2044
2045		$triggerids = [];
2046
2047		foreach ($this->getFormattedTriggers() as $trigger) {
2048			$triggerid = null;
2049
2050			if (array_key_exists('uuid', $trigger)) {
2051				$triggerid = $this->referencer->findTriggeridByUuid($trigger['uuid']);
2052			}
2053
2054			// In import file host trigger can have UUID assigned after conversion, such should be searched by name.
2055			if ($triggerid === null) {
2056				$triggerid = $this->referencer->findTriggeridByName($trigger['description'], $trigger['expression'],
2057					$trigger['recovery_expression']
2058				);
2059
2060				// Template triggers should only be searched by UUID.
2061				if ($triggerid !== null && array_key_exists('uuid', $trigger)) {
2062					$db_trigger = $this->referencer->findTriggerById($triggerid);
2063
2064					if ($db_trigger['uuid'] !== '' && $db_trigger['uuid'] !== $trigger['uuid']) {
2065						$triggerid = null;
2066					}
2067				}
2068			}
2069
2070			if ($triggerid !== null) {
2071				$triggerids[$triggerid] = [];
2072			}
2073		}
2074
2075		$db_triggerids = API::Trigger()->get([
2076			'output' => [],
2077			'selectHosts' => ['hostid'],
2078			'hostids' => $processed_hostids,
2079			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL],
2080			'inherited' => false,
2081			'preservekeys' => true,
2082			'nopermissions' => true
2083		]);
2084
2085		// Check that potentially deletable trigger belongs to same hosts that are in the import file.
2086		// If some triggers belong to more hosts than import file contains, don't delete them.
2087		$triggerids_to_delete = [];
2088		$processed_hostids = array_flip($processed_hostids);
2089
2090		foreach (array_diff_key($db_triggerids, $triggerids) as $triggerid => $trigger) {
2091			$trigger_hostids = array_flip(array_column($trigger['hosts'], 'hostid'));
2092
2093			if (!array_diff_key($trigger_hostids, $processed_hostids)) {
2094				$triggerids_to_delete[] = $triggerid;
2095			}
2096		}
2097
2098		if ($triggerids_to_delete) {
2099			API::Trigger()->delete($triggerids_to_delete);
2100		}
2101
2102		// refresh triggers because template triggers can be inherited to host and used in maps
2103		$this->referencer->refreshTriggers();
2104	}
2105
2106	/**
2107	 * Deletes graphs from DB that are missing in import file.
2108	 */
2109	protected function deleteMissingGraphs(): void {
2110		if (!$this->options['graphs']['deleteMissing']) {
2111			return;
2112		}
2113
2114		$processed_hostids = array_merge(
2115			$this->importedObjectContainer->getHostids(),
2116			$this->importedObjectContainer->getTemplateids()
2117		);
2118
2119		if (!$processed_hostids) {
2120			return;
2121		}
2122
2123		$graphids = [];
2124
2125		foreach ($this->getFormattedGraphs() as $graph) {
2126			$graphid = null;
2127
2128			if (array_key_exists('uuid', $graph)) {
2129				$graphid = $this->referencer->findGraphidByUuid($graph['uuid']);
2130			}
2131
2132			if ($graphid !== null) {
2133				$graphids[$graphid] = [];
2134			}
2135			elseif (array_key_exists('gitems', $graph)) {
2136				// In import file host graph can have UUID assigned after conversion, such should be searched by name.
2137				foreach ($graph['gitems'] as $gitem) {
2138					$gitem_hostid = $this->referencer->findTemplateidOrHostidByHost($gitem['item']['host']);
2139					$graphid = $this->referencer->findGraphidByName($gitem_hostid, $graph['name']);
2140
2141					if ($graphid !== null) {
2142						$graphids[$graphid] = [];
2143					}
2144				}
2145			}
2146		}
2147
2148		$db_graphids = API::Graph()->get([
2149			'output' => ['graphid'],
2150			'hostids' => $processed_hostids,
2151			'selectHosts' => ['hostid'],
2152			'preservekeys' => true,
2153			'nopermissions' => true,
2154			'inherited' => false,
2155			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
2156		]);
2157
2158		// check that potentially deletable graph belongs to same hosts that are in XML
2159		// if some graphs belong to more hosts than current XML contains, don't delete them
2160		$graphids_to_delete = [];
2161		$processed_hostids = array_flip($processed_hostids);
2162
2163		foreach (array_diff_key($db_graphids, $graphids) as $graphid => $graph) {
2164			$graph_hostids = array_flip(array_column($graph['hosts'], 'hostid'));
2165
2166			if (!array_diff_key($graph_hostids, $processed_hostids)) {
2167				$graphids_to_delete[] = $graphid;
2168			}
2169		}
2170
2171		if ($graphids_to_delete) {
2172			API::Graph()->delete($graphids_to_delete);
2173		}
2174
2175		$this->referencer->refreshGraphs();
2176	}
2177
2178	/**
2179	 * Deletes prototypes from DB that are missing in import file.
2180	 *
2181	 * @param array $discovery_rules_by_hosts
2182	 *
2183	 * @throws APIException
2184	 */
2185	protected function deleteMissingPrototypes(array $discovery_rules_by_hosts): void {
2186		$discovery_ruleids = [];
2187		$host_prototypeids = [];
2188		$trigger_prototypeids = [];
2189		$graph_prototypeids = [];
2190		$item_prototypeids = [];
2191
2192		foreach ($discovery_rules_by_hosts as $host => $discovery_rules) {
2193			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
2194
2195			foreach ($discovery_rules as $discovery_rule) {
2196				$discoveryid = array_key_exists('uuid', $discovery_rule)
2197					? $this->referencer->findItemidByUuid($discovery_rule['uuid'])
2198					: $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']);
2199
2200				if ($discoveryid === null) {
2201					continue;
2202				}
2203
2204				$discovery_ruleids[$discoveryid] = [];
2205
2206				foreach ($discovery_rule['host_prototypes'] as $host_prototype) {
2207					$host_prototypeid = array_key_exists('uuid', $host_prototype)
2208						? $this->referencer->findHostPrototypeidByUuid($host_prototype['uuid'])
2209						: $this->referencer->findHostPrototypeidByHost($hostid, $discoveryid, $host_prototype['host']);
2210
2211					if ($host_prototypeid !== null) {
2212						$host_prototypeids[$host_prototypeid] = [];
2213					}
2214				}
2215
2216				foreach ($discovery_rule['trigger_prototypes'] as $trigger_prototype) {
2217					$trigger_prototypeid = array_key_exists('uuid', $trigger_prototype)
2218						? $this->referencer->findTriggeridByUuid($trigger_prototype['uuid'])
2219						: $this->referencer->findTriggeridByName($trigger_prototype['description'],
2220							$trigger_prototype['expression'], $trigger_prototype['recovery_expression']
2221						);
2222
2223					if ($trigger_prototypeid !== null) {
2224						$trigger_prototypeids[$trigger_prototypeid] = [];
2225					}
2226				}
2227
2228				foreach ($discovery_rule['graph_prototypes'] as $graph_prototype) {
2229					$graph_prototypeid = array_key_exists('uuid', $graph_prototype)
2230						? $this->referencer->findGraphidByUuid($graph_prototype['uuid'])
2231						: $this->referencer->findGraphidByName($hostid, $graph_prototype['name']);
2232
2233					if ($graph_prototypeid !== null) {
2234						$graph_prototypeids[$graph_prototypeid] = [];
2235					}
2236				}
2237
2238				foreach ($discovery_rule['item_prototypes'] as $item_prototype) {
2239					$item_prototypeid = array_key_exists('uuid', $item_prototype)
2240						? $this->referencer->findItemidByUuid($item_prototype['uuid'])
2241						: $this->referencer->findItemidByKey($hostid, $item_prototype['key_']);
2242
2243					if ($item_prototypeid !== null) {
2244						$item_prototypeids[$item_prototypeid] = [];
2245					}
2246				}
2247			}
2248		}
2249
2250		$db_host_prototypes = API::HostPrototype()->get([
2251			'output' => [],
2252			'discoveryids' => array_keys($discovery_ruleids),
2253			'preservekeys' => true,
2254			'nopermissions' => true,
2255			'inherited' => false
2256		]);
2257
2258		$host_prototypes_to_delete = array_diff_key($db_host_prototypes, $host_prototypeids);
2259
2260		if ($host_prototypes_to_delete) {
2261			API::HostPrototype()->delete(array_keys($host_prototypes_to_delete));
2262		}
2263
2264		$db_trigger_prototypes = API::TriggerPrototype()->get([
2265			'output' => [],
2266			'discoveryids' => array_keys($discovery_ruleids),
2267			'preservekeys' => true,
2268			'nopermissions' => true,
2269			'inherited' => false
2270		]);
2271
2272		$trigger_prototypes_to_delete = array_diff_key($db_trigger_prototypes, $trigger_prototypeids);
2273
2274		// Unlike triggers that belong to multiple hosts, trigger prototypes do not, so we just delete them.
2275		if ($trigger_prototypes_to_delete) {
2276			API::TriggerPrototype()->delete(array_keys($trigger_prototypes_to_delete));
2277
2278			$this->referencer->refreshTriggers();
2279		}
2280
2281		$db_graph_prototypes = API::GraphPrototype()->get([
2282			'output' => [],
2283			'discoveryids' => array_keys($discovery_ruleids),
2284			'preservekeys' => true,
2285			'nopermissions' => true,
2286			'inherited' => false
2287		]);
2288
2289		$graph_prototypes_to_delete = array_diff_key($db_graph_prototypes, $graph_prototypeids);
2290
2291		// Unlike graphs that belong to multiple hosts, graph prototypes do not, so we just delete them.
2292		if ($graph_prototypes_to_delete) {
2293			API::GraphPrototype()->delete(array_keys($graph_prototypes_to_delete));
2294
2295			$this->referencer->refreshGraphs();
2296		}
2297
2298		$db_item_prototypes = API::ItemPrototype()->get([
2299			'output' => [],
2300			'discoveryids' => array_keys($discovery_ruleids),
2301			'preservekeys' => true,
2302			'nopermissions' => true,
2303			'inherited' => false
2304		]);
2305
2306		$item_prototypes_to_delete = array_diff_key($db_item_prototypes, $item_prototypeids);
2307
2308		if ($item_prototypes_to_delete) {
2309			API::ItemPrototype()->delete(array_keys($item_prototypes_to_delete));
2310
2311			$this->referencer->refreshItems();
2312		}
2313	}
2314
2315	/**
2316	 * Deletes web scenarios from DB that are missing in import file.
2317	 */
2318	protected function deleteMissingHttpTests(): void {
2319		if (!$this->options['httptests']['deleteMissing']) {
2320			return;
2321		}
2322
2323		$processed_hostids = array_merge(
2324			$this->importedObjectContainer->getHostids(),
2325			$this->importedObjectContainer->getTemplateids()
2326		);
2327
2328		if (!$processed_hostids) {
2329			return;
2330		}
2331
2332		$httptestids = [];
2333
2334		foreach ($this->getFormattedHttpTests() as $host => $httptests) {
2335			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
2336
2337			if ($hostid === null) {
2338				continue;
2339			}
2340
2341			foreach ($httptests as $httptest) {
2342				$httptestid = array_key_exists('uuid', $httptest)
2343					? $this->referencer->findHttpTestidByUuid($httptest['uuid'])
2344					: $this->referencer->findHttpTestidByName($hostid, $httptest['name']);
2345
2346				if ($httptestid !== null) {
2347					$httptestids[$httptestid] = [];
2348				}
2349			}
2350		}
2351
2352		$db_httptestids = API::HttpTest()->get([
2353			'output' => [],
2354			'hostids' => $processed_hostids,
2355			'inherited' => false,
2356			'preservekeys' => true,
2357			'nopermissions' => true
2358		]);
2359
2360		$httptestids_to_delete = array_diff_key($db_httptestids, $httptestids);
2361
2362		if ($httptestids_to_delete) {
2363			API::HttpTest()->delete(array_keys($httptestids_to_delete));
2364		}
2365
2366		$this->referencer->refreshHttpTests();
2367	}
2368
2369	/**
2370	 * Deletes template dashboards from DB that are missing in import file.
2371	 *
2372	 * @throws APIException
2373	 */
2374	protected function deleteMissingTemplateDashboards(): void {
2375		if (!$this->options['templateDashboards']['deleteMissing']) {
2376			return;
2377		}
2378
2379		$dashboards = $this->getFormattedTemplateDashboards();
2380
2381		if ($dashboards) {
2382			$dashboard_importer = new CTemplateDashboardImporter($this->options, $this->referencer,
2383				$this->importedObjectContainer
2384			);
2385
2386			$dashboard_importer->delete($dashboards);
2387		}
2388	}
2389
2390	/**
2391	 * Deletes discovery rules from DB that are missing in import file.
2392	 */
2393	protected function deleteMissingDiscoveryRules(): void {
2394		if (!$this->options['discoveryRules']['deleteMissing']) {
2395			return;
2396		}
2397
2398		$processed_hostids = array_merge(
2399			$this->importedObjectContainer->getHostids(),
2400			$this->importedObjectContainer->getTemplateids()
2401		);
2402
2403		if (!$processed_hostids) {
2404			return;
2405		}
2406
2407		$discovery_ruleids = [];
2408
2409		foreach ($this->getFormattedDiscoveryRules() as $host => $discovery_rules) {
2410			$hostid = $this->referencer->findTemplateidOrHostidByHost($host);
2411
2412			if ($hostid === null) {
2413				continue;
2414			}
2415
2416			foreach ($discovery_rules as $discovery_rule) {
2417				$discovery_ruleid = array_key_exists('uuid', $discovery_rule)
2418					? $this->referencer->findItemidByUuid($discovery_rule['uuid'])
2419					: $this->referencer->findItemidByKey($hostid, $discovery_rule['key_']);
2420
2421				if ($discovery_ruleid !== null) {
2422					$discovery_ruleids[$discovery_ruleid] = [];
2423				}
2424			}
2425		}
2426
2427		$db_discovery_ruleids = API::DiscoveryRule()->get([
2428			'output' => ['itemid'],
2429			'hostids' => $processed_hostids,
2430			'inherited' => false,
2431			'preservekeys' => true,
2432			'nopermissions' => true
2433		]);
2434
2435		$discovery_ruleids_to_delete = array_diff_key($db_discovery_ruleids, $discovery_ruleids);
2436
2437		if ($discovery_ruleids_to_delete) {
2438			API::DiscoveryRule()->delete(array_keys($discovery_ruleids_to_delete));
2439		}
2440
2441		$this->referencer->refreshItems();
2442	}
2443
2444	/**
2445	 * Get formatted groups.
2446	 *
2447	 * @return array
2448	 */
2449	protected function getFormattedGroups(): array {
2450		if (!array_key_exists('groups', $this->formattedData)) {
2451			$this->formattedData['groups'] = $this->adapter->getGroups();
2452		}
2453
2454		return $this->formattedData['groups'];
2455	}
2456
2457	/**
2458	 * Get formatted templates.
2459	 *
2460	 * @return array
2461	 */
2462	public function getFormattedTemplates(): array {
2463		if (!array_key_exists('templates', $this->formattedData)) {
2464			$this->formattedData['templates'] = $this->adapter->getTemplates();
2465		}
2466
2467		return $this->formattedData['templates'];
2468	}
2469
2470	/**
2471	 * Get formatted hosts.
2472	 *
2473	 * @return array
2474	 */
2475	public function getFormattedHosts(): array {
2476		if (!array_key_exists('hosts', $this->formattedData)) {
2477			$this->formattedData['hosts'] = $this->adapter->getHosts();
2478		}
2479
2480		return $this->formattedData['hosts'];
2481	}
2482
2483	/**
2484	 * Get formatted items.
2485	 *
2486	 * @return array
2487	 */
2488	protected function getFormattedItems(): array {
2489		if (!array_key_exists('items', $this->formattedData)) {
2490			$this->formattedData['items'] = $this->adapter->getItems();
2491		}
2492
2493		return $this->formattedData['items'];
2494	}
2495
2496	/**
2497	 * Get formatted discovery rules.
2498	 *
2499	 * @return array
2500	 */
2501	protected function getFormattedDiscoveryRules(): array {
2502		if (!array_key_exists('discoveryRules', $this->formattedData)) {
2503			$this->formattedData['discoveryRules'] = $this->adapter->getDiscoveryRules();
2504		}
2505
2506		return $this->formattedData['discoveryRules'];
2507	}
2508
2509	/**
2510	 * Get formatted web scenarios.
2511	 *
2512	 * @return array
2513	 */
2514	protected function getFormattedHttpTests(): array {
2515		if (!array_key_exists('httptests', $this->formattedData)) {
2516			$this->formattedData['httptests'] = $this->adapter->getHttpTests();
2517		}
2518
2519		return $this->formattedData['httptests'];
2520	}
2521
2522	/**
2523	 * Get formatted web scenario steps.
2524	 *
2525	 * @return array
2526	 */
2527	protected function getFormattedHttpSteps(): array {
2528		if (!array_key_exists('httpsteps', $this->formattedData)) {
2529			$this->formattedData['httpsteps'] = $this->adapter->getHttpSteps();
2530		}
2531
2532		return $this->formattedData['httpsteps'];
2533	}
2534
2535	/**
2536	 * Get formatted triggers.
2537	 *
2538	 * @return array
2539	 */
2540	protected function getFormattedTriggers(): array {
2541		if (!array_key_exists('triggers', $this->formattedData)) {
2542			$this->formattedData['triggers'] = $this->adapter->getTriggers();
2543		}
2544
2545		return $this->formattedData['triggers'];
2546	}
2547
2548	/**
2549	 * Get formatted graphs.
2550	 *
2551	 * @return array
2552	 */
2553	protected function getFormattedGraphs(): array {
2554		if (!array_key_exists('graphs', $this->formattedData)) {
2555			$this->formattedData['graphs'] = $this->adapter->getGraphs();
2556		}
2557
2558		return $this->formattedData['graphs'];
2559	}
2560
2561	/**
2562	 * Get formatted images.
2563	 *
2564	 * @return array
2565	 */
2566	protected function getFormattedImages(): array {
2567		if (!array_key_exists('images', $this->formattedData)) {
2568			$this->formattedData['images'] = $this->adapter->getImages();
2569		}
2570
2571		return $this->formattedData['images'];
2572	}
2573
2574	/**
2575	 * Get formatted maps.
2576	 *
2577	 * @return array
2578	 */
2579	protected function getFormattedMaps(): array {
2580		if (!array_key_exists('maps', $this->formattedData)) {
2581			$this->formattedData['maps'] = $this->adapter->getMaps();
2582		}
2583
2584		return $this->formattedData['maps'];
2585	}
2586
2587	/**
2588	 * Get formatted template dashboards.
2589	 *
2590	 * @return array
2591	 */
2592	protected function getFormattedTemplateDashboards(): array {
2593		if (!array_key_exists('templateDashboards', $this->formattedData)) {
2594				$this->formattedData['templateDashboards'] = $this->adapter->getTemplateDashboards();
2595		}
2596
2597		return $this->formattedData['templateDashboards'];
2598	}
2599
2600	/**
2601	 * Get formatted media types.
2602	 *
2603	 * @return array
2604	 */
2605	protected function getFormattedMediaTypes(): array {
2606		if (!array_key_exists('mediaTypes', $this->formattedData)) {
2607			$this->formattedData['mediaTypes'] = $this->adapter->getMediaTypes();
2608		}
2609
2610		return $this->formattedData['mediaTypes'];
2611	}
2612
2613	/**
2614	 * Get items keys order tree, to ensure that master item will be inserted or updated before any of it dependent
2615	 * item. Returns associative array where key is item index and value is item dependency level.
2616	 *
2617	 * @param string $master_item_key  String containing master key name used to identify item master.
2618	 *
2619	 * @return array
2620	 *
2621	 * @throws Exception
2622	 */
2623	protected function getItemsOrder(string $master_item_key): array {
2624		$entities = $this->getFormattedItems();
2625
2626		return $this->getEntitiesOrder($entities, $master_item_key);
2627	}
2628
2629	/**
2630	 * Get discovery rules items prototypes keys order tree, to ensure that master item will be inserted or updated
2631	 * before any of it dependent item. Returns associative array where key is item prototype index and value is item
2632	 * prototype dependency level.
2633	 *
2634	 * @param string $master_item_key  String containing master key name used to identify item master.
2635	 *
2636	 * @return array
2637	 *
2638	 * @throws Exception
2639	 */
2640	protected function getDiscoveryRulesItemsOrder(string $master_item_key): array {
2641		$discovery_rules_by_hosts = $this->getFormattedDiscoveryRules();
2642		$entities_order = [];
2643
2644		foreach ($discovery_rules_by_hosts as $host => $discovery_rules) {
2645			foreach ($discovery_rules as $discovery_rule) {
2646				if ($discovery_rule['item_prototypes']) {
2647					$item_prototypes = [$host => $discovery_rule['item_prototypes']];
2648					$item_prototypes = $this->getEntitiesOrder($item_prototypes, $master_item_key, true);
2649					$entities_order[$host][$discovery_rule['key_']] = $item_prototypes[$host];
2650				}
2651			}
2652		}
2653
2654		return $entities_order;
2655	}
2656
2657	/**
2658	 * Generic method to get entities order tree, to ensure that master entity will be inserted or updated before any
2659	 * of it dependent entities.
2660	 * Returns associative array where key is entity index in source array grouped by host key and value is entity
2661	 * dependency level.
2662	 *
2663	 * @param array  $items_by_hosts   Associative array of host key and host items.
2664	 * @param string $master_item_key  String containing master key name to identify item master.
2665	 * @param bool   $get_prototypes   Option to get also master item prototypes not found in supplied input.
2666	 *
2667	 * @return array
2668	 *
2669	 * @throws Exception if data is invalid.
2670	 */
2671	protected function getEntitiesOrder(array $items_by_hosts, string $master_item_key,
2672			bool $get_prototypes = false): array {
2673		$parent_item_hostids = [];
2674		$parent_item_keys = [];
2675		$resolved_masters_cache = [];
2676
2677		$host_name_to_hostid = array_fill_keys(array_keys($items_by_hosts), null);
2678
2679		foreach ($host_name_to_hostid as $host_name => &$hostid) {
2680			$hostid = $this->referencer->findTemplateidOrHostidByHost($host_name);
2681		}
2682		unset($hostid);
2683
2684		foreach ($items_by_hosts as $host_name => $items) {
2685			if (!array_key_exists($host_name, $host_name_to_hostid)) {
2686				throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'host',
2687					_s('value "%1$s" not found', $host_name)
2688				));
2689			}
2690
2691			if (!array_key_exists($host_name, $resolved_masters_cache)) {
2692				$resolved_masters_cache[$host_name] = [];
2693			}
2694
2695			// Cache input array entities.
2696			foreach ($items as $item) {
2697				$resolved_masters_cache[$host_name][$item['key_']] = [
2698					'type' => $item['type'],
2699					$master_item_key => $item[$master_item_key]
2700				];
2701
2702				if ($item['type'] == ITEM_TYPE_DEPENDENT && array_key_exists('key', $item[$master_item_key])) {
2703					$parent_item_hostids[$host_name_to_hostid[$host_name]] = true;
2704					$parent_item_keys[$item[$master_item_key]['key']] = true;
2705				}
2706			}
2707		}
2708
2709		// There are entities to resolve from database, resolve and cache them recursively.
2710		if ($parent_item_keys) {
2711			/*
2712			 * For existing items, 'referencer' should be initialized before 'setDbItem' method will be used.
2713			 * Registering reference when property 'db_items' is empty, will not allow first call of
2714			 * 'findValueMapidByName' method update references to existing items.
2715			 */
2716			$this->referencer->initItemsReferences();
2717
2718			$options = [
2719				'output' => ['itemid', 'uuid', 'key_', 'type', 'hostid', 'master_itemid'],
2720				'hostids' => array_keys($parent_item_hostids),
2721				'filter' => ['key_' => array_keys($parent_item_keys)],
2722				'preservekeys' => true
2723			];
2724
2725			$db_items = API::Item()->get($options + ['webitems' => true]);
2726
2727			if ($get_prototypes) {
2728				$db_items += API::ItemPrototype()->get($options);
2729			}
2730
2731			$resolve_entity_keys = [];
2732			$itemid_to_item_key_by_hosts = [];
2733
2734			for ($level = 0; $level < ZBX_DEPENDENT_ITEM_MAX_LEVELS; $level++) {
2735				$missing_master_itemids = [];
2736
2737				foreach ($db_items as $itemid => $item) {
2738					$host_name = array_search($item['hostid'], $host_name_to_hostid);
2739
2740					$this->referencer->setDbItem($itemid, $item);
2741
2742					$item['key'] = $item['key_'];
2743					unset($item['key_']);
2744
2745					$itemid_to_item_key_by_hosts[$host_name][$itemid] = $item['key'];
2746
2747					$cache_entity = [
2748						'type' => $item['type']
2749					];
2750
2751					if ($item['type'] == ITEM_TYPE_DEPENDENT) {
2752						$master_itemid = $item['master_itemid'];
2753
2754						if (array_key_exists($master_itemid, $itemid_to_item_key_by_hosts[$host_name])) {
2755							$cache_entity[$master_item_key] = [
2756								'key' => $itemid_to_item_key_by_hosts[$host_name][$master_itemid]
2757							];
2758						}
2759						else {
2760							$missing_master_itemids[] = $item['master_itemid'];
2761							$resolve_entity_keys[] = [
2762								'host' => $host_name,
2763								'key' => $item['key'],
2764								'master_itemid' => $item['master_itemid']
2765							];
2766						}
2767					}
2768
2769					$resolved_masters_cache[$host_name][$item['key']] = $cache_entity;
2770				}
2771
2772				if ($missing_master_itemids) {
2773					$options = [
2774						'output' => ['uuid', 'key_', 'type', 'hostid', 'master_itemid'],
2775						'itemids' => $missing_master_itemids,
2776						'preservekeys' => true
2777					];
2778					$db_items = API::Item()->get($options + ['webitems' => true]);
2779
2780					if ($get_prototypes) {
2781						$db_items += API::ItemPrototype()->get($options);
2782					}
2783				}
2784				else {
2785					break;
2786				}
2787			}
2788
2789			if ($missing_master_itemids) {
2790				throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
2791					_('maximum number of dependency levels reached')
2792				));
2793			}
2794
2795			foreach ($resolve_entity_keys as $item) {
2796				$master_itemid = $item['master_itemid'];
2797
2798				if (!array_key_exists($item['host'], $itemid_to_item_key_by_hosts) ||
2799						!array_key_exists($master_itemid, $itemid_to_item_key_by_hosts[$item['host']])) {
2800					throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
2801						_s('value "%1$s" not found', $master_itemid)
2802					));
2803				}
2804
2805				$master_key = $itemid_to_item_key_by_hosts[$item['host']][$master_itemid];
2806				$resolved_masters_cache[$item['host']][$item['key']] += [
2807					$master_item_key => ['key' => $master_key]
2808				];
2809			}
2810
2811			unset($resolve_entity_keys, $itemid_to_item_key_by_hosts);
2812		}
2813
2814		// Resolve every entity dependency level.
2815		$tree = [];
2816
2817		foreach ($items_by_hosts as $host_name => $items) {
2818			$hostid = $host_name_to_hostid[$host_name];
2819			$host_items_tree = [];
2820
2821			foreach ($items as $index => $item) {
2822				$level = 0;
2823				$traversal_path = [$item['key_']];
2824
2825				while ($item && $item['type'] == ITEM_TYPE_DEPENDENT) {
2826					if (!array_key_exists('key', $item[$master_item_key])) {
2827						throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
2828							_('cannot be empty')
2829						));
2830					}
2831
2832					$master_key = $item[$master_item_key]['key'];
2833
2834					if (array_key_exists($host_name, $resolved_masters_cache)
2835							&& array_key_exists($master_key, $resolved_masters_cache[$host_name])) {
2836						$item = $resolved_masters_cache[$host_name][$master_key];
2837
2838						if (($item['type'] == ITEM_TYPE_DEPENDENT
2839									&& $item[$master_item_key]
2840									&& $master_key === $item[$master_item_key]['key'])
2841								|| in_array($master_key, $traversal_path)) {
2842							throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
2843								_('circular item dependency is not allowed')
2844							));
2845						}
2846
2847						$traversal_path[] = $master_key;
2848						$level++;
2849					}
2850					else {
2851						throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
2852							_s('value "%1$s" not found', $master_key)
2853						));
2854					}
2855
2856					if ($level > ZBX_DEPENDENT_ITEM_MAX_LEVELS) {
2857						throw new Exception(_s('Incorrect value for field "%1$s": %2$s.', 'master_itemid',
2858							_('maximum number of dependency levels reached')
2859						));
2860					}
2861				}
2862
2863				$host_items_tree[$index] = $level;
2864			}
2865
2866			$tree[$host_name] = $host_items_tree;
2867		}
2868
2869		// Order item indexes in descending order by nesting level.
2870		foreach ($tree as &$item_indexes) {
2871			asort($item_indexes);
2872		}
2873		unset($item_indexes);
2874
2875		return $tree;
2876	}
2877}
2878