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 * Converter for converting import data from 1.8 to 2.0.
24 */
25class C10ImportConverter extends CConverter {
26
27	/**
28	 * Converter used for converting simple check item keys.
29	 *
30	 * @var CConverter
31	 */
32	protected $itemKeyConverter;
33
34	/**
35	 * Converter used for converting trigger expressions.
36	 *
37	 * @var CConverter
38	 */
39	protected $triggerConverter;
40
41	public function __construct() {
42		$this->itemKeyConverter = new C10ItemKeyConverter();
43		$this->triggerConverter = new C10TriggerConverter();
44	}
45
46	public function convert($value) {
47		$content = $value['zabbix_export'];
48
49		$content['version'] = '2.0';
50		$content = $this->convertTime($content);
51
52		$content = $this->convertDependencies($content);
53		$content = $this->separateTemplatesFromHosts($content);
54		$content = $this->convertHosts($content);
55		$content = $this->convertTemplates($content);
56
57		$content = $this->convertSysmaps($content);
58
59		$content = $this->convertScreens($content);
60
61		$content = $this->filterDuplicateGroups($content);
62		$content = $this->filterDuplicateTriggers($content);
63		$content = $this->filterDuplicateGraphs($content);
64
65		$value['zabbix_export'] = $content;
66
67		return $value;
68	}
69
70	/**
71	 * Convert the date and time elements.
72	 *
73	 * @param array $content
74	 *
75	 * @return array
76	 */
77	protected function convertTime(array $content) {
78		if (array_key_exists('date', $content) && array_key_exists('time', $content)) {
79			list($day, $month, $year) = explode('.', $content['date']);
80			list($hours, $minutes) = explode('.', $content['time']);
81			$content['date'] = date(DATE_TIME_FORMAT_SECONDS_XML, mktime($hours, $minutes, 0, $month, $day, $year));
82		}
83
84		unset($content['time']);
85
86		return $content;
87	}
88
89	/**
90	 * Separate templates and their elements from other hosts into the "templates" array.
91	 *
92	 * @param array $content
93	 *
94	 * @return array
95	 */
96	protected function separateTemplatesFromHosts(array $content) {
97		if (!isset($content['hosts']) || !$content['hosts']) {
98			return $content;
99		}
100
101		$templates = [];
102		foreach ($content['hosts'] as $i => $host) {
103			// skip hosts
104			if (isset($host['status']) && $host['status'] != HOST_STATUS_TEMPLATE) {
105				continue;
106			}
107
108			$template = [];
109			foreach (['name', 'groups', 'items', 'templates', 'triggers', 'graphs', 'macros'] as $key) {
110				if (isset($host[$key])) {
111					$template[$key] = $host[$key];
112				}
113			}
114
115			$templates[] = $template;
116
117			unset($content['hosts'][$i]);
118		}
119
120		if ($templates) {
121			$content['templates'] = $templates;
122
123			// reset host keys
124			$content['hosts'] = array_values($content['hosts']);
125		}
126
127		return $content;
128	}
129
130	/**
131	 * Convert host elements.
132	 *
133	 * @param array $content
134	 *
135	 * @return array
136	 */
137	protected function convertHosts(array $content) {
138		if (!isset($content['hosts']) || !$content['hosts']) {
139			return $content;
140		}
141
142		foreach ($content['hosts'] as &$host) {
143			$host = CArrayHelper::renameKeys($host, ['name' => 'host']);
144			$host = $this->convertHostInterfaces($host);
145			$host = $this->convertHostProfiles($host);
146			$host = $this->convertHostApplications($host);
147			$host = $this->convertHostItems($host);
148			$host = $this->convertHostTriggers($host, $host['host']);
149			$host = $this->convertHostGraphs($host, $host['host']);
150			$host = $this->convertHostMacros($host);
151
152			$host = $this->wrapChildren($host, 'templates', 'name');
153			$host = $this->wrapChildren($host, 'groups', 'name');
154
155			unset($host['useip']);
156			unset($host['ip']);
157			unset($host['dns']);
158			unset($host['port']);
159			unset($host['ipmi_ip']);
160			unset($host['ipmi_port']);
161			unset($host['host_profile']);
162			unset($host['host_profiles_ext']);
163		}
164		unset($host);
165
166		$content = $this->mergeTo($content['hosts'], $content, 'groups');
167		$content = $this->mergeTo($content['hosts'], $content, 'triggers');
168		$content = $this->mergeTo($content['hosts'], $content, 'graphs');
169		foreach ($content['hosts'] as &$host) {
170			unset($host['triggers']);
171			unset($host['graphs']);
172		}
173		unset($host);
174
175		return $content;
176	}
177
178	/**
179	 * Convert template elements.
180	 *
181	 * @param array $content
182	 *
183	 * @return array
184	 */
185	protected function convertTemplates(array $content) {
186		if (!isset($content['templates'])) {
187			return $content;
188		}
189
190		foreach ($content['templates'] as &$template) {
191			$template = CArrayHelper::renameKeys($template, ['name' => 'template']);
192			$template = $this->convertHostApplications($template);
193			$template = $this->convertHostItems($template);
194			$template = $this->convertHostTriggers($template, $template['template']);
195			$template = $this->convertHostGraphs($template, $template['template']);
196			$template = $this->convertHostMacros($template);
197
198			$template = $this->wrapChildren($template, 'templates', 'name');
199			$template = $this->wrapChildren($template, 'groups', 'name');
200		}
201		unset($template);
202
203		$content = $this->mergeTo($content['templates'], $content, 'groups');
204		$content = $this->mergeTo($content['templates'], $content, 'triggers');
205		$content = $this->mergeTo($content['templates'], $content, 'graphs');
206		foreach ($content['templates'] as &$host) {
207			unset($host['triggers']);
208			unset($host['graphs']);
209		}
210		unset($host);
211
212		return $content;
213	}
214
215	/**
216	 * Create host interfaces from the host properties and items and add them to the "host" element.
217	 *
218	 * @param array $host
219	 *
220	 * @return array
221	 */
222	protected function convertHostInterfaces(array $host) {
223		$interfaces = [];
224		$i = 0;
225
226		// create an agent interface from the host properties
227		if (isset($host['useip']) && isset($host['ip']) && isset($host['dns']) && isset($host['port'])) {
228			$agentInterface = [
229				'type' => INTERFACE_TYPE_AGENT,
230				'useip' => $host['useip'],
231				'ip' => $host['ip'],
232				'dns' => $host['dns'],
233				'port' => $host['port'],
234				'default' => INTERFACE_PRIMARY,
235				'interface_ref' => 'if'.$i
236			];
237			$interfaces[] = $agentInterface;
238			$i++;
239		}
240
241		$hasIpmiItem = false;
242		$snmpItems = [];
243
244		if (isset($host['items']) && $host['items']) {
245			foreach ($host['items'] as $item) {
246				if (!isset($item['type'])) {
247					continue;
248				}
249
250				if ($item['type'] == ITEM_TYPE_IPMI) {
251					$hasIpmiItem = true;
252				}
253				if ($item['type'] == ITEM_TYPE_SNMPV1 || $item['type'] == ITEM_TYPE_SNMPV2C
254						|| $item['type'] == ITEM_TYPE_SNMPV3) {
255					$snmpItems[] = $item;
256				}
257			}
258
259			// if a least one IPMI item exists on a host, create an IPMI interface
260			if ($hasIpmiItem) {
261				$ipmiInterface = [
262					'type' => INTERFACE_TYPE_IPMI,
263					'useip' => INTERFACE_USE_IP,
264					'ip' => (array_key_exists('ipmi_ip', $host) && $host['ipmi_ip'] !== '')
265						? $host['ipmi_ip'] : $host['ip'],
266					'dns' => '',
267					'port' => (array_key_exists('ipmi_port', $host) && $host['ipmi_port'] !== '')
268						? $host['ipmi_port'] : '623',
269					'default' => INTERFACE_PRIMARY,
270					'interface_ref' => 'if'.$i
271				];
272				$interfaces[] = $ipmiInterface;
273				$i++;
274			}
275
276			// if SNMP item exist, create an SNMP interface for each SNMP item port.
277			if ($snmpItems) {
278				$snmpInterfaces = [];
279				foreach ($snmpItems as $item) {
280					if (!isset($item['snmp_port']) || isset($snmpInterfaces[$item['snmp_port']])) {
281						continue;
282					}
283
284					$snmpInterface = [
285						'type' => INTERFACE_TYPE_SNMP,
286						'useip' => $host['useip'],
287						'ip' => $host['ip'],
288						'dns' => $host['dns'],
289						'port' => $item['snmp_port'],
290						'default' => (count($snmpInterfaces)) ? INTERFACE_SECONDARY : INTERFACE_PRIMARY,
291						'interface_ref' => 'if'.$i
292					];
293					$snmpInterfaces[$item['snmp_port']] = $snmpInterface;
294					$interfaces[] = $snmpInterface;
295					$i++;
296				}
297			}
298		}
299
300		if ($interfaces) {
301			$host['interfaces'] = $interfaces;
302		}
303
304		// map items to new interfaces
305		if (isset($host['items']) && $host['items']) {
306			foreach ($host['items'] as &$item) {
307				if (!isset($item['type'])) {
308					continue;
309				}
310
311				$interfaceType = itemTypeInterface($item['type']);
312				switch ($interfaceType) {
313					case INTERFACE_TYPE_AGENT:
314					case INTERFACE_TYPE_ANY:
315						$item['interface_ref'] = $agentInterface['interface_ref'];
316
317						break;
318					case INTERFACE_TYPE_IPMI:
319						$item['interface_ref'] = $ipmiInterface['interface_ref'];
320
321						break;
322					case INTERFACE_TYPE_SNMP:
323						if (isset($item['snmp_port'])) {
324							$item['interface_ref'] = $snmpInterfaces[$item['snmp_port']]['interface_ref'];
325						}
326
327						break;
328				}
329			}
330			unset($item);
331		}
332
333		return $host;
334	}
335
336	/**
337	 * Convert host "host_profile" and "host_profiles_ext" elements and calculate "inventory_mode".
338	 *
339	 * @param array $host
340	 *
341	 * @return array
342	 */
343	protected function convertHostProfiles(array $host) {
344		$host['inventory'] = ['inventory_mode' => HOST_INVENTORY_DISABLED];
345
346		// rename and merge profile fields
347		if (array_key_exists('host_profile', $host)) {
348			foreach ($host['host_profile'] as $key => $value) {
349				$host['inventory'][$this->getNewProfileName($key)] = $value;
350			}
351			$host['inventory']['inventory_mode'] = HOST_INVENTORY_MANUAL;
352		}
353
354		if (array_key_exists('host_profiles_ext', $host)) {
355			foreach ($host['host_profiles_ext'] as $key => $value) {
356				$key = $this->getNewProfileName($key);
357
358				// if renaming results in a duplicate inventory field, concatenate them
359				// this is the case with "notes" and "device_notes"
360				if (array_key_exists($key, $host['inventory']) && $host['inventory'][$key] !== '') {
361					$host['inventory'][$key] .= "\r\n\r\n".$value;
362				}
363				else {
364					$host['inventory'][$key] = $value;
365				}
366			}
367			$host['inventory']['inventory_mode'] = HOST_INVENTORY_MANUAL;
368		}
369
370		return $host;
371	}
372
373	/**
374	 * Map an old profile key name to the new inventory key name.
375	 *
376	 * @param string $key
377	 *
378	 * @return string
379	 */
380	protected function getNewProfileName($key) {
381		$map = [
382			'devicetype' => 'type',
383			'serialno' => 'serialno_a',
384			'macaddress' => 'macaddress_a',
385			'hardware' => 'hardware_full',
386			'software' => 'software_full',
387			'device_type' => 'type_full',
388			'device_alias' => 'alias',
389			'device_os' => 'os_full',
390			'device_os_short' => 'os_short',
391			'device_serial' => 'serialno_b',
392			'device_tag' => 'asset_tag',
393			'ip_macaddress' => 'macaddress_b',
394			'device_hardware' => 'hardware',
395			'device_software' => 'software',
396			'device_app_01' => 'software_app_a',
397			'device_app_02' => 'software_app_b',
398			'device_app_03' => 'software_app_c',
399			'device_app_04' => 'software_app_d',
400			'device_app_05' => 'software_app_e',
401			'device_chassis' => 'chassis',
402			'device_model' => 'model',
403			'device_hw_arch' => 'hw_arch',
404			'device_vendor' => 'vendor',
405			'device_contract' => 'contract_number',
406			'device_who' => 'installer_name',
407			'device_status' => 'deployment_status',
408			'device_url_1' => 'url_a',
409			'device_url_2' => 'url_b',
410			'device_url_3' => 'url_c',
411			'device_networks' => 'host_networks',
412			'ip_subnet_mask' => 'host_netmask',
413			'ip_router' => 'host_router',
414			'oob_subnet_mask' => 'oob_netmask',
415			'date_hw_buy' => 'date_hw_purchase',
416			'site_street_1' => 'site_address_a',
417			'site_street_2' => 'site_address_b',
418			'site_street_3' => 'site_address_c',
419			'poc_1_phone_1' => 'poc_1_phone_a',
420			'poc_1_phone_2' => 'poc_1_phone_b',
421			'poc_2_phone_1' => 'poc_2_phone_a',
422			'poc_2_phone_2' => 'poc_2_phone_b',
423			'device_notes' => 'notes',
424		];
425
426		return array_key_exists($key, $map) ? $map[$key] : $key;
427	}
428
429	/**
430	 * Filters duplicate host groups from the content.
431	 *
432	 * @param array $content
433	 *
434	 * @return array
435	 */
436	protected function filterDuplicateGroups(array $content) {
437		if (!isset($content['groups'])) {
438			return $content;
439		}
440
441		$groups = [];
442
443		foreach ($content['groups'] as $group) {
444			$groups[$group['name']] = $group;
445		}
446
447		$content['groups'] = array_values($groups);
448
449		return $content;
450	}
451
452	/**
453	 * Converts triggers elements.
454	 *
455	 * @param array 	$host
456	 * @param string 	$hostName 	technical name of the host that the triggers were imported under
457	 *
458	 * @return array
459	 */
460	protected function convertHostTriggers(array $host, $hostName) {
461		if (!isset($host['triggers']) || !$host['triggers']) {
462			return $host;
463		}
464
465		foreach ($host['triggers'] as &$trigger) {
466			$trigger = CArrayHelper::renameKeys($trigger, ['description' => 'name', 'comments' => 'description']);
467			$trigger = $this->convertTriggerExpression($trigger, $hostName);
468		}
469		unset($trigger);
470
471		return $host;
472	}
473
474	/**
475	 * Allocate the dependencies from the root element to the trigger elements and convert them to a new format.
476	 *
477	 * Dependencies, that cannot be resolved are skipped.
478	 *
479	 * @param array $content
480	 *
481	 * @return array
482	 */
483	protected function convertDependencies(array $content) {
484		// we cannot import dependencies if hosts are missing
485		if (!isset($content['dependencies']) || !$content['dependencies'] || !isset($content['hosts'])) {
486			unset($content['dependencies']);
487
488			return $content;
489		}
490
491		// build a description-expression trigger index with references to the triggers in the content
492		$descriptionExpressionIndex = [];
493		foreach ($content['hosts'] as $hostKey => $host) {
494			if (!isset($host['triggers']) || !$host['triggers']) {
495				continue;
496			}
497
498			foreach ($host['triggers'] as $triggerKey => $trigger) {
499				$descriptionExpressionIndex[$trigger['description']][$trigger['expression']][] =
500					&$content['hosts'][$hostKey]['triggers'][$triggerKey];
501			}
502		}
503
504		$hosts = zbx_toHash($content['hosts'], 'name');
505
506		foreach ($content['dependencies'] as $dependency) {
507			list($sourceHost, $sourceDescription) = explode(':', $dependency['description'], 2);
508			unset($dependency['description']);
509
510			// if one of the hosts is missing from the data or doesn't have any triggers, skip this dependency
511			if (!isset($hosts[$sourceHost]) || !isset($hosts[$sourceHost]['triggers'])) {
512				continue;
513			}
514
515			foreach ($dependency as $depends) {
516				list($targetHost, $targetDescription) = explode(':', $depends, 2);
517
518				// if one of the hosts is missing from the data or doesn't have any triggers, skip this dependency
519				if (!isset($hosts[$targetHost]) || !isset($hosts[$targetHost]['triggers'])) {
520					continue;
521				}
522
523				// find the target trigger
524				// use the first trigger with the same description
525				$targetTrigger = null;
526				foreach ($hosts[$targetHost]['triggers'] as $trigger) {
527					if ($trigger['description'] === $targetDescription) {
528						$targetTrigger = $trigger;
529
530						break;
531					}
532				}
533
534				// if the target trigger wasn't found - skip this dependency
535				if (!$targetTrigger) {
536					continue;
537				}
538
539				// find the source trigger and add the dependencies to all of the copies of the trigger
540				foreach ($hosts[$targetHost]['triggers'] as $trigger) {
541					if ($trigger['description'] === $sourceDescription) {
542						// if the source trigger is not present in the data - skip this dependency
543						if (!isset($descriptionExpressionIndex[$trigger['description']])
544								|| !isset($descriptionExpressionIndex[$trigger['description']][$trigger['expression']])) {
545
546							continue 2;
547						}
548
549						// working with references to triggers in the content here
550						foreach ($descriptionExpressionIndex[$trigger['description']][$trigger['expression']] as &$trigger) {
551							$trigger['dependencies'][] = [
552								'name' => $targetTrigger['description'],
553								'expression' => $this->triggerConverter->convert($targetTrigger['expression'])
554							];
555						}
556						unset($trigger);
557					}
558				}
559			}
560		}
561
562		$content['hosts'] = array_values($hosts);
563		unset($content['dependencies']);
564
565		return $content;
566	}
567
568	/**
569	 * Filters duplicate triggers from the array and returns the content with unique triggers.
570	 *
571	 * @param array $content
572	 *
573	 * @return array
574	 */
575	protected function filterDuplicateTriggers(array $content) {
576		if (!isset($content['triggers'])) {
577			return $content;
578		}
579
580		$existingTriggers = [];
581
582		$filteredTriggers = [];
583		foreach ($content['triggers'] as $trigger) {
584			$name = $trigger['name'];
585			$expression = $trigger['expression'];
586
587			if (isset($existingTriggers[$name]) && isset($existingTriggers[$name][$expression])) {
588				continue;
589			}
590
591			$filteredTriggers[] = $trigger;
592			$existingTriggers[$name][$expression] = true;
593		}
594
595		$content['triggers'] = $filteredTriggers;
596
597		return $content;
598	}
599
600	/**
601	 * Convert trigger expression and replace host macros.
602	 *
603	 * @param array 	$trigger
604	 * @param string 	$hostName	technical name of the host that the trigger was imported under
605	 *
606	 * @return string
607	 */
608	protected function convertTriggerExpression(array $trigger, $hostName) {
609		$trigger['expression'] = $this->triggerConverter->convert($trigger['expression']);
610
611		// replace host macros with the host name
612		// not sure why this is required, but this has been preserved from when refactoring CXmlImport18
613		$trigger['expression'] = str_replace('{HOSTNAME}', $hostName, $trigger['expression']);
614		$trigger['expression'] = str_replace('{HOST.HOST}', $hostName, $trigger['expression']);
615
616		return $trigger;
617	}
618
619	/**
620	 * Convert application from items.
621	 *
622	 * @param array $host
623	 *
624	 * @return array
625	 */
626	protected function convertHostApplications(array $host) {
627		if (!isset($host['items']) || !$host['items']) {
628			return $host;
629		}
630
631		foreach ($host['items'] as $item) {
632			if (isset($item['applications']) && $item['applications'] !== '') {
633				foreach ($item['applications'] as $application) {
634					$host['applications'][] = ['name' => $application];
635				}
636			}
637		}
638
639		return $host;
640	}
641
642	/**
643	 * Convert item elements.
644	 *
645	 * @param array $host
646	 *
647	 * @return array
648	 */
649	protected function convertHostItems(array $host) {
650		if (!isset($host['items']) || !$host['items']) {
651			return $host;
652		}
653
654		foreach ($host['items'] as &$item) {
655			$item = CArrayHelper::renameKeys($item, ['description' => 'name']);
656
657			// convert simple check keys
658			$item['key'] = $this->itemKeyConverter->convert($item['key']);
659
660			$item = $this->wrapChildren($item, 'applications', 'name');
661
662			unset($item['lastlogsize']);
663		}
664		unset($item);
665
666		return $host;
667	}
668
669	/**
670	 * Convert graph elements.
671	 *
672	 * @param array 	$host
673	 * @param string 	$hostName	technical name of the host that the graphs were imported under
674	 *
675	 * @return array
676	 */
677	protected function convertHostGraphs(array $host, $hostName) {
678		if (!isset($host['graphs']) || !$host['graphs']) {
679			return $host;
680		}
681
682		foreach ($host['graphs'] as &$graph) {
683			$graph = CArrayHelper::renameKeys($graph, [
684				'graphtype' => 'type',
685				'graph_elements' => 'graph_items',
686				'ymin_type' => 'ymin_type_1',
687				'ymax_type' => 'ymax_type_1',
688				'ymin_item_key' => 'ymin_item_1',
689				'ymax_item_key' => 'ymax_item_1'
690			]);
691			$graph = $this->convertGraphItemReference($graph, 'ymin_item_1');
692			$graph = $this->convertGraphItemReference($graph, 'ymax_item_1');
693
694			if ($graph['type'] == GRAPH_TYPE_NORMAL || $graph['type'] == GRAPH_TYPE_STACKED) {
695				unset($graph['show_legend']);
696			}
697
698			foreach ($graph['graph_items'] as &$graphItem) {
699				$graphItem = $this->convertGraphItemReference($graphItem, 'item', $hostName);
700
701				unset($graphItem['periods_cnt']);
702			}
703			unset($graphItem);
704		}
705		unset($graph);
706
707		return $host;
708	}
709
710	/**
711	 * Convert item references used in graphs.
712	 *
713	 * @param array 		$array		source array
714	 * @param string		$key		property under which the reference is stored
715	 * @param string|null 	$host_name	if set to some host name, host macros will be resolved into this host
716	 *
717	 * @return array
718	 */
719	protected function convertGraphItemReference(array $array, $key, $host_name = null) {
720		if ($array[$key] === '') {
721			$array[$key] = [];
722		}
723		else {
724			$colon_pos = strpos($array[$key], ':');
725
726			list ($host, $item_key) = explode(':', $array[$key], 2);
727
728			// replace host macros with the host name
729			// not sure why this is required, but this has been preserved from when refactoring CXmlImport18
730			if ($host_name !== null && ($host === '{HOSTNAME}' || $host === '{HOST.HOST}')) {
731				$host = $host_name;
732			}
733
734			$array[$key] = ['host' => $host, 'key' => $this->itemKeyConverter->convert($item_key)];
735		}
736
737		return $array;
738	}
739
740	/**
741	 * Filters duplicate graphs from the array and returns the content with unique graphs.
742	 *
743	 * Graphs are assumed identical if their names and items are identical.
744	 *
745	 * @param array $content
746	 *
747	 * @return array
748	 */
749	protected function filterDuplicateGraphs(array $content) {
750		if (!isset($content['graphs'])) {
751			return $content;
752		}
753
754		$existingGraphs = [];
755
756		$filteredGraphs = [];
757		foreach ($content['graphs'] as $graph) {
758			$name = $graph['name'];
759			$graphItems = $graph['graph_items'];
760
761			if (isset($existingGraphs[$name])) {
762				foreach ($existingGraphs[$name] as $existingGraphItems) {
763					if ($graphItems == $existingGraphItems) {
764						continue 2;
765					}
766				}
767			}
768
769			$filteredGraphs[] = $graph;
770
771			$existingGraphs[$name][] = $graphItems;
772		}
773
774		$content['graphs'] = $filteredGraphs;
775
776		return $content;
777	}
778
779	/**
780	 * Converts host macro elements.
781	 *
782	 * @param array 	$host
783	 *
784	 * @return array
785	 */
786	protected function convertHostMacros(array $host) {
787		if (!isset($host['macros']) || !$host['macros']) {
788			return $host;
789		}
790
791		foreach ($host['macros'] as &$macro) {
792			$macro = CArrayHelper::renameKeys($macro, ['name' => 'macro']);
793		}
794		unset($macro);
795
796		return $host;
797	}
798
799	/**
800	 * Convert map elements.
801	 *
802	 * @param array $content
803	 *
804	 * @return array
805	 */
806	protected function convertSysmaps(array $content) {
807		if (!isset($content['sysmaps']) || !$content['sysmaps']) {
808			return $content;
809		}
810
811		$content = CArrayHelper::renameKeys($content, ['sysmaps' => 'maps']);
812		foreach ($content['maps'] as &$map) {
813			if (isset($map['selements']) && $map['selements']) {
814				foreach ($map['selements'] as &$selement) {
815					$selement = CArrayHelper::renameKeys($selement, [
816						'elementid' => 'element',
817						'iconid_off' => 'icon_off',
818						'iconid_on' => 'icon_on',
819						'iconid_disabled' => 'icon_disabled',
820						'iconid_maintenance' => 'icon_maintenance'
821					]);
822
823					if (isset($selement['elementtype']) && $selement['elementtype'] == SYSMAP_ELEMENT_TYPE_TRIGGER) {
824						$selement['element']['expression'] = $this->triggerConverter->convert(
825							$selement['element']['expression']
826						);
827
828						unset($selement['element']['host']);
829					}
830
831					unset($selement['iconid_unknown']);
832				}
833				unset($selement);
834			}
835
836			if (isset($map['links']) && $map['links']) {
837				foreach ($map['links'] as &$link) {
838					if (isset($link['linktriggers']) && $link['linktriggers']) {
839						foreach ($link['linktriggers'] as &$linkTrigger) {
840							$linkTrigger = CArrayHelper::renameKeys($linkTrigger, ['triggerid' => 'trigger']);
841							$linkTrigger['trigger']['expression'] = $this->triggerConverter->convert(
842								$linkTrigger['trigger']['expression']
843							);
844
845							unset($linkTrigger['trigger']['host']);
846						}
847						unset($linkTrigger);
848					}
849				}
850				unset($link);
851			}
852
853			$map = CArrayHelper::renameKeys($map, ['backgroundid' => 'background']);
854		}
855		unset($map);
856
857		return $content;
858	}
859
860	/**
861	 * Convert screen elements.
862	 *
863	 * @param array $content
864	 *
865	 * @return array
866	 */
867	protected function convertScreens(array $content) {
868		if (!isset($content['screens']) || !$content['screens']) {
869			return $content;
870		}
871
872		foreach ($content['screens'] as &$screen) {
873			$screen = CArrayHelper::renameKeys($screen, ['screenitems' => 'screen_items']);
874
875			if (isset($screen['screen_items']) && $screen['screen_items']) {
876				foreach ($screen['screen_items'] as &$screenItem) {
877					$screenItem = CArrayHelper::renameKeys($screenItem, ['resourceid' => 'resource']);
878
879					if (isset($screenItem['resource']) && $screenItem['resource'] !== '0') {
880						$screenItem['resource'] = CArrayHelper::renameKeys($screenItem['resource'], ['key_' => 'key']);
881					}
882				}
883				unset($screenItem);
884			}
885		}
886		unset($screen);
887
888		return $content;
889	}
890
891	/**
892	 * Merges all of the values from each element of $source stored in the $key property to the $key property of $target.
893	 *
894	 * @param array $source
895	 * @param array $target
896	 * @param string $key
897	 *
898	 * @return array    $target array with the new values
899	 */
900	protected function mergeTo(array $source, array $target, $key) {
901		$values = (isset($target[$key])) ? $target[$key] : [];
902
903		foreach ($source as $sourceItem) {
904			if (!isset($sourceItem[$key]) || !$sourceItem[$key]) {
905				continue;
906			}
907
908			foreach ($sourceItem[$key] as $value) {
909				$values[] = $value;
910			}
911
912		}
913
914		if ($values) {
915			$target[$key] = $values;
916		}
917
918		return $target;
919	}
920
921	/**
922	 * Adds a $wrapperKey property for each element of $key in $array and moves it's value to the new property.
923	 *
924	 * @param array $array
925	 * @param string $key
926	 * @param string $wrapperKey
927	 *
928	 * @return array
929	 */
930	protected function wrapChildren(array $array, $key, $wrapperKey) {
931		if (array_key_exists($key, $array)) {
932			foreach ($array[$key] as &$content) {
933				$content = [$wrapperKey => $content];
934			}
935			unset($content);
936		}
937
938		return $array;
939	}
940}
941