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 CTriggerExpression
44	 */
45	protected $triggerExpression;
46
47	/**
48	 * @var array
49	 */
50	protected $options;
51
52	/**
53	 * @var array with data read from source string
54	 */
55	protected $data;
56
57	/**
58	 * @var array  cached data from the adapter
59	 */
60	protected $formattedData = [];
61
62	/**
63	 * Constructor.
64	 * Source string must be suitable for reader class,
65	 * i.e. if string contains json then reader should be able to read json.
66	 *
67	 * @param array						$options					import options "createMissing", "updateExisting" and "deleteMissing"
68	 * @param CImportReferencer			$referencer					class containing all importable objects
69	 * @param CImportedObjectContainer	$importedObjectContainer	class containing processed host and template IDs
70	 * @param CTriggerExpression		$triggerExpression			class to parse trigger expression
71	 */
72	public function __construct(array $options = [], CImportReferencer $referencer,
73			CImportedObjectContainer $importedObjectContainer, CTriggerExpression $triggerExpression) {
74		$this->options = [
75			'groups' => ['createMissing' => false],
76			'hosts' => ['updateExisting' => false, 'createMissing' => false],
77			'templates' => ['updateExisting' => false, 'createMissing' => false],
78			'templateScreens' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
79			'applications' => ['updateExisting' => false, 'createMissing' => false],
80			'templateLinkage' => ['createMissing' => false],
81			'items' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
82			'discoveryRules' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
83			'triggers' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
84			'graphs' => ['updateExisting' => false, 'createMissing' => false, 'deleteMissing' => false],
85			'screens' => ['updateExisting' => false, 'createMissing' => false],
86			'maps' => ['updateExisting' => false, 'createMissing' => false],
87			'images' => ['updateExisting' => false, 'createMissing' => false],
88			'valueMaps' => ['updateExisting' => false, 'createMissing' => false]
89		];
90
91		$this->options = array_merge($this->options, $options);
92		$this->referencer = $referencer;
93		$this->importedObjectContainer = $importedObjectContainer;
94		$this->triggerExpression = $triggerExpression;
95	}
96
97	/**
98	 * Import configuration data.
99	 *
100	 * @param CImportDataAdapter $adapter   an object to provide access to the imported data
101	 *
102	 * @return bool
103	 */
104	public function import(CImportDataAdapter $adapter) {
105		$this->adapter = $adapter;
106
107		// parse all import for references to resolve them all together with less sql count
108		$this->gatherReferences();
109
110		$this->processGroups();
111		$this->processTemplates();
112		$this->processHosts();
113
114		// delete missing objects from processed hosts and templates
115		$this->deleteMissingDiscoveryRules();
116		$this->deleteMissingTriggers();
117		$this->deleteMissingGraphs();
118		$this->deleteMissingItems();
119		$this->deleteMissingApplications();
120
121		// import objects
122		$this->processApplications();
123		$this->processValueMaps();
124		$this->processItems();
125		$this->processTriggers();
126		$this->processDiscoveryRules();
127		$this->processGraphs();
128		$this->processImages();
129		$this->processMaps();
130		$this->processTemplateScreens();
131		$this->processScreens();
132
133		return true;
134	}
135
136	/**
137	 * Parse all import data and collect references to objects.
138	 * For host objects it collects host names, for items - host name and item key, etc.
139	 * Collected references are added and resolved via the $this->referencer object.
140	 *
141	 * @see CImportReferencer
142	 */
143	protected function gatherReferences() {
144		$groupsRefs = [];
145		$templatesRefs = [];
146		$hostsRefs = [];
147		$applicationsRefs = [];
148		$itemsRefs = [];
149		$valueMapsRefs = [];
150		$triggersRefs = [];
151		$graphsRefs = [];
152		$iconMapsRefs = [];
153		$mapsRefs = [];
154		$screensRefs = [];
155		$templateScreensRefs = [];
156		$macrosRefs = [];
157		$proxyRefs = [];
158		$hostPrototypesRefs = [];
159
160		foreach ($this->getFormattedGroups() as $group) {
161			$groupsRefs[$group['name']] = $group['name'];
162		}
163
164		foreach ($this->getFormattedTemplates() as $template) {
165			$templatesRefs[$template['host']] = $template['host'];
166
167			foreach ($template['groups'] as $group) {
168				$groupsRefs[$group['name']] = $group['name'];
169			}
170
171			if (array_key_exists('macros', $template)) {
172				foreach ($template['macros'] as $macro) {
173					$macrosRefs[$template['host']][$macro['macro']] = $macro['macro'];
174				}
175			}
176
177			if (!empty($template['templates'])) {
178				foreach ($template['templates'] as $linkedTemplate) {
179					$templatesRefs[$linkedTemplate['name']] = $linkedTemplate['name'];
180				}
181			}
182		}
183
184		foreach ($this->getFormattedHosts() as $host) {
185			$hostsRefs[$host['host']] = $host['host'];
186
187			foreach ($host['groups'] as $group) {
188				$groupsRefs[$group['name']] = $group['name'];
189			}
190
191			if (array_key_exists('macros', $host)) {
192				foreach ($host['macros'] as $macro) {
193					$macrosRefs[$host['host']][$macro['macro']] = $macro['macro'];
194				}
195			}
196
197			if (!empty($host['templates'])) {
198				foreach ($host['templates'] as $linkedTemplate) {
199					$templatesRefs[$linkedTemplate['name']] = $linkedTemplate['name'];
200				}
201			}
202
203			if (!empty($host['proxy'])) {
204				$proxyRefs[$host['proxy']['name']] = $host['proxy']['name'];
205			}
206		}
207
208		foreach ($this->getFormattedApplications() as $host => $applications) {
209			foreach ($applications as $app) {
210				$applicationsRefs[$host][$app['name']] = $app['name'];
211			}
212		}
213
214		foreach ($this->getFormattedValueMaps() as $valuemap) {
215			$valueMapsRefs[$valuemap['name']] = $valuemap['name'];
216		}
217
218		foreach ($this->getFormattedItems() as $host => $items) {
219			foreach ($items as $item) {
220				$itemsRefs[$host][$item['key_']] = $item['key_'];
221
222				foreach ($item['applications'] as $app) {
223					$applicationsRefs[$host][$app['name']] = $app['name'];
224				}
225
226				if (!empty($item['valuemap'])) {
227					$valueMapsRefs[$item['valuemap']['name']] = $item['valuemap']['name'];
228				}
229			}
230		}
231
232		foreach ($this->getFormattedDiscoveryRules() as $host => $discoveryRules) {
233			foreach ($discoveryRules as $discoveryRule) {
234				$itemsRefs[$host][$discoveryRule['key_']] = $discoveryRule['key_'];
235
236				foreach ($discoveryRule['item_prototypes'] as $itemp) {
237					$itemsRefs[$host][$itemp['key_']] = $itemp['key_'];
238
239					foreach ($itemp['applications'] as $app) {
240						$applicationsRefs[$host][$app['name']] = $app['name'];
241					}
242
243					if (!empty($itemp['valuemap'])) {
244						$valueMapsRefs[$itemp['valuemap']['name']] = $itemp['valuemap']['name'];
245					}
246				}
247
248				foreach ($discoveryRule['trigger_prototypes'] as $trigger) {
249					$triggersRefs[$trigger['description']][$trigger['expression']] = $trigger['expression'];
250
251					// add found hosts and items to references from parsed trigger expressions
252					foreach ($trigger['parsedExpressions'] as $expression) {
253						$hostsRefs[$expression['host']] = $expression['host'];
254						$itemsRefs[$expression['host']][$expression['item']] = $expression['item'];
255					}
256
257					if (array_key_exists('dependencies', $trigger)) {
258						foreach ($trigger['dependencies'] as $dependency) {
259							$triggersRefs[$dependency['name']][$dependency['expression']] = $dependency['expression'];
260						}
261					}
262				}
263
264				foreach ($discoveryRule['graph_prototypes'] as $graph) {
265					if ($graph['ymin_item_1']) {
266						$yMinItem = $graph['ymin_item_1'];
267						$itemsRefs[$yMinItem['host']][$yMinItem['key']] = $yMinItem['key'];
268					}
269
270					if ($graph['ymax_item_1']) {
271						$yMaxItem = $graph['ymax_item_1'];
272						$itemsRefs[$yMaxItem['host']][$yMaxItem['key']] = $yMaxItem['key'];
273					}
274
275					foreach ($graph['gitems'] as $gitem) {
276						$gitemItem = $gitem['item'];
277
278						$hostsRefs[$gitemItem['host']] = $gitemItem['host'];
279						$itemsRefs[$gitemItem['host']][$gitemItem['key']] = $gitemItem['key'];
280						$graphsRefs[$gitemItem['host']][$graph['name']] = $graph['name'];
281					}
282				}
283
284				foreach ($discoveryRule['host_prototypes'] as $hostPrototype) {
285					$hostPrototypesRefs[$host][$discoveryRule['key_']][$hostPrototype['host']] = $hostPrototype['host'];
286
287					foreach ($hostPrototype['group_prototypes'] as $groupPrototype) {
288						if (isset($groupPrototype['group'])) {
289							$groupsRefs[$groupPrototype['group']['name']] = $groupPrototype['group']['name'];
290						}
291					}
292
293					foreach ($hostPrototype['templates'] as $template) {
294						$templatesRefs[$template['name']] = $template['name'];
295					}
296				}
297			}
298		}
299
300		foreach ($this->getFormattedGraphs() as $graph) {
301			if ($graph['ymin_item_1']) {
302				$yMinItem = $graph['ymin_item_1'];
303
304				$hostsRefs[$yMinItem['host']] = $yMinItem['host'];
305				$itemsRefs[$yMinItem['host']][$yMinItem['key']] = $yMinItem['key'];
306			}
307
308			if ($graph['ymax_item_1']) {
309				$yMaxItem = $graph['ymax_item_1'];
310
311				$hostsRefs[$yMaxItem['host']] = $yMaxItem['host'];
312				$itemsRefs[$yMaxItem['host']][$yMaxItem['key']] = $yMaxItem['key'];
313			}
314
315			if (isset($graph['gitems']) && $graph['gitems']) {
316				foreach ($graph['gitems'] as $gitem) {
317					$gitemItem = $gitem['item'];
318
319					$hostsRefs[$gitemItem['host']] = $gitemItem['host'];
320					$itemsRefs[$gitemItem['host']][$gitemItem['key']] = $gitemItem['key'];
321					$graphsRefs[$gitemItem['host']][$graph['name']] = $graph['name'];
322				}
323			}
324		}
325
326		foreach ($this->getFormattedTriggers() as $trigger) {
327			$triggersRefs[$trigger['description']][$trigger['expression']] = $trigger['expression'];
328
329			// add found hosts and items to references from parsed trigger expressions
330			foreach ($trigger['parsedExpressions'] as $expression) {
331				$hostsRefs[$expression['host']] = $expression['host'];
332				$itemsRefs[$expression['host']][$expression['item']] = $expression['item'];
333			}
334
335			if (array_key_exists('dependencies', $trigger)) {
336				foreach ($trigger['dependencies'] as $dependency) {
337					$triggersRefs[$dependency['name']][$dependency['expression']] = $dependency['expression'];
338				}
339			}
340		}
341
342		foreach ($this->getFormattedMaps() as $map) {
343			$mapsRefs[$map['name']] = $map['name'];
344
345			if (!empty($map['iconmap'])) {
346				$iconMapsRefs[$map['iconmap']['name']] = $map['iconmap']['name'];
347			}
348
349			if (isset($map['selements'])) {
350				foreach ($map['selements'] as $selement) {
351					switch ($selement['elementtype']) {
352						case SYSMAP_ELEMENT_TYPE_MAP:
353							$mapsRefs[$selement['element']['name']] = $selement['element']['name'];
354							break;
355
356						case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
357							$groupsRefs[$selement['element']['name']] = $selement['element']['name'];
358							break;
359
360						case SYSMAP_ELEMENT_TYPE_HOST:
361							$hostsRefs[$selement['element']['host']] = $selement['element']['host'];
362							break;
363
364						case SYSMAP_ELEMENT_TYPE_TRIGGER:
365							$el = $selement['element'];
366							$triggersRefs[$el['description']][$el['expression']] = $el['expression'];
367							break;
368					}
369				}
370			}
371
372			if (isset($map['links'])) {
373				foreach ($map['links'] as $link) {
374					if (isset($link['linktriggers'])) {
375						foreach ($link['linktriggers'] as $linkTrigger) {
376							$t = $linkTrigger['trigger'];
377							$triggersRefs[$t['description']][$t['expression']] = $t['expression'];
378						}
379					}
380				}
381			}
382		}
383
384		foreach ($this->getFormattedScreens() as $screen) {
385			$screensRefs[$screen['name']] = $screen['name'];
386
387			if (!empty($screen['screenitems'])) {
388				foreach ($screen['screenitems'] as $screenItem) {
389					$resource = $screenItem['resource'];
390
391					if (empty($resource)) {
392						continue;
393					}
394
395					switch ($screenItem['resourcetype']) {
396						case SCREEN_RESOURCE_HOSTS_INFO:
397						case SCREEN_RESOURCE_TRIGGERS_INFO:
398						case SCREEN_RESOURCE_TRIGGERS_OVERVIEW:
399						case SCREEN_RESOURCE_DATA_OVERVIEW:
400						case SCREEN_RESOURCE_HOSTGROUP_TRIGGERS:
401							$groupsRefs[$resource['name']] = $resource['name'];
402							break;
403
404						case SCREEN_RESOURCE_HOST_TRIGGERS:
405							$hostsRefs[$resource['host']] = $resource['host'];
406							break;
407
408						case SCREEN_RESOURCE_GRAPH:
409						case SCREEN_RESOURCE_LLD_GRAPH:
410							$hostsRefs[$resource['host']] = $resource['host'];
411							$graphsRefs[$resource['host']][$resource['name']] = $resource['name'];
412							break;
413
414						case SCREEN_RESOURCE_CLOCK:
415							if ($screenItem['style'] != TIME_TYPE_HOST) {
416								break;
417							}
418							// break; is not missing here
419
420						case SCREEN_RESOURCE_SIMPLE_GRAPH:
421						case SCREEN_RESOURCE_LLD_SIMPLE_GRAPH:
422						case SCREEN_RESOURCE_PLAIN_TEXT:
423							$hostsRefs[$resource['host']] = $resource['host'];
424							$itemsRefs[$resource['host']][$resource['key']] = $resource['key'];
425							break;
426
427						case SCREEN_RESOURCE_MAP:
428							$mapsRefs[$resource['name']] = $resource['name'];
429							break;
430
431						case SCREEN_RESOURCE_SCREEN:
432							$screensRefs[$resource['name']] = $resource['name'];
433							break;
434					}
435				}
436			}
437		}
438
439		foreach ($this->getFormattedTemplateScreens() as $screens) {
440			foreach ($screens as $screen) {
441				$templateScreensRefs[$screen['name']] = $screen['name'];
442
443				if (!empty($screen['screenitems'])) {
444					foreach ($screen['screenitems'] as $screenItem) {
445						$resource = $screenItem['resource'];
446
447						switch ($screenItem['resourcetype']) {
448							case SCREEN_RESOURCE_GRAPH:
449							case SCREEN_RESOURCE_LLD_GRAPH:
450								$hostsRefs[$resource['host']] = $resource['host'];
451								$graphsRefs[$resource['host']][$resource['name']] = $resource['name'];
452								break;
453
454							case SCREEN_RESOURCE_CLOCK:
455								if ($screenItem['style'] != TIME_TYPE_HOST) {
456									break;
457								}
458								// break; is not missing here
459
460							case SCREEN_RESOURCE_SIMPLE_GRAPH:
461							case SCREEN_RESOURCE_LLD_SIMPLE_GRAPH:
462							case SCREEN_RESOURCE_PLAIN_TEXT:
463								$hostsRefs[$resource['host']] = $resource['host'];
464								$itemsRefs[$resource['host']][$resource['key']] = $resource['key'];
465								break;
466						}
467					}
468				}
469			}
470		}
471
472		$this->referencer->addGroups($groupsRefs);
473		$this->referencer->addTemplates($templatesRefs);
474		$this->referencer->addHosts($hostsRefs);
475		$this->referencer->addApplications($applicationsRefs);
476		$this->referencer->addItems($itemsRefs);
477		$this->referencer->addValueMaps($valueMapsRefs);
478		$this->referencer->addTriggers($triggersRefs);
479		$this->referencer->addGraphs($graphsRefs);
480		$this->referencer->addIconMaps($iconMapsRefs);
481		$this->referencer->addMaps($mapsRefs);
482		$this->referencer->addScreens($screensRefs);
483		$this->referencer->addTemplateScreens($templateScreensRefs);
484		$this->referencer->addMacros($macrosRefs);
485		$this->referencer->addProxies($proxyRefs);
486		$this->referencer->addHostPrototypes($hostPrototypesRefs);
487	}
488
489	/**
490	 * Import groups.
491	 *
492	 * @return null
493	 */
494	protected function processGroups() {
495		if (!$this->options['groups']['createMissing']) {
496			return;
497		}
498
499		$groups = $this->getFormattedGroups();
500
501		if (empty($groups)) {
502			return;
503		}
504
505		// skip the groups that already exist
506		foreach ($groups as $gnum => $group) {
507			if ($this->referencer->resolveGroup($group['name'])) {
508				unset($groups[$gnum]);
509			}
510		}
511
512		if ($groups) {
513			// reset indexing because ids from api does not preserve input array keys
514			$groups = array_values($groups);
515			$newGroups = API::HostGroup()->create($groups);
516
517			foreach ($newGroups['groupids'] as $gnum => $groupid) {
518				$this->referencer->addGroupRef($groups[$gnum]['name'], $groupid);
519			}
520		}
521	}
522
523	/**
524	 * Import templates.
525	 *
526	 * @throws Exception
527	 */
528	protected function processTemplates() {
529		if ($this->options['templates']['updateExisting'] || $this->options['templates']['createMissing']) {
530			$templates = $this->getFormattedTemplates();
531			if ($templates) {
532				$templateImporter = new CTemplateImporter($this->options, $this->referencer,
533					$this->importedObjectContainer
534				);
535				$templateImporter->import($templates);
536
537				// get list of imported template IDs and add them processed template ID list
538				$templateIds = $templateImporter->getProcessedTemplateIds();
539				$this->importedObjectContainer->addTemplateIds($templateIds);
540			}
541		}
542	}
543
544	/**
545	 * Import hosts.
546	 *
547	 * @throws Exception
548	 */
549	protected function processHosts() {
550		if ($this->options['hosts']['updateExisting'] || $this->options['hosts']['createMissing']) {
551			$hosts = $this->getFormattedHosts();
552			if ($hosts) {
553				$hostImporter = new CHostImporter($this->options, $this->referencer, $this->importedObjectContainer);
554				$hostImporter->import($hosts);
555
556				// get list of imported host IDs and add them processed host ID list
557				$hostIds = $hostImporter->getProcessedHostIds();
558				$this->importedObjectContainer->addHostIds($hostIds);
559			}
560		}
561	}
562
563	/**
564	 * Import applications.
565	 */
566	protected function processApplications() {
567		if (!$this->options['applications']['createMissing']) {
568			return;
569		}
570
571		$allApplications = $this->getFormattedApplications();
572
573		if (!$allApplications) {
574			return;
575		}
576
577		$applicationsToCreate = [];
578
579		foreach ($allApplications as $host => $applications) {
580			$hostId = $this->referencer->resolveHostOrTemplate($host);
581
582			if (!$this->importedObjectContainer->isHostProcessed($hostId)
583					&& !$this->importedObjectContainer->isTemplateProcessed($hostId)) {
584				continue;
585			}
586
587			foreach ($applications as $application) {
588				$application['hostid'] = $hostId;
589				$appId = $this->referencer->resolveApplication($hostId, $application['name']);
590
591				if (!$appId) {
592					$applicationsToCreate[] = $application;
593				}
594			}
595		}
596
597		if ($applicationsToCreate) {
598			API::Application()->create($applicationsToCreate);
599		}
600
601		// refresh applications because templated ones can be inherited to host and used in items
602		$this->referencer->refreshApplications();
603	}
604
605	/**
606	 * Import value maps.
607	 */
608	protected function processValueMaps() {
609		if (!$this->options['valueMaps']['createMissing'] && !$this->options['valueMaps']['updateExisting']) {
610			return;
611		}
612
613		$all_valuemaps = $this->getFormattedValueMaps();
614
615		if (!$all_valuemaps) {
616			return;
617		}
618
619		$valuemaps_to_create = [];
620		$valuemaps_to_update = [];
621
622		foreach ($all_valuemaps as $valuemap) {
623			$valuemapid = $this->referencer->resolveValueMap($valuemap['name']);
624
625			if ($valuemapid) {
626				$valuemap['valuemapid'] = $valuemapid;
627				$valuemaps_to_update[] = $valuemap;
628			}
629			else {
630				$valuemaps_to_create[] = $valuemap;
631			}
632		}
633
634		if ($this->options['valueMaps']['createMissing'] && $valuemaps_to_create) {
635			$valuemapids = API::ValueMap()->create($valuemaps_to_create);
636
637			foreach ($valuemaps_to_create as $key => $valuemap) {
638				$this->referencer->addValueMapRef($valuemap['name'], $valuemapids['valuemapids'][$key]);
639			}
640		}
641
642		if ($this->options['valueMaps']['updateExisting'] && $valuemaps_to_update) {
643			API::ValueMap()->update($valuemaps_to_update);
644		}
645	}
646
647	/**
648	 * Import items.
649	 */
650	protected function processItems() {
651		if (!$this->options['items']['createMissing'] && !$this->options['items']['updateExisting']) {
652			return;
653		}
654
655		$allItems = $this->getFormattedItems();
656
657		if (!$allItems) {
658			return;
659		}
660
661		$itemsToCreate = [];
662		$itemsToUpdate = [];
663
664		foreach ($allItems as $host => $items) {
665			$hostId = $this->referencer->resolveHostOrTemplate($host);
666
667			if (!$this->importedObjectContainer->isHostProcessed($hostId)
668					&& !$this->importedObjectContainer->isTemplateProcessed($hostId)) {
669				continue;
670			}
671
672			foreach ($items as $item) {
673				$item['hostid'] = $hostId;
674
675				if (isset($item['applications']) && $item['applications']) {
676					$applicationsIds = [];
677
678					foreach ($item['applications'] as $application) {
679						if ($applicationId = $this->referencer->resolveApplication($hostId, $application['name'])) {
680							$applicationsIds[] = $applicationId;
681						}
682						else {
683							throw new Exception(_s('Item "%1$s" on "%2$s": application "%3$s" does not exist.',
684								$item['name'], $host, $application['name']));
685						}
686					}
687
688					$item['applications'] = $applicationsIds;
689				}
690
691				if (array_key_exists('interface_ref', $item) && $item['interface_ref']) {
692					$item['interfaceid'] = $this->referencer->interfacesCache[$hostId][$item['interface_ref']];
693				}
694
695				if (isset($item['valuemap']) && $item['valuemap']) {
696					$valueMapId = $this->referencer->resolveValueMap($item['valuemap']['name']);
697
698					if (!$valueMapId) {
699						throw new Exception(_s(
700							'Cannot find value map "%1$s" used for item "%2$s" on "%3$s".',
701							$item['valuemap']['name'],
702							$item['name'],
703							$host
704						));
705					}
706
707					$item['valuemapid'] = $valueMapId;
708				}
709
710				$itemsId = $this->referencer->resolveItem($hostId, $item['key_']);
711
712				if ($itemsId) {
713					$item['itemid'] = $itemsId;
714					$itemsToUpdate[] = $item;
715				}
716				else {
717					$itemsToCreate[] = $item;
718				}
719			}
720		}
721
722		// create/update the items and create a hash hostid->key_->itemid
723		if ($this->options['items']['createMissing'] && $itemsToCreate) {
724			API::Item()->create($itemsToCreate);
725		}
726
727		if ($this->options['items']['updateExisting'] && $itemsToUpdate) {
728			API::Item()->update($itemsToUpdate);
729		}
730
731		// refresh items because templated ones can be inherited to host and used in triggers, graphs, etc.
732		$this->referencer->refreshItems();
733	}
734
735	/**
736	 * Import discovery rules.
737	 *
738	 * @throws Exception
739	 */
740	protected function processDiscoveryRules() {
741		if (!$this->options['discoveryRules']['createMissing'] && !$this->options['discoveryRules']['updateExisting']) {
742			return;
743		}
744
745		$allDiscoveryRules = $this->getFormattedDiscoveryRules();
746
747		if (!$allDiscoveryRules) {
748			return;
749		}
750
751		// unset rules that are related to hosts we did not process
752		foreach ($allDiscoveryRules as $host => $discoveryRules) {
753			$hostId = $this->referencer->resolveHostOrTemplate($host);
754
755			if (!$this->importedObjectContainer->isHostProcessed($hostId)
756					&& !$this->importedObjectContainer->isTemplateProcessed($hostId)) {
757				unset($allDiscoveryRules[$host]);
758			}
759		}
760
761		$itemsToCreate = [];
762		$itemsToUpdate = [];
763
764		foreach ($allDiscoveryRules as $host => $discoveryRules) {
765			$hostId = $this->referencer->resolveHostOrTemplate($host);
766
767			foreach ($discoveryRules as $item) {
768				$item['hostid'] = $hostId;
769
770				if (array_key_exists('interface_ref', $item) && $item['interface_ref']) {
771					$item['interfaceid'] = $this->referencer->interfacesCache[$hostId][$item['interface_ref']];
772				}
773
774				unset($item['item_prototypes']);
775				unset($item['trigger_prototypes']);
776				unset($item['graph_prototypes']);
777				unset($item['host_prototypes']);
778
779				$itemId = $this->referencer->resolveItem($hostId, $item['key_']);
780
781				if ($itemId) {
782					$item['itemid'] = $itemId;
783					$itemsToUpdate[] = $item;
784				}
785				else {
786					$itemsToCreate[] = $item;
787				}
788			}
789		}
790
791		// create/update discovery rules and add processed rules to array $processedRules
792		$processedRules = [];
793
794		if ($this->options['discoveryRules']['createMissing'] && $itemsToCreate) {
795			$newItemsIds = API::DiscoveryRule()->create($itemsToCreate);
796
797			foreach ($newItemsIds['itemids'] as $inum => $itemid) {
798				$item = $itemsToCreate[$inum];
799
800				$this->referencer->addItemRef($item['hostid'], $item['key_'], $itemid);
801			}
802
803			foreach ($itemsToCreate as $item) {
804				$processedRules[$item['hostid']][$item['key_']] = 1;
805			}
806		}
807
808		if ($this->options['discoveryRules']['updateExisting'] && $itemsToUpdate) {
809			API::DiscoveryRule()->update($itemsToUpdate);
810
811			foreach ($itemsToUpdate as $item) {
812				$processedRules[$item['hostid']][$item['key_']] = 1;
813			}
814		}
815
816		// refresh discovery rules because templated ones can be inherited to host and used for prototypes
817		$this->referencer->refreshItems();
818
819		// process prototypes
820		$prototypesToUpdate = [];
821		$prototypesToCreate = [];
822		$hostPrototypesToUpdate = [];
823		$hostPrototypesToCreate = [];
824
825		foreach ($allDiscoveryRules as $host => $discoveryRules) {
826			$hostId = $this->referencer->resolveHostOrTemplate($host);
827
828			foreach ($discoveryRules as $item) {
829				// if rule was not processed we should not create/update any of its prototypes
830				if (!isset($processedRules[$hostId][$item['key_']])) {
831					continue;
832				}
833
834				$item['hostid'] = $hostId;
835				$itemId = $this->referencer->resolveItem($hostId, $item['key_']);
836
837				// prototypes
838				foreach ($item['item_prototypes'] as $prototype) {
839					$prototype['hostid'] = $hostId;
840
841					$applicationsIds = [];
842
843					foreach ($prototype['applications'] as $application) {
844						$applicationsIds[] = $this->referencer->resolveApplication($hostId, $application['name']);
845					}
846
847					$prototype['applications'] = $applicationsIds;
848
849					if (array_key_exists('application_prototypes', $prototype)) {
850						$prototype['applicationPrototypes'] = $prototype['application_prototypes'];
851					}
852
853					if (array_key_exists('interface_ref', $prototype) && $prototype['interface_ref']) {
854						$prototype['interfaceid'] = $this->referencer->interfacesCache[$hostId][$prototype['interface_ref']];
855					}
856
857					if ($prototype['valuemap']) {
858						$valueMapId = $this->referencer->resolveValueMap($prototype['valuemap']['name']);
859
860						if (!$valueMapId) {
861							throw new Exception(_s(
862								'Cannot find value map "%1$s" used for item prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
863								$prototype['valuemap']['name'],
864								$prototype['name'],
865								$item['name'],
866								$host
867							));
868						}
869
870						$prototype['valuemapid'] = $valueMapId;
871					}
872
873					$prototypeId = $this->referencer->resolveItem($hostId, $prototype['key_']);
874					$prototype['rule'] = ['hostid' => $hostId, 'key' => $item['key_']];
875
876					if ($prototypeId) {
877						$prototype['itemid'] = $prototypeId;
878						$prototypesToUpdate[] = $prototype;
879					}
880					else {
881						$prototypesToCreate[] = $prototype;
882					}
883				}
884
885				// host prototype
886				foreach ($item['host_prototypes'] as $hostPrototype) {
887					// resolve group prototypes
888					$groupLinks = [];
889
890					foreach ($hostPrototype['group_links'] as $groupLink) {
891						$groupId = $this->referencer->resolveGroup($groupLink['group']['name']);
892
893						if (!$groupId) {
894							throw new Exception(_s(
895								'Cannot find host group "%1$s" for host prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
896								$groupLink['group']['name'],
897								$hostPrototype['name'],
898								$item['name'],
899								$host
900							));
901						}
902
903						$groupLinks[] = ['groupid' => $groupId];
904					}
905
906					$hostPrototype['groupLinks'] = $groupLinks;
907					$hostPrototype['groupPrototypes'] = $hostPrototype['group_prototypes'];
908					unset($hostPrototype['group_links'], $hostPrototype['group_prototypes']);
909
910					// resolve templates
911					$templates = [];
912
913					foreach ($hostPrototype['templates'] as $template) {
914						$templateId = $this->referencer->resolveTemplate($template['name']);
915
916						if (!$templateId) {
917							throw new Exception(_s(
918								'Cannot find template "%1$s" for host prototype "%2$s" of discovery rule "%3$s" on "%4$s".',
919								$template['name'],
920								$hostPrototype['name'],
921								$item['name'],
922								$host
923							));
924						}
925
926						$templates[] = ['templateid' => $templateId];
927					}
928
929					$hostPrototype['templates'] = $templates;
930
931					$hostPrototypeId = $this->referencer->resolveHostPrototype($hostId, $itemId, $hostPrototype['host']);
932
933					if ($hostPrototypeId) {
934						$hostPrototype['hostid'] = $hostPrototypeId;
935						$hostPrototypesToUpdate[] = $hostPrototype;
936					}
937					else {
938						$hostPrototype['ruleid'] = $itemId;
939						$hostPrototypesToCreate[] = $hostPrototype;
940					}
941				}
942
943				if (array_key_exists('interface_ref', $item) && $item['interface_ref']) {
944					$item['interfaceid'] = $this->referencer->interfacesCache[$hostId][$item['interface_ref']];
945				}
946				unset($item['item_prototypes']);
947				unset($item['trigger_prototypes']);
948				unset($item['graph_prototypes']);
949				unset($item['host_prototypes']);
950
951				$itemsId = $this->referencer->resolveItem($hostId, $item['key_']);
952
953				if ($itemsId) {
954					$item['itemid'] = $itemsId;
955					$itemsToUpdate[] = $item;
956				}
957				else {
958					$itemsToCreate[] = $item;
959				}
960			}
961		}
962
963		if ($prototypesToCreate) {
964			foreach ($prototypesToCreate as &$prototype) {
965				$prototype['ruleid'] = $this->referencer->resolveItem($prototype['rule']['hostid'], $prototype['rule']['key']);
966			}
967			unset($prototype);
968
969			$newPrototypeIds = API::ItemPrototype()->create($prototypesToCreate);
970
971			foreach ($newPrototypeIds['itemids'] as $inum => $itemid) {
972				$item = $prototypesToCreate[$inum];
973				$this->referencer->addItemRef($item['hostid'], $item['key_'], $itemid);
974			}
975		}
976
977		if ($prototypesToUpdate) {
978			foreach ($prototypesToCreate as &$prototype) {
979				$prototype['ruleid'] = $this->referencer->resolveItem($prototype['rule']['hostid'], $prototype['rule']['key']);
980			}
981			unset($prototype);
982
983			API::ItemPrototype()->update($prototypesToUpdate);
984		}
985
986		if ($hostPrototypesToCreate) {
987			API::HostPrototype()->create($hostPrototypesToCreate);
988		}
989		if ($hostPrototypesToUpdate) {
990			API::HostPrototype()->update($hostPrototypesToUpdate);
991		}
992
993		// refresh prototypes because templated ones can be inherited to host and used in triggers prototypes or graph prototypes
994		$this->referencer->refreshItems();
995
996		// first we need to create item prototypes and only then graph prototypes
997		$triggersToCreate = [];
998		$triggersToUpdate = [];
999		$graphsToCreate = [];
1000		$graphsToUpdate = [];
1001		// the list of triggers to process dependencies
1002		$triggers = [];
1003
1004		foreach ($allDiscoveryRules as $host => $discoveryRules) {
1005			$hostId = $this->referencer->resolveHostOrTemplate($host);
1006
1007			foreach ($discoveryRules as $item) {
1008				// if rule was not processed we should not create/update any of its prototypes
1009				if (!isset($processedRules[$hostId][$item['key_']])) {
1010					continue;
1011				}
1012
1013				// trigger prototypes
1014				foreach ($item['trigger_prototypes'] as $trigger) {
1015					// search for existing  items in trigger prototype expressions
1016					foreach ($trigger['parsedExpressions'] as $expression) {
1017						$hostId = $this->referencer->resolveHostOrTemplate($expression['host']);
1018						$itemId = $hostId ? $this->referencer->resolveItem($hostId, $expression['item']) : false;
1019
1020						if (!$itemId) {
1021							throw new Exception(_s(
1022								'Cannot find item "%1$s" on "%2$s" used in trigger prototype "%3$s" of discovery rule "%4$s" on "%5$s".',
1023								$expression['item'],
1024								$expression['host'],
1025								$trigger['description'],
1026								$item['name'],
1027								$host
1028							));
1029						}
1030					}
1031
1032					$triggerId = $this->referencer->resolveTrigger($trigger['description'], $trigger['expression']);
1033
1034					$triggers[] = $trigger;
1035					unset($trigger['dependencies']);
1036
1037					if ($triggerId) {
1038						$trigger['triggerid'] = $triggerId;
1039						$triggersToUpdate[] = $trigger;
1040					}
1041					else {
1042						$triggersToCreate[] = $trigger;
1043					}
1044				}
1045
1046				// graph prototypes
1047				foreach ($item['graph_prototypes'] as $graph) {
1048					if ($graph['ymin_item_1']) {
1049						$hostId = $this->referencer->resolveHostOrTemplate($graph['ymin_item_1']['host']);
1050						$itemId = $hostId
1051							? $this->referencer->resolveItem($hostId, $graph['ymin_item_1']['key'])
1052							: false;
1053
1054						if (!$itemId) {
1055							throw new Exception(_s(
1056								'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".',
1057								$graph['ymin_item_1']['key'],
1058								$graph['ymin_item_1']['host'],
1059								$graph['name'],
1060								$item['name'],
1061								$host
1062							));
1063						}
1064
1065						$graph['ymin_itemid'] = $itemId;
1066					}
1067
1068					if ($graph['ymax_item_1']) {
1069						$hostId = $this->referencer->resolveHostOrTemplate($graph['ymax_item_1']['host']);
1070						$itemId = $hostId
1071							? $this->referencer->resolveItem($hostId, $graph['ymax_item_1']['key'])
1072							: false;
1073
1074						if (!$itemId) {
1075							throw new Exception(_s(
1076								'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".',
1077								$graph['ymax_item_1']['key'],
1078								$graph['ymax_item_1']['host'],
1079								$graph['name'],
1080								$item['name'],
1081								$host
1082							));
1083						}
1084
1085						$graph['ymax_itemid'] = $itemId;
1086					}
1087
1088					foreach ($graph['gitems'] as &$gitem) {
1089						$gitemHostId = $this->referencer->resolveHostOrTemplate($gitem['item']['host']);
1090						$gitem['itemid'] = $gitemHostId
1091							? $this->referencer->resolveItem($gitemHostId, $gitem['item']['key'])
1092							: false;
1093
1094						if (!$gitem['itemid']) {
1095							throw new Exception(_s(
1096								'Cannot find item "%1$s" on "%2$s" used in graph prototype "%3$s" of discovery rule "%4$s" on "%5$s".',
1097								$gitem['item']['key'],
1098								$gitem['item']['host'],
1099								$graph['name'],
1100								$item['name'],
1101								$host
1102							));
1103						}
1104					}
1105					unset($gitem);
1106
1107					$graphId = $this->referencer->resolveGraph($gitemHostId, $graph['name']);
1108					if ($graphId) {
1109						$graph['graphid'] = $graphId;
1110						$graphsToUpdate[] = $graph;
1111					}
1112					else {
1113						$graphsToCreate[] = $graph;
1114					}
1115				}
1116			}
1117		}
1118
1119		if ($triggersToCreate) {
1120			$result = API::TriggerPrototype()->create($triggersToCreate);
1121
1122			foreach ($result['triggerids'] as $tnum => $triggerid) {
1123				$trigger = $triggersToCreate[$tnum];
1124				$this->referencer->addTriggerRef($trigger['description'], $trigger['expression'], $triggerid);
1125			}
1126		}
1127		if ($triggersToUpdate) {
1128			API::TriggerPrototype()->update($triggersToUpdate);
1129		}
1130		if ($graphsToCreate) {
1131			API::GraphPrototype()->create($graphsToCreate);
1132			$this->referencer->refreshGraphs();
1133		}
1134		if ($graphsToUpdate) {
1135			API::GraphPrototype()->update($graphsToUpdate);
1136		}
1137
1138		$this->processTriggerPrototypeDependencies($triggers);
1139	}
1140
1141	/**
1142	 * Update trigger dependencies
1143	 *
1144	 * @param array $triggers
1145	 *
1146	 * @throws Exception
1147	 */
1148	protected function processTriggerPrototypeDependencies(array $triggers) {
1149		$dependencies = [];
1150
1151		foreach ($triggers as $trigger) {
1152			if (!array_key_exists('dependencies', $trigger)) {
1153				continue;
1154			}
1155
1156			$deps = [];
1157			$triggerid = $this->referencer->resolveTrigger($trigger['description'], $trigger['expression']);
1158
1159			foreach ($trigger['dependencies'] as $dependency) {
1160				$dep_triggerid = $this->referencer->resolveTrigger($dependency['name'], $dependency['expression']);
1161
1162				if (!$dep_triggerid) {
1163					throw new Exception(_s('Trigger prototype "%1$s" depends on trigger "%2$s", which does not exist.',
1164						$trigger['description'],
1165						$dependency['name']
1166					));
1167				}
1168
1169				$deps[] = ['triggerid' => $dep_triggerid];
1170			}
1171
1172			$dependencies[] = [
1173				'triggerid' => $triggerid,
1174				'dependencies' => $deps
1175			];
1176		}
1177
1178		if ($dependencies) {
1179			API::TriggerPrototype()->update($dependencies);
1180		}
1181	}
1182
1183	/**
1184	 * Import graphs.
1185	 *
1186	 * @throws Exception
1187	 */
1188	protected function processGraphs() {
1189		if (!$this->options['graphs']['createMissing'] && !$this->options['graphs']['updateExisting']) {
1190			return;
1191		}
1192
1193		$allGraphs = $this->getFormattedGraphs();
1194
1195		if (!$allGraphs) {
1196			return;
1197		}
1198
1199		$graphsToCreate = [];
1200		$graphsToUpdate = [];
1201
1202		foreach ($allGraphs as $graph) {
1203			if ($graph['ymin_item_1']) {
1204				$hostId = $this->referencer->resolveHostOrTemplate($graph['ymin_item_1']['host']);
1205				$itemId = $hostId ? $this->referencer->resolveItem($hostId, $graph['ymin_item_1']['key']) : false;
1206
1207				if (!$itemId) {
1208					throw new Exception(_s(
1209						'Cannot find item "%1$s" on "%2$s" used as the Y axis MIN value for graph "%3$s".',
1210						$graph['ymin_item_1']['key'],
1211						$graph['ymin_item_1']['host'],
1212						$graph['name']
1213					));
1214				}
1215
1216				$graph['ymin_itemid'] = $itemId;
1217			}
1218
1219			if ($graph['ymax_item_1']) {
1220				$hostId = $this->referencer->resolveHostOrTemplate($graph['ymax_item_1']['host']);
1221				$itemId = $hostId ? $this->referencer->resolveItem($hostId, $graph['ymax_item_1']['key']) : false;
1222
1223				if (!$itemId) {
1224					throw new Exception(_s(
1225						'Cannot find item "%1$s" on "%2$s" used as the Y axis MAX value for graph "%3$s".',
1226						$graph['ymax_item_1']['key'],
1227						$graph['ymax_item_1']['host'],
1228						$graph['name']
1229					));
1230				}
1231
1232				$graph['ymax_itemid'] = $itemId;
1233			}
1234
1235			if (isset($graph['gitems']) && $graph['gitems']) {
1236				foreach ($graph['gitems'] as &$gitem) {
1237					$gitemHostId = $this->referencer->resolveHostOrTemplate($gitem['item']['host']);
1238					$gitem['itemid'] = $gitemHostId
1239						? $this->referencer->resolveItem($gitemHostId, $gitem['item']['key'])
1240						: false;
1241
1242					if (!$gitem['itemid']) {
1243						throw new Exception(_s(
1244							'Cannot find item "%1$s" on "%2$s" used in graph "%3$s".',
1245							$gitem['item']['key'],
1246							$gitem['item']['host'],
1247							$graph['name']
1248						));
1249					}
1250				}
1251				unset($gitem);
1252
1253				$graphId = $this->referencer->resolveGraph($gitemHostId, $graph['name']);
1254
1255				if ($graphId) {
1256					$graph['graphid'] = $graphId;
1257					$graphsToUpdate[] = $graph;
1258				}
1259				else {
1260					$graphsToCreate[] = $graph;
1261				}
1262			}
1263		}
1264
1265		if ($this->options['graphs']['createMissing'] && $graphsToCreate) {
1266			API::Graph()->create($graphsToCreate);
1267		}
1268		if ($this->options['graphs']['updateExisting'] && $graphsToUpdate) {
1269			API::Graph()->update($graphsToUpdate);
1270		}
1271
1272		$this->referencer->refreshGraphs();
1273	}
1274
1275	/**
1276	 * Import triggers.
1277	 */
1278	protected function processTriggers() {
1279		if (!$this->options['triggers']['createMissing'] && !$this->options['triggers']['updateExisting']) {
1280			return;
1281		}
1282
1283		$allTriggers = $this->getFormattedTriggers();
1284
1285		if (!$allTriggers) {
1286			return;
1287		}
1288
1289		$triggersToCreate = [];
1290		$triggersToUpdate = [];
1291		// the list of triggers to process dependencies
1292		$triggers = [];
1293
1294		foreach ($allTriggers as $trigger) {
1295			// search for existing items in trigger expressions
1296			foreach ($trigger['parsedExpressions'] as $expression) {
1297				$hostId = $this->referencer->resolveHostOrTemplate($expression['host']);
1298				$itemId = $hostId ? $this->referencer->resolveItem($hostId, $expression['item']) : false;
1299
1300				if (!$itemId) {
1301					throw new Exception(_s('Cannot find item "%1$s" on "%2$s" used in trigger "%3$s".',
1302						$expression['item'],
1303						$expression['host'],
1304						$trigger['description']
1305					));
1306				}
1307			}
1308
1309			$triggerId = $this->referencer->resolveTrigger($trigger['description'], $trigger['expression']);
1310
1311			if ($triggerId) {
1312				if ($this->options['triggers']['updateExisting']) {
1313					$triggers[] = $trigger;
1314
1315					$trigger['triggerid'] = $triggerId;
1316					unset($trigger['dependencies']);
1317					$triggersToUpdate[] = $trigger;
1318				}
1319			}
1320			else {
1321				if ($this->options['triggers']['createMissing']) {
1322					$triggers[] = $trigger;
1323
1324					unset($trigger['dependencies']);
1325					$triggersToCreate[] = $trigger;
1326				}
1327			}
1328		}
1329
1330		if ($triggersToCreate) {
1331			$result = API::Trigger()->create($triggersToCreate);
1332
1333			foreach ($result['triggerids'] as $tnum => $triggerid) {
1334				$trigger = $triggersToCreate[$tnum];
1335				$this->referencer->addTriggerRef($trigger['description'], $trigger['expression'], $triggerid);
1336			}
1337		}
1338
1339		if ($triggersToUpdate) {
1340			API::Trigger()->update($triggersToUpdate);
1341		}
1342
1343		// refresh triggers because template triggers can be inherited to host and used in maps
1344		$this->referencer->refreshTriggers();
1345
1346		$this->processTriggerDependencies($triggers);
1347	}
1348
1349	/**
1350	 * Update trigger dependencies
1351	 *
1352	 * @param array $triggers
1353	 *
1354	 * @throws Exception
1355	 */
1356	protected function processTriggerDependencies(array $triggers) {
1357		$dependencies = [];
1358
1359		foreach ($triggers as $trigger) {
1360			if (!array_key_exists('dependencies', $trigger)) {
1361				continue;
1362			}
1363
1364			$deps = [];
1365			$triggerid = $this->referencer->resolveTrigger($trigger['description'], $trigger['expression']);
1366
1367			foreach ($trigger['dependencies'] as $dependency) {
1368				$dep_triggerid = $this->referencer->resolveTrigger($dependency['name'], $dependency['expression']);
1369
1370				if (!$dep_triggerid) {
1371					throw new Exception(_s('Trigger "%1$s" depends on trigger "%2$s", which does not exist.',
1372						$trigger['description'],
1373						$dependency['name']
1374					));
1375				}
1376
1377				$deps[] = ['triggerid' => $dep_triggerid];
1378			}
1379
1380			$dependencies[] = [
1381				'triggerid' => $triggerid,
1382				'dependencies' => $deps
1383			];
1384		}
1385
1386		if ($dependencies) {
1387			API::Trigger()->update($dependencies);
1388		}
1389	}
1390
1391	/**
1392	 * Import images.
1393	 *
1394	 * @throws Exception
1395	 *
1396	 * @return null
1397	 */
1398	protected function processImages() {
1399		if (!$this->options['images']['updateExisting'] && !$this->options['images']['createMissing']) {
1400			return;
1401		}
1402
1403		$allImages = $this->getFormattedImages();
1404
1405		if (!$allImages) {
1406			return;
1407		}
1408
1409		$allImages = zbx_toHash($allImages, 'name');
1410
1411		$dbImages = API::Image()->get([
1412			'output' => ['imageid', 'name'],
1413			'filter' => ['name' => array_keys($allImages)]
1414		]);
1415		$dbImages = zbx_toHash($dbImages, 'name');
1416
1417		$imagesToUpdate = [];
1418		$imagesToCreate = [];
1419
1420		foreach ($allImages as $imageName => $image) {
1421			if (isset($dbImages[$imageName])) {
1422				$image['imageid'] = $dbImages[$imageName]['imageid'];
1423				unset($image['imagetype']);
1424				$imagesToUpdate[] = $image;
1425			}
1426			else {
1427				$imagesToCreate[] = $image;
1428			}
1429		}
1430
1431		if ($this->options['images']['createMissing'] && $imagesToCreate) {
1432			API::Image()->create($imagesToCreate);
1433		}
1434
1435		if ($this->options['images']['updateExisting'] && $imagesToUpdate) {
1436			API::Image()->update($imagesToUpdate);
1437		}
1438	}
1439
1440	/**
1441	 * Import maps.
1442	 */
1443	protected function processMaps() {
1444		if ($this->options['maps']['updateExisting'] || $this->options['maps']['createMissing']) {
1445			$maps = $this->getFormattedMaps();
1446			if ($maps) {
1447				$mapImporter = new CMapImporter($this->options, $this->referencer, $this->importedObjectContainer);
1448				$mapImporter->import($maps);
1449			}
1450		}
1451	}
1452
1453	/**
1454	 * Import screens.
1455	 */
1456	protected function processScreens() {
1457		if ($this->options['screens']['updateExisting'] || $this->options['screens']['createMissing']) {
1458			$screens = $this->getFormattedScreens();
1459			if ($screens) {
1460				$screenImporter = new CScreenImporter($this->options, $this->referencer,
1461					$this->importedObjectContainer
1462				);
1463				$screenImporter->import($screens);
1464			}
1465		}
1466	}
1467
1468	/**
1469	 * Import template screens.
1470	 */
1471	protected function processTemplateScreens() {
1472		if ($this->options['templateScreens']['updateExisting']
1473				|| $this->options['templateScreens']['createMissing']
1474				|| $this->options['templateScreens']['deleteMissing']) {
1475			$screens = $this->getFormattedTemplateScreens();
1476			$screenImporter = new CTemplateScreenImporter($this->options, $this->referencer,
1477				$this->importedObjectContainer
1478			);
1479			$screenImporter->delete($screens);
1480			$screenImporter->import($screens);
1481		}
1482	}
1483
1484	/**
1485	 * Deletes items from DB that are missing in XML.
1486	 *
1487	 * @return null
1488	 */
1489	protected function deleteMissingItems() {
1490		if (!$this->options['items']['deleteMissing']) {
1491			return;
1492		}
1493
1494		$processedHostIds = $this->importedObjectContainer->getHostIds();
1495		$processedTemplateIds = $this->importedObjectContainer->getTemplateIds();
1496
1497		$processedHostIds = array_merge($processedHostIds, $processedTemplateIds);
1498
1499		// no hosts or templates have been processed
1500		if (!$processedHostIds) {
1501			return;
1502		}
1503
1504		$itemIdsXML = [];
1505
1506		$allItems = $this->getFormattedItems();
1507
1508		if ($allItems) {
1509			foreach ($allItems as $host => $items) {
1510				$hostId = $this->referencer->resolveHostOrTemplate($host);
1511
1512				foreach ($items as $item) {
1513					$itemId = $this->referencer->resolveItem($hostId, $item['key_']);
1514
1515					if ($itemId) {
1516						$itemIdsXML[$itemId] = $itemId;
1517					}
1518				}
1519			}
1520		}
1521
1522		$dbItemIds = API::Item()->get([
1523			'output' => ['itemid'],
1524			'hostids' => $processedHostIds,
1525			'preservekeys' => true,
1526			'nopermissions' => true,
1527			'inherited' => false,
1528			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
1529		]);
1530
1531		$itemsToDelete = array_diff_key($dbItemIds, $itemIdsXML);
1532
1533		if ($itemsToDelete) {
1534			API::Item()->delete(array_keys($itemsToDelete));
1535		}
1536
1537		$this->referencer->refreshItems();
1538	}
1539
1540	/**
1541	 * Deletes applications from DB that are missing in XML.
1542	 *
1543	 * @return null
1544	 */
1545	protected function deleteMissingApplications() {
1546		if (!$this->options['applications']['deleteMissing']) {
1547			return;
1548		}
1549
1550		$processedHostIds = $this->importedObjectContainer->getHostIds();
1551		$processedTemplateIds = $this->importedObjectContainer->getTemplateIds();
1552
1553		$processedHostIds = array_merge($processedHostIds, $processedTemplateIds);
1554
1555		// no hosts or templates have been processed
1556		if (!$processedHostIds) {
1557			return;
1558		}
1559
1560		$applicationIdsXML = [];
1561
1562		$allApplications = $this->getFormattedApplications();
1563
1564		if ($allApplications) {
1565			foreach ($allApplications as $host => $applications) {
1566				$hostId = $this->referencer->resolveHostOrTemplate($host);
1567
1568				foreach ($applications as $application) {
1569					$applicationId = $this->referencer->resolveApplication($hostId, $application['name']);
1570
1571					if ($applicationId) {
1572						$applicationIdsXML[$applicationId] = $applicationId;
1573					}
1574				}
1575			}
1576		}
1577
1578		$dbApplicationIds = API::Application()->get([
1579			'output' => ['applicationid'],
1580			'hostids' => $processedHostIds,
1581			'preservekeys' => true,
1582			'nopermissions' => true,
1583			'inherited' => false,
1584			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
1585		]);
1586
1587		$applicationsToDelete = array_diff_key($dbApplicationIds, $applicationIdsXML);
1588		if ($applicationsToDelete) {
1589			API::Application()->delete(array_keys($applicationsToDelete));
1590		}
1591
1592		// refresh applications because templated ones can be inherited to host and used in items
1593		$this->referencer->refreshApplications();
1594	}
1595
1596	/**
1597	 * Deletes triggers from DB that are missing in XML.
1598	 *
1599	 * @return null
1600	 */
1601	protected function deleteMissingTriggers() {
1602		if (!$this->options['triggers']['deleteMissing']) {
1603			return;
1604		}
1605
1606		$processedHostIds = $this->importedObjectContainer->getHostIds();
1607		$processedTemplateIds = $this->importedObjectContainer->getTemplateIds();
1608
1609		$processedHostIds = array_merge($processedHostIds, $processedTemplateIds);
1610
1611		// no hosts or templates have been processed
1612		if (!$processedHostIds) {
1613			return;
1614		}
1615
1616		$triggersXML = [];
1617
1618		$allTriggers = $this->getFormattedTriggers();
1619
1620		if ($allTriggers) {
1621			foreach ($allTriggers as $trigger) {
1622				$triggerId = $this->referencer->resolveTrigger($trigger['description'], $trigger['expression']);
1623
1624				if ($triggerId) {
1625					$triggersXML[$triggerId] = $triggerId;
1626				}
1627			}
1628		}
1629
1630		$dbTriggerIds = API::Trigger()->get([
1631			'output' => ['triggerid'],
1632			'hostids' => $processedHostIds,
1633			'selectHosts' => ['hostid'],
1634			'preservekeys' => true,
1635			'nopermissions' => true,
1636			'inherited' => false,
1637			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
1638		]);
1639
1640		// check that potentially deletable trigger belongs to same hosts that are in XML
1641		// if some triggers belong to more hosts than current XML contains, don't delete them
1642		$triggersToDelete = array_diff_key($dbTriggerIds, $triggersXML);
1643		$triggerIdsToDelete = [];
1644		$processedHostIds = array_flip($processedHostIds);
1645
1646		foreach ($triggersToDelete as $triggerId => $trigger) {
1647			$triggerHostIds = array_flip(zbx_objectValues($trigger['hosts'], 'hostid'));
1648			if (!array_diff_key($triggerHostIds, $processedHostIds)) {
1649				$triggerIdsToDelete[] = $triggerId;
1650			}
1651		}
1652
1653		if ($triggerIdsToDelete) {
1654			API::Trigger()->delete($triggerIdsToDelete);
1655		}
1656
1657		// refresh triggers because template triggers can be inherited to host and used in maps
1658		$this->referencer->refreshTriggers();
1659	}
1660
1661	/**
1662	 * Deletes graphs from DB that are missing in XML.
1663	 *
1664	 * @return null
1665	 */
1666	protected function deleteMissingGraphs() {
1667		if (!$this->options['graphs']['deleteMissing']) {
1668			return;
1669		}
1670
1671		$processedHostIds = $this->importedObjectContainer->getHostIds();
1672		$processedTemplateIds = $this->importedObjectContainer->getTemplateIds();
1673
1674		$processedHostIds = array_merge($processedHostIds, $processedTemplateIds);
1675
1676		// no hosts or templates have been processed
1677		if (!$processedHostIds) {
1678			return;
1679		}
1680
1681		$graphsIdsXML = [];
1682
1683		// gather host IDs for graphs that exist in XML
1684		$allGraphs = $this->getFormattedGraphs();
1685
1686		if ($allGraphs) {
1687			foreach ($allGraphs as $graph) {
1688				if (isset($graph['gitems']) && $graph['gitems']) {
1689					foreach ($graph['gitems'] as $gitem) {
1690						$gitemHostId = $this->referencer->resolveHostOrTemplate($gitem['item']['host']);
1691						$graphId = $this->referencer->resolveGraph($gitemHostId, $graph['name']);
1692
1693						if ($graphId) {
1694							$graphsIdsXML[$graphId] = $graphId;
1695						}
1696					}
1697				}
1698			}
1699		}
1700
1701		$dbGraphIds = API::Graph()->get([
1702			'output' => ['graphid'],
1703			'hostids' => $processedHostIds,
1704			'selectHosts' => ['hostid'],
1705			'preservekeys' => true,
1706			'nopermissions' => true,
1707			'inherited' => false,
1708			'filter' => ['flags' => ZBX_FLAG_DISCOVERY_NORMAL]
1709		]);
1710
1711		// check that potentially deletable graph belongs to same hosts that are in XML
1712		// if some graphs belong to more hosts than current XML contains, don't delete them
1713		$graphsToDelete = array_diff_key($dbGraphIds, $graphsIdsXML);
1714		$graphIdsToDelete = [];
1715		$processedHostIds = array_flip($processedHostIds);
1716
1717		foreach ($graphsToDelete as $graphId => $graph) {
1718			$graphHostIds = array_flip(zbx_objectValues($graph['hosts'], 'hostid'));
1719
1720			if (!array_diff_key($graphHostIds, $processedHostIds)) {
1721				$graphIdsToDelete[] = $graphId;
1722			}
1723		}
1724
1725		if ($graphIdsToDelete) {
1726			API::Graph()->delete($graphIdsToDelete);
1727		}
1728
1729		$this->referencer->refreshGraphs();
1730	}
1731
1732	/**
1733	 * Deletes discovery rules and prototypes from DB that are missing in XML.
1734	 *
1735	 * @return null
1736	 */
1737	protected function deleteMissingDiscoveryRules() {
1738		if (!$this->options['discoveryRules']['deleteMissing']) {
1739			return;
1740		}
1741
1742		$processedHostIds = $this->importedObjectContainer->getHostIds();
1743		$processedTemplateIds = $this->importedObjectContainer->getTemplateIds();
1744
1745		$processedHostIds = array_merge($processedHostIds, $processedTemplateIds);
1746
1747		// no hosts or templates have been processed
1748		if (!$processedHostIds) {
1749			return;
1750		}
1751
1752		$discoveryRuleIdsXML = [];
1753
1754		$allDiscoveryRules = $this->getFormattedDiscoveryRules();
1755
1756		if ($allDiscoveryRules) {
1757			foreach ($allDiscoveryRules as $host => $discoveryRules) {
1758				$hostId = $this->referencer->resolveHostOrTemplate($host);
1759
1760				foreach ($discoveryRules as $discoveryRule) {
1761					$discoveryRuleId = $this->referencer->resolveItem($hostId, $discoveryRule['key_']);
1762
1763					if ($discoveryRuleId) {
1764						$discoveryRuleIdsXML[$discoveryRuleId] = $discoveryRuleId;
1765					}
1766				}
1767			}
1768		}
1769
1770		$dbDiscoveryRuleIds = API::DiscoveryRule()->get([
1771			'output' => ['itemid'],
1772			'hostids' => $processedHostIds,
1773			'preservekeys' => true,
1774			'nopermissions' => true,
1775			'inherited' => false
1776		]);
1777
1778		$discoveryRulesToDelete = array_diff_key($dbDiscoveryRuleIds, $discoveryRuleIdsXML);
1779
1780		if ($discoveryRulesToDelete) {
1781			API::DiscoveryRule()->delete(array_keys($discoveryRulesToDelete));
1782		}
1783
1784		// refresh discovery rules because templated ones can be inherited to host and used for prototypes
1785		$this->referencer->refreshItems();
1786
1787		$hostPrototypeIdsXML = [];
1788		$triggerPrototypeIdsXML = [];
1789		$itemPrototypeIdsXML = [];
1790		$graphPrototypeIdsXML = [];
1791
1792		foreach ($allDiscoveryRules as $host => $discoveryRules) {
1793			$hostId = $this->referencer->resolveHostOrTemplate($host);
1794
1795			foreach ($discoveryRules as $discoveryRule) {
1796				$discoveryRuleId = $this->referencer->resolveItem($hostId, $discoveryRule['key_']);
1797
1798				if ($discoveryRuleId) {
1799					// gather host prototype IDs to delete
1800					foreach ($discoveryRule['host_prototypes'] as $hostPrototype) {
1801						$hostPrototypeId = $this->referencer->resolveHostPrototype($hostId, $discoveryRuleId,
1802							$hostPrototype['host']
1803						);
1804
1805						if ($hostPrototypeId) {
1806							$hostPrototypeIdsXML[$hostPrototypeId] = $hostPrototypeId;
1807						}
1808					}
1809
1810					// gather trigger prototype IDs to delete
1811					foreach ($discoveryRule['trigger_prototypes'] as $triggerPrototype) {
1812						$triggerPrototypeId = $this->referencer->resolveTrigger($triggerPrototype['description'],
1813							$triggerPrototype['expression']
1814						);
1815
1816						if ($triggerPrototypeId) {
1817							$triggerPrototypeIdsXML[$triggerPrototypeId] = $triggerPrototypeId;
1818						}
1819					}
1820
1821					// gather graph prototype IDs to delete
1822					foreach ($discoveryRule['graph_prototypes'] as $graphPrototype) {
1823						$graphPrototypeId = $this->referencer->resolveGraph($hostId, $graphPrototype['name']);
1824
1825						if ($graphPrototypeId) {
1826							$graphPrototypeIdsXML[$graphPrototypeId] = $graphPrototypeId;
1827						}
1828					}
1829
1830					// gather item prototype IDs to delete
1831					foreach ($discoveryRule['item_prototypes'] as $itemPrototype) {
1832						$itemPrototypeId = $this->referencer->resolveItem($hostId, $itemPrototype['key_']);
1833
1834						if ($itemPrototypeId) {
1835							$itemPrototypeIdsXML[$itemPrototypeId] = $itemPrototypeId;
1836						}
1837					}
1838				}
1839			}
1840		}
1841
1842		// delete missing host prototypes
1843		$dbHostPrototypeIds = API::HostPrototype()->get([
1844			'output' => ['hostid'],
1845			'discoveryids' => $discoveryRuleIdsXML,
1846			'preservekeys' => true,
1847			'nopermissions' => true,
1848			'inherited' => false
1849		]);
1850
1851		$hostPrototypesToDelete = array_diff_key($dbHostPrototypeIds, $hostPrototypeIdsXML);
1852
1853		if ($hostPrototypesToDelete) {
1854			API::HostPrototype()->delete(array_keys($hostPrototypesToDelete));
1855		}
1856
1857		// delete missing trigger prototypes
1858		$dbTriggerPrototypeIds = API::TriggerPrototype()->get([
1859			'output' => ['triggerid'],
1860			'hostids' => $processedHostIds,
1861			'preservekeys' => true,
1862			'nopermissions' => true,
1863			'inherited' => false
1864		]);
1865
1866		$triggerPrototypesToDelete = array_diff_key($dbTriggerPrototypeIds, $triggerPrototypeIdsXML);
1867
1868		// unlike triggers that belong to multiple hosts, trigger prototypes do not, so we just delete them
1869		if ($triggerPrototypesToDelete) {
1870			API::TriggerPrototype()->delete(array_keys($triggerPrototypesToDelete));
1871		}
1872
1873		// delete missing graph prototypes
1874		$dbGraphPrototypeIds = API::GraphPrototype()->get([
1875			'output' => ['graphid'],
1876			'hostids' => $processedHostIds,
1877			'preservekeys' => true,
1878			'nopermissions' => true,
1879			'inherited' => false
1880		]);
1881
1882		$graphPrototypesToDelete = array_diff_key($dbGraphPrototypeIds, $graphPrototypeIdsXML);
1883
1884		// unlike graphs that belong to multiple hosts, graph prototypes do not, so we just delete them
1885		if ($graphPrototypesToDelete) {
1886			API::GraphPrototype()->delete(array_keys($graphPrototypesToDelete));
1887		}
1888
1889		// delete missing item prototypes
1890		$dbItemPrototypeIds = API::ItemPrototype()->get([
1891			'output' => ['itemid'],
1892			'hostids' => $processedHostIds,
1893			'preservekeys' => true,
1894			'nopermissions' => true,
1895			'inherited' => false
1896		]);
1897
1898		$itemPrototypesToDelete = array_diff_key($dbItemPrototypeIds, $itemPrototypeIdsXML);
1899
1900		if ($itemPrototypesToDelete) {
1901			API::ItemPrototype()->delete(array_keys($itemPrototypesToDelete));
1902		}
1903	}
1904
1905	/**
1906	 * Get formatted groups.
1907	 *
1908	 * @return array
1909	 */
1910	protected function getFormattedGroups() {
1911		if (!isset($this->formattedData['groups'])) {
1912			$this->formattedData['groups'] = $this->adapter->getGroups();
1913		}
1914
1915		return $this->formattedData['groups'];
1916	}
1917
1918	/**
1919	 * Get formatted templates.
1920	 *
1921	 * @return array
1922	 */
1923	public function getFormattedTemplates() {
1924		if (!isset($this->formattedData['templates'])) {
1925			$this->formattedData['templates'] = $this->adapter->getTemplates();
1926		}
1927
1928		return $this->formattedData['templates'];
1929	}
1930
1931	/**
1932	 * Get formatted hosts.
1933	 *
1934	 * @return array
1935	 */
1936	public function getFormattedHosts() {
1937		if (!isset($this->formattedData['hosts'])) {
1938			$this->formattedData['hosts'] = $this->adapter->getHosts();
1939		}
1940
1941		return $this->formattedData['hosts'];
1942	}
1943
1944	/**
1945	 * Get formatted applications.
1946	 *
1947	 * @return array
1948	 */
1949	protected function getFormattedApplications() {
1950		if (!isset($this->formattedData['applications'])) {
1951			$this->formattedData['applications'] = $this->adapter->getApplications();
1952		}
1953
1954		return $this->formattedData['applications'];
1955	}
1956
1957	/**
1958	 * Get formatted value maps.
1959	 *
1960	 * @return array
1961	 */
1962	protected function getFormattedValueMaps() {
1963		if (!isset($this->formattedData['valueMaps'])) {
1964			$this->formattedData['valueMaps'] = $this->adapter->getValueMaps();
1965		}
1966
1967		return $this->formattedData['valueMaps'];
1968	}
1969
1970	/**
1971	 * Get formatted items.
1972	 *
1973	 * @return array
1974	 */
1975	protected function getFormattedItems() {
1976		if (!isset($this->formattedData['items'])) {
1977			$this->formattedData['items'] = $this->adapter->getItems();
1978		}
1979
1980		return $this->formattedData['items'];
1981	}
1982
1983	/**
1984	 * Get formatted discovery rules.
1985	 *
1986	 * @return array
1987	 */
1988	protected function getFormattedDiscoveryRules() {
1989		if (!isset($this->formattedData['discoveryRules'])) {
1990			$this->formattedData['discoveryRules'] = $this->adapter->getDiscoveryRules();
1991
1992			foreach ($this->formattedData['discoveryRules'] as &$discoveryRules) {
1993				foreach ($discoveryRules as &$discoveryRule) {
1994					foreach ($discoveryRule['trigger_prototypes'] as &$triggerPrototype) {
1995						$triggerPrototype['parsedExpressions'] = $this->parseTriggerExpression($triggerPrototype['expression']);
1996					}
1997					unset($triggerPrototype);
1998				}
1999				unset($discoveryRule);
2000			}
2001			unset($discoveryRules);
2002		}
2003
2004		return $this->formattedData['discoveryRules'];
2005	}
2006
2007	/**
2008	 * Get formatted triggers.
2009	 *
2010	 * @return array
2011	 */
2012	protected function getFormattedTriggers() {
2013		if (!isset($this->formattedData['triggers'])) {
2014			$this->formattedData['triggers'] = $this->adapter->getTriggers();
2015
2016			foreach ($this->formattedData['triggers'] as &$trigger) {
2017				$trigger['parsedExpressions'] = $this->parseTriggerExpression($trigger['expression']);
2018			}
2019			unset($trigger);
2020		}
2021
2022		return $this->formattedData['triggers'];
2023	}
2024
2025	/**
2026	 * Parses a trigger expression and returns an array of used hosts and items.
2027	 *
2028	 * @param string $expression
2029	 *
2030	 * @return array
2031	 *
2032	 * @throws Exception
2033	 */
2034	protected function parseTriggerExpression($expression) {
2035		$expressions = [];
2036
2037		$result = $this->triggerExpression->parse($expression);
2038		if (!$result) {
2039			throw new Exception($this->triggerExpression->error);
2040		}
2041
2042		foreach ($result->getTokensByType(CTriggerExpressionParserResult::TOKEN_TYPE_FUNCTION_MACRO) as $token) {
2043			$expressions[] = [
2044				'host' => $token['data']['host'],
2045				'item' => $token['data']['item'],
2046			];
2047		}
2048
2049		return $expressions;
2050	}
2051
2052	/**
2053	 * Get formatted graphs.
2054	 *
2055	 * @return array
2056	 */
2057	protected function getFormattedGraphs() {
2058		if (!isset($this->formattedData['graphs'])) {
2059			$this->formattedData['graphs'] = $this->adapter->getGraphs();
2060		}
2061
2062		return $this->formattedData['graphs'];
2063	}
2064
2065	/**
2066	 * Get formatted images.
2067	 *
2068	 * @return array
2069	 */
2070	protected function getFormattedImages() {
2071		if (!isset($this->formattedData['images'])) {
2072			$this->formattedData['images'] = $this->adapter->getImages();
2073		}
2074
2075		return $this->formattedData['images'];
2076	}
2077
2078	/**
2079	 * Get formatted maps.
2080	 *
2081	 * @return array
2082	 */
2083	protected function getFormattedMaps() {
2084		if (!isset($this->formattedData['maps'])) {
2085			$this->formattedData['maps'] = $this->adapter->getMaps();
2086		}
2087
2088		return $this->formattedData['maps'];
2089	}
2090
2091	/**
2092	 * Get formatted screens.
2093	 *
2094	 * @return array
2095	 */
2096	protected function getFormattedScreens() {
2097		if (!isset($this->formattedData['screens'])) {
2098			$this->formattedData['screens'] = $this->adapter->getScreens();
2099		}
2100
2101		return $this->formattedData['screens'];
2102	}
2103
2104	/**
2105	 * Get formatted template screens.
2106	 *
2107	 * @return array
2108	 */
2109	protected function getFormattedTemplateScreens() {
2110		if (!isset($this->formattedData['templateScreens'])) {
2111				$this->formattedData['templateScreens'] = $this->adapter->getTemplateScreens();
2112		}
2113
2114		return $this->formattedData['templateScreens'];
2115	}
2116}
2117