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
22class CConfigurationExportBuilder {
23
24	/**
25	 * @var array
26	 */
27	protected $data = [];
28
29	/**
30	 * @param $version  current export version
31	 */
32	public function __construct() {
33		$this->data['version'] = ZABBIX_EXPORT_VERSION;
34		$this->data['date'] = date(DATE_TIME_FORMAT_SECONDS_XML, time() - date('Z'));
35	}
36
37	/**
38	 * Get array with formatted export data.
39	 *
40	 * @return array
41	 */
42	public function getExport() {
43		return ['zabbix_export' => $this->data];
44	}
45
46	/**
47	 * Build data structure.
48	 *
49	 * @param array  $schema    Tag schema from validation class.
50	 * @param array  $data      Export data.
51	 * @param string $main_tag  Main element (for error reporting).
52	 *
53	 * @return array
54	 */
55	protected function build(array $schema, array $data, $main_tag = null) {
56		$n = 0;
57		$result = [];
58
59		$rules = $schema['rules'];
60
61		if ($schema['type'] & XML_INDEXED_ARRAY) {
62			$rules = $schema['rules'][$schema['prefix']]['rules'];
63		}
64
65		foreach ($data as $row) {
66			$store = [];
67			foreach ($rules as $tag => $val) {
68				$is_required = $val['type'] & XML_REQUIRED;
69				$is_string = $val['type'] & XML_STRING;
70				$is_array = $val['type'] & XML_ARRAY;
71				$is_indexed_array = $val['type'] & XML_INDEXED_ARRAY;
72				$has_data = array_key_exists($tag, $row);
73
74				if (array_key_exists('ex_default', $val)) {
75					$default_value = (string) call_user_func($val['ex_default'], $row);
76				}
77				elseif (array_key_exists('default', $val)) {
78					$default_value = (string) $val['default'];
79				}
80				else {
81					$default_value = null;
82				}
83
84				if (!$default_value && !$has_data) {
85					if ($is_required) {
86						throw new Exception(_s('Invalid tag "%1$s": %2$s.', $main_tag,
87							_s('the tag "%1$s" is missing', $tag)
88						));
89					}
90					continue;
91				}
92
93				$value = $has_data ? $row[$tag] : $default_value;
94
95				if (!$is_required && $has_data && $default_value == $value) {
96					continue;
97				}
98
99				if (array_key_exists('export', $val)) {
100					$store[$tag] = call_user_func($val['export'], $row);
101					continue;
102				}
103
104				if (($is_indexed_array || $is_array) && $has_data) {
105					$temp_store = $this->build($val, $is_array ? [$value] : $value, $tag);
106					if ($is_required || $temp_store) {
107						$store[$tag] = $temp_store;
108					}
109					continue;
110				}
111
112				if ($is_string && $value !== null) {
113					$value = str_replace("\r\n", "\n", $value);
114				}
115
116				if (array_key_exists('in', $val)) {
117					if (!array_key_exists($value, $val['in'])) {
118						throw new Exception(_s('Invalid tag "%1$s": %2$s.', $tag,
119							_s('unexpected constant value "%1$s"', $value)
120						));
121					}
122
123					$store[$tag] = $val['in'][$value];
124				}
125				else {
126					$store[$tag] = $value;
127				}
128			}
129
130			if ($schema['type'] & XML_INDEXED_ARRAY) {
131				$result[$n++] = $store;
132			}
133			else {
134				$result = $store;
135			}
136		}
137
138		return $result;
139	}
140
141	/**
142	 * Format groups.
143	 *
144	 * @param array $schema  Tag schema from validation class.
145	 * @param array $groups  Export data.
146	 */
147	public function buildGroups(array $schema, array $groups) {
148		$groups = $this->formatGroups($groups);
149
150		$this->data['groups'] = $this->build($schema, $groups, 'groups');
151	}
152
153	/**
154	 * Format templates.
155	 *
156	 * @param array $schema           Tag schema from validation class.
157	 * @param array $templates        Export data.
158	 * @param array $simple_triggers  Simple triggers.
159	 */
160	public function buildTemplates(array $schema, array $templates, array $simple_triggers) {
161		$templates = $this->formatTemplates($templates, $simple_triggers);
162
163		$this->data['templates'] = $this->build($schema, $templates, 'templates');
164	}
165
166	/**
167	 * Format hosts.
168	 *
169	 * @param array $schema           Tag schema from validation class.
170	 * @param array $hosts            Export data.
171	 * @param array $simple_triggers  Simple triggers.
172	 */
173	public function buildHosts(array $schema, array $hosts, array $simple_triggers) {
174		$hosts = $this->formatHosts($hosts, $simple_triggers);
175
176		$this->data['hosts'] = $this->build($schema, $hosts, 'hosts');
177	}
178
179	/**
180	 * Format triggers.
181	 *
182	 * @param array $schema    Tag schema from validation class.
183	 * @param array $triggers  Export data.
184	 */
185	public function buildTriggers(array $schema, array $triggers) {
186		$triggers = $this->formatTriggers($triggers);
187
188		$this->data['triggers'] = $this->build($schema, $triggers, 'triggers');
189	}
190
191	/**
192	 * Format graphs.
193	 *
194	 * @param array $schema  Tag schema from validation class.
195	 * @param array $graphs  Export data.
196	 */
197	public function buildGraphs(array $schema, array $graphs) {
198		$graphs = $this->formatGraphs($graphs);
199
200		$this->data['graphs'] = $this->build($schema, $graphs, 'graphs');
201	}
202
203	/**
204	 * Format media types.
205	 *
206	 * @param array $schema       Tag schema from validation class.
207	 * @param array $media_types  Export data.
208	 */
209	public function buildMediaTypes(array $schema, array $media_types) {
210		$media_types = $this->formatMediaTypes($media_types);
211
212		$this->data['media_types'] = $this->build($schema, $media_types, 'media_types');
213	}
214
215	/**
216	 * Separate simple triggers.
217	 *
218	 * @param array $triggers
219	 *
220	 * @return array
221	 */
222	public function extractSimpleTriggers(array &$triggers) {
223		$simple_triggers = [];
224
225		foreach ($triggers as $triggerid => $trigger) {
226			if (count($trigger['items']) == 1 && $trigger['items'][0]['type'] != ITEM_TYPE_HTTPTEST
227					&& $trigger['items'][0]['templateid'] == 0) {
228				$simple_triggers[] = $trigger;
229				unset($triggers[$triggerid]);
230			}
231		}
232
233		return $simple_triggers;
234	}
235
236	/**
237	 * Format templates.
238	 *
239	 * @param array $templates
240	 * @param array $simple_triggers
241	 */
242	protected function formatTemplates(array $templates, array $simple_triggers = null) {
243		$result = [];
244
245		CArrayHelper::sort($templates, ['host']);
246
247		foreach ($templates as $template) {
248			$result[] = [
249				'uuid' => $template['uuid'],
250				'template' => $template['host'],
251				'name' => $template['name'],
252				'description' => $template['description'],
253				'groups' => $this->formatGroups($template['groups']),
254				'items' => $this->formatItems($template['items'], $simple_triggers),
255				'discovery_rules' => $this->formatDiscoveryRules($template['discoveryRules']),
256				'httptests' => $this->formatHttpTests($template['httptests']),
257				'macros' => $this->formatMacros($template['macros']),
258				'templates' => $this->formatTemplateLinkage($template['parentTemplates']),
259				'dashboards' => $this->formatDashboards($template['dashboards']),
260				'tags' => $this->formatTags($template['tags']),
261				'valuemaps' => $this->formatValueMaps($template['valuemaps'])
262			];
263		}
264
265		return $result;
266	}
267
268	/**
269	 * Format hosts.
270	 *
271	 * @param array $hosts
272	 * @param array $simple_triggers
273	 */
274	protected function formatHosts(array $hosts, array $simple_triggers = null) {
275		$result = [];
276
277		CArrayHelper::sort($hosts, ['host']);
278
279		foreach ($hosts as $host) {
280			$host = $this->createInterfaceReferences($host);
281
282			$result[] = [
283				'host' => $host['host'],
284				'name' => $host['name'],
285				'description' => $host['description'],
286				'proxy' => $host['proxy'],
287				'status' => $host['status'],
288				'ipmi_authtype' => $host['ipmi_authtype'],
289				'ipmi_privilege' => $host['ipmi_privilege'],
290				'ipmi_username' => $host['ipmi_username'],
291				'ipmi_password' => $host['ipmi_password'],
292				'templates' => $this->formatTemplateLinkage($host['parentTemplates']),
293				'groups' => $this->formatGroups($host['groups']),
294				'interfaces' => $this->formatHostInterfaces($host['interfaces']),
295				'items' => $this->formatItems($host['items'], $simple_triggers),
296				'discovery_rules' => $this->formatDiscoveryRules($host['discoveryRules']),
297				'httptests' => $this->formatHttpTests($host['httptests']),
298				'macros' => $this->formatMacros($host['macros']),
299				'inventory_mode' => $host['inventory_mode'],
300				'inventory' => $this->formatHostInventory($host['inventory']),
301				'tags' => $this->formatTags($host['tags']),
302				'valuemaps' => $this->formatValueMaps($host['valuemaps'])
303			];
304		}
305
306		return $result;
307	}
308
309	/**
310	 * Format images.
311	 *
312	 * @param array $images
313	 */
314	public function buildImages(array $images) {
315		$this->data['images'] = [];
316
317		foreach ($images as $image) {
318			$this->data['images'][] = [
319				'name' => $image['name'],
320				'imagetype' => $image['imagetype'],
321				'encodedImage' => $image['encodedImage']
322			];
323		}
324	}
325
326	/**
327	 * Format maps.
328	 *
329	 * @param array $schema  Tag schema from validation class.
330	 * @param array $maps    Export data.
331	 */
332	public function buildMaps(array $schema, array $maps) {
333		$this->data['maps'] = [];
334
335		CArrayHelper::sort($maps, ['name']);
336
337		foreach ($maps as $map) {
338			$tmpSelements = $this->formatMapElements($map['selements']);
339			$this->data['maps'][] = [
340				'name' => $map['name'],
341				'width' => $map['width'],
342				'height' => $map['height'],
343				'label_type' => $map['label_type'],
344				'label_location' => $map['label_location'],
345				'highlight' => $map['highlight'],
346				'expandproblem' => $map['expandproblem'],
347				'markelements' => $map['markelements'],
348				'show_unack' => $map['show_unack'],
349				'severity_min' => $map['severity_min'],
350				'show_suppressed' => $map['show_suppressed'],
351				'grid_size' => $map['grid_size'],
352				'grid_show' => $map['grid_show'],
353				'grid_align' => $map['grid_align'],
354				'label_format' => $map['label_format'],
355				'label_type_host' => $map['label_type_host'],
356				'label_type_hostgroup' => $map['label_type_hostgroup'],
357				'label_type_trigger' => $map['label_type_trigger'],
358				'label_type_map' => $map['label_type_map'],
359				'label_type_image' => $map['label_type_image'],
360				'label_string_host' => $map['label_string_host'],
361				'label_string_hostgroup' => $map['label_string_hostgroup'],
362				'label_string_trigger' => $map['label_string_trigger'],
363				'label_string_map' => $map['label_string_map'],
364				'label_string_image' => $map['label_string_image'],
365				'expand_macros' => $map['expand_macros'],
366				'background' => $map['backgroundid'],
367				'iconmap' => $map['iconmap'],
368				'urls' => $this->formatMapUrls($map['urls']),
369				'selements' => $tmpSelements,
370				'shapes' => $map['shapes'],
371				'lines' => $map['lines'],
372				'links' => $this->formatMapLinks($map['links'], $tmpSelements)
373			];
374		}
375
376		$this->data['maps'] = $this->build($schema, $this->data['maps'], 'maps');
377	}
378
379	/**
380	 * Format media types.
381	 *
382	 * @param array $media_types
383	 */
384	protected function formatMediaTypes(array $media_types) {
385		$result = [];
386
387		CArrayHelper::sort($media_types, ['name']);
388
389		foreach ($media_types as $media_type) {
390			$result[] = [
391				'name' => $media_type['name'],
392				'type' => $media_type['type'],
393				'smtp_server' => $media_type['smtp_server'],
394				'smtp_port' => $media_type['smtp_port'],
395				'smtp_helo' => $media_type['smtp_helo'],
396				'smtp_email' => $media_type['smtp_email'],
397				'smtp_security' => $media_type['smtp_security'],
398				'smtp_verify_host' => $media_type['smtp_verify_host'],
399				'smtp_verify_peer' => $media_type['smtp_verify_peer'],
400				'smtp_authentication' => $media_type['smtp_authentication'],
401				'username' => $media_type['username'],
402				'password' => $media_type['passwd'],
403				'content_type' => $media_type['content_type'],
404				'script_name' => $media_type['exec_path'],
405				'parameters' => self::formatMediaTypeParameters($media_type),
406				'gsm_modem' => $media_type['gsm_modem'],
407				'status' => $media_type['status'],
408				'max_sessions' => $media_type['maxsessions'],
409				'attempts' => $media_type['maxattempts'],
410				'attempt_interval' => $media_type['attempt_interval'],
411				'script' => $media_type['script'],
412				'timeout' => $media_type['timeout'],
413				'process_tags' => $media_type['process_tags'],
414				'show_event_menu' => $media_type['show_event_menu'],
415				'event_menu_url' => $media_type['event_menu_url'],
416				'event_menu_name' => $media_type['event_menu_name'],
417				'description' => $media_type['description'],
418				'message_templates' => self::formatMediaTypeMessageTemplates($media_type['message_templates'])
419			];
420		}
421
422		return $result;
423	}
424
425	/**
426	 * Format media type parameters.
427	 *
428	 * @static
429	 *
430	 * @param array $media_type
431	 *
432	 * @return array|string
433	 */
434	private static function formatMediaTypeParameters(array $media_type) {
435		if ($media_type['type'] == MEDIA_TYPE_EXEC) {
436			return $media_type['exec_params'];
437		}
438
439		if ($media_type['type'] == MEDIA_TYPE_WEBHOOK) {
440			CArrayHelper::sort($media_type['parameters'], ['name']);
441
442			return array_values($media_type['parameters']);
443		}
444
445		return [];
446	}
447
448	/**
449	 * Format media type message templates.
450	 *
451	 * @static
452	 *
453	 * @param array $message_templates
454	 *
455	 * @return array
456	 */
457	private static function formatMediaTypeMessageTemplates(array $message_templates): array {
458		$result = [];
459
460		CArrayHelper::sort($message_templates, ['eventsource', 'recovery']);
461
462		foreach ($message_templates as $message_template) {
463			$result[] = [
464				'event_source' => $message_template['eventsource'],
465				'operation_mode' => $message_template['recovery'],
466				'subject' => $message_template['subject'],
467				'message' => $message_template['message']
468			];
469		}
470
471		return $result;
472	}
473
474	/**
475	 * Format value maps.
476	 *
477	 * @param array $valuemaps
478	 */
479	protected function formatValueMaps(array $valuemaps) {
480		CArrayHelper::sort($valuemaps, ['name']);
481
482		foreach ($valuemaps as &$valuemap) {
483			foreach ($valuemap['mappings'] as &$mapping) {
484				if ($mapping['type'] == VALUEMAP_MAPPING_TYPE_EQUAL) {
485					unset($mapping['type']);
486				}
487				elseif ($mapping['type'] == VALUEMAP_MAPPING_TYPE_DEFAULT) {
488					unset($mapping['value']);
489				}
490			}
491			unset($mapping);
492		}
493		unset($valuemap);
494
495		return $valuemaps;
496	}
497
498	/**
499	 * For each host interface an unique reference must be created and then added for all items, discovery rules
500	 * and item prototypes that use the interface.
501	 *
502	 * @param array $host
503	 *
504	 * @return array
505	 */
506	protected function createInterfaceReferences(array $host) {
507		$references = [
508			'num' => 1,
509			'refs' => []
510		];
511
512		// create interface references
513		foreach ($host['interfaces'] as &$interface) {
514			$refNum = $references['num']++;
515			$referenceKey = 'if'.$refNum;
516			$interface['interface_ref'] = $referenceKey;
517			$references['refs'][$interface['interfaceid']] = $referenceKey;
518		}
519		unset($interface);
520
521		foreach ($host['items'] as &$item) {
522			if ($item['interfaceid']) {
523				$item['interface_ref'] = $references['refs'][$item['interfaceid']];
524			}
525		}
526		unset($item);
527
528		foreach ($host['discoveryRules'] as &$discoveryRule) {
529			if ($discoveryRule['interfaceid']) {
530				$discoveryRule['interface_ref'] = $references['refs'][$discoveryRule['interfaceid']];
531			}
532
533			foreach ($discoveryRule['itemPrototypes'] as &$prototype) {
534				if ($prototype['interfaceid']) {
535					$prototype['interface_ref'] = $references['refs'][$prototype['interfaceid']];
536				}
537			}
538			unset($prototype);
539		}
540		unset($discoveryRule);
541
542		return $host;
543	}
544
545	/**
546	 * Format discovery rules.
547	 *
548	 * @param array $discoveryRules
549	 *
550	 * @return array
551	 */
552	protected function formatDiscoveryRules(array $discoveryRules) {
553		$result = [];
554
555		CArrayHelper::sort($discoveryRules, ['key_']);
556
557		$simple_trigger_prototypes = [];
558
559		foreach ($discoveryRules as $discoveryRule) {
560			foreach ($discoveryRule['triggerPrototypes'] as $i => $trigger_prototype) {
561				if (count($trigger_prototype['items']) == 1) {
562					$simple_trigger_prototypes[] = $trigger_prototype;
563					unset($discoveryRule['triggerPrototypes'][$i]);
564				}
565			}
566
567			$data = [
568				'uuid' => $discoveryRule['uuid'],
569				'name' => $discoveryRule['name'],
570				'type' => $discoveryRule['type'],
571				'snmp_oid' => $discoveryRule['snmp_oid'],
572				'key' => $discoveryRule['key_'],
573				'delay' => $discoveryRule['delay'],
574				'status' => $discoveryRule['status'],
575				'allowed_hosts' => $discoveryRule['trapper_hosts'],
576				'params' => $discoveryRule['params'],
577				'ipmi_sensor' => $discoveryRule['ipmi_sensor'],
578				'authtype' => $discoveryRule['authtype'],
579				'username' => $discoveryRule['username'],
580				'password' => $discoveryRule['password'],
581				'publickey' => $discoveryRule['publickey'],
582				'privatekey' => $discoveryRule['privatekey'],
583				'filter' => $discoveryRule['filter'],
584				'lifetime' => $discoveryRule['lifetime'],
585				'description' => $discoveryRule['description'],
586				'item_prototypes' => $this->formatItems($discoveryRule['itemPrototypes'], $simple_trigger_prototypes),
587				'trigger_prototypes' => $this->formatTriggers($discoveryRule['triggerPrototypes']),
588				'graph_prototypes' => $this->formatGraphs($discoveryRule['graphPrototypes']),
589				'host_prototypes' => $this->formatHostPrototypes($discoveryRule['hostPrototypes']),
590				'jmx_endpoint' => $discoveryRule['jmx_endpoint'],
591				'timeout' => $discoveryRule['timeout'],
592				'url' => $discoveryRule['url'],
593				'query_fields' => $discoveryRule['query_fields'],
594				'parameters' => $discoveryRule['parameters'],
595				'posts' => $discoveryRule['posts'],
596				'status_codes' => $discoveryRule['status_codes'],
597				'follow_redirects' => $discoveryRule['follow_redirects'],
598				'post_type' => $discoveryRule['post_type'],
599				'http_proxy' => $discoveryRule['http_proxy'],
600				'headers' => $discoveryRule['headers'],
601				'retrieve_mode' => $discoveryRule['retrieve_mode'],
602				'request_method' => $discoveryRule['request_method'],
603				'allow_traps' => $discoveryRule['allow_traps'],
604				'ssl_cert_file' => $discoveryRule['ssl_cert_file'],
605				'ssl_key_file' => $discoveryRule['ssl_key_file'],
606				'ssl_key_password' => $discoveryRule['ssl_key_password'],
607				'verify_peer' => $discoveryRule['verify_peer'],
608				'verify_host' => $discoveryRule['verify_host'],
609				'lld_macro_paths' => $discoveryRule['lld_macro_paths'],
610				'preprocessing' => self::formatPreprocessingSteps($discoveryRule['preprocessing']),
611				'overrides' => $discoveryRule['overrides']
612			];
613
614			if (isset($discoveryRule['interface_ref'])) {
615				$data['interface_ref'] = $discoveryRule['interface_ref'];
616			}
617
618			if ($discoveryRule['query_fields']) {
619				$query_fields = [];
620
621				foreach ($discoveryRule['query_fields'] as $query_field) {
622					$query_fields[] = [
623						'name' => key($query_field),
624						'value' => reset($query_field)
625					];
626				}
627
628				$data['query_fields'] = $query_fields;
629			}
630
631			if ($discoveryRule['headers']) {
632				$headers = [];
633
634				foreach ($discoveryRule['headers'] as $name => $value) {
635					$headers[] = compact('name', 'value');
636				}
637
638				$data['headers'] = $headers;
639			}
640
641			$data['master_item'] = ($discoveryRule['type'] == ITEM_TYPE_DEPENDENT)
642				? ['key' => $discoveryRule['master_item']['key_']]
643				: [];
644
645			$result[] = $data;
646		}
647
648		return $result;
649	}
650
651	/**
652	 * Format preprocessing steps.
653	 *
654	 * @param array $preprocessing_steps
655	 *
656	 * @static
657	 *
658	 * @return array
659	 */
660	private static function formatPreprocessingSteps(array $preprocessing_steps) {
661		foreach ($preprocessing_steps as &$preprocessing_step) {
662			$preprocessing_step['parameters'] = ($preprocessing_step['type'] == ZBX_PREPROC_SCRIPT)
663				? [$preprocessing_step['params']]
664				: explode("\n", $preprocessing_step['params']);
665			unset($preprocessing_step['params']);
666		}
667		unset($preprocessing_step);
668
669		return $preprocessing_steps;
670	}
671
672	/**
673	 * Format web scenarios.
674	 *
675	 * @param array $httptests
676	 *
677	 * @return array
678	 */
679	protected function formatHttpTests(array $httptests) {
680		$result = [];
681
682		order_result($httptests, 'name');
683
684		foreach ($httptests as $httptest) {
685			$result[] = [
686				'uuid' => $httptest['uuid'],
687				'name' => $httptest['name'],
688				'delay' => $httptest['delay'],
689				'attempts' => $httptest['retries'],
690				'agent' => $httptest['agent'],
691				'http_proxy' => $httptest['http_proxy'],
692				'variables' => $httptest['variables'],
693				'headers' => $httptest['headers'],
694				'status' => $httptest['status'],
695				'authentication' => $httptest['authentication'],
696				'http_user' => $httptest['http_user'],
697				'http_password' => $httptest['http_password'],
698				'verify_peer' => $httptest['verify_peer'],
699				'verify_host' => $httptest['verify_host'],
700				'ssl_cert_file' => $httptest['ssl_cert_file'],
701				'ssl_key_file' => $httptest['ssl_key_file'],
702				'ssl_key_password' => $httptest['ssl_key_password'],
703				'steps' => $this->formatHttpSteps($httptest['steps']),
704				'tags' => $this->formatTags($httptest['tags'])
705			];
706		}
707
708		return $result;
709	}
710
711	/**
712	 * Format web scenario steps.
713	 *
714	 * @param array $httpsteps
715	 *
716	 * @return array
717	 */
718	protected function formatHttpSteps(array $httpsteps) {
719		$result = [];
720
721		order_result($httpsteps, 'no');
722
723		foreach ($httpsteps as $httpstep) {
724			$result[] = [
725				'name' => $httpstep['name'],
726				'url' => $httpstep['url'],
727				'query_fields' => $httpstep['query_fields'],
728				'posts' => $httpstep['posts'],
729				'variables' => $httpstep['variables'],
730				'headers' => $httpstep['headers'],
731				'follow_redirects' => $httpstep['follow_redirects'],
732				'retrieve_mode' => $httpstep['retrieve_mode'],
733				'timeout' => $httpstep['timeout'],
734				'required' => $httpstep['required'],
735				'status_codes' => $httpstep['status_codes']
736			];
737		}
738
739		return $result;
740	}
741
742	/**
743	 * Format host inventory.
744	 *
745	 * @param array $inventory
746	 *
747	 * @return array
748	 */
749	protected function formatHostInventory(array $inventory) {
750		unset($inventory['hostid']);
751
752		return $inventory;
753	}
754
755	/**
756	 * Format graphs.
757	 *
758	 * @param array $graphs
759	 *
760	 * @return array
761	 */
762	protected function formatGraphs(array $graphs) {
763		$result = [];
764
765		CArrayHelper::sort($graphs, ['name']);
766
767		foreach ($graphs as $graph) {
768			$data = [
769				'name' => $graph['name'],
770				'width' => $graph['width'],
771				'height' => $graph['height'],
772				'yaxismin' => $graph['yaxismin'],
773				'yaxismax' => $graph['yaxismax'],
774				'show_work_period' => $graph['show_work_period'],
775				'show_triggers' => $graph['show_triggers'],
776				'type' => $graph['graphtype'],
777				'show_legend' => $graph['show_legend'],
778				'show_3d' => $graph['show_3d'],
779				'percent_left' => $graph['percent_left'],
780				'percent_right' => $graph['percent_right'],
781				'ymin_type_1' => $graph['ymin_type'],
782				'ymax_type_1' => $graph['ymax_type'],
783				'ymin_item_1' => $graph['ymin_itemid'],
784				'ymax_item_1' => $graph['ymax_itemid'],
785				'graph_items' => $this->formatGraphItems($graph['gitems'])
786			];
787
788			if (array_key_exists('uuid', $graph)) {
789				$data['uuid'] = $graph['uuid'];
790			}
791
792			if ($graph['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
793				$data['discover'] = $graph['discover'];
794			}
795
796			$result[] = $data;
797		}
798
799		return $result;
800	}
801
802	/**
803	 * Format host prototypes.
804	 *
805	 * @param array $hostPrototypes
806	 *
807	 * @return array
808	 */
809	protected function formatHostPrototypes(array $hostPrototypes) {
810		$result = [];
811
812		CArrayHelper::sort($hostPrototypes, ['host']);
813
814		foreach ($hostPrototypes as $hostPrototype) {
815			$result[] = [
816				'uuid' => $hostPrototype['uuid'],
817				'host' => $hostPrototype['host'],
818				'name' => $hostPrototype['name'],
819				'status' => $hostPrototype['status'],
820				'discover' => $hostPrototype['discover'],
821				'group_links' => $this->formatGroupLinks($hostPrototype['groupLinks']),
822				'group_prototypes' => $this->formatGroupPrototypes($hostPrototype['groupPrototypes']),
823				'macros' => $this->formatMacros($hostPrototype['macros']),
824				'tags' => $this->formatTags($hostPrototype['tags']),
825				'templates' => $this->formatTemplateLinkage($hostPrototype['templates']),
826				'inventory_mode' => $hostPrototype['inventory_mode'],
827				'custom_interfaces' => $hostPrototype['custom_interfaces'],
828				'interfaces' => $this->formatHostPrototypeInterfaces($hostPrototype['interfaces'])
829			];
830		}
831
832		return $result;
833	}
834
835	/**
836	 * Format group links.
837	 *
838	 * @param array $groupLinks
839	 *
840	 * @return array
841	 */
842	protected function formatGroupLinks(array $groupLinks) {
843		$result = [];
844
845		CArrayHelper::sort($groupLinks, ['name']);
846
847		foreach ($groupLinks as $groupLink) {
848			$result[] = [
849				'group' => $groupLink['groupid']
850			];
851		}
852
853		return $result;
854	}
855
856	/**
857	 * Format group prototypes.
858	 *
859	 * @param array $groupPrototypes
860	 *
861	 * @return array
862	 */
863	protected function formatGroupPrototypes(array $groupPrototypes) {
864		$result = [];
865
866		CArrayHelper::sort($groupPrototypes, ['name']);
867
868		foreach ($groupPrototypes as $groupPrototype) {
869			$result[] = [
870				'name' => $groupPrototype['name']
871			];
872		}
873
874		return $result;
875	}
876
877	/**
878	 * Format template linkage.
879	 *
880	 * @param array $templates
881	 *
882	 * @return array
883	 */
884	protected function formatTemplateLinkage(array $templates) {
885		$result = [];
886
887		CArrayHelper::sort($templates, ['host']);
888
889		foreach ($templates as $template) {
890			$result[] = [
891				'name' => $template['host']
892			];
893		}
894
895		return $result;
896	}
897
898	/**
899	 * Format triggers.
900	 *
901	 * @param array $triggers
902	 *
903	 * @return array
904	 */
905	protected function formatTriggers(array $triggers) {
906		$result = [];
907
908		CArrayHelper::sort($triggers, ['description', 'expression', 'recovery_expression']);
909
910		foreach ($triggers as $trigger) {
911			$data = [
912				'expression' => $trigger['expression'],
913				'recovery_mode' => $trigger['recovery_mode'],
914				'recovery_expression' => $trigger['recovery_expression'],
915				'name' => $trigger['description'],
916				'event_name' => $trigger['event_name'],
917				'opdata' => $trigger['opdata'],
918				'correlation_mode' => $trigger['correlation_mode'],
919				'correlation_tag' => $trigger['correlation_tag'],
920				'url' => $trigger['url'],
921				'status' => $trigger['status'],
922				'priority' => $trigger['priority'],
923				'description' => $trigger['comments'],
924				'type' => $trigger['type'],
925				'manual_close' => $trigger['manual_close'],
926				'dependencies' => $this->formatDependencies($trigger['dependencies']),
927				'tags' => $this->formatTags($trigger['tags'])
928			];
929
930			if (array_key_exists('uuid', $trigger)) {
931				$data['uuid'] = $trigger['uuid'];
932			}
933
934			if ($trigger['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
935				$data['discover'] = $trigger['discover'];
936			}
937
938			$result[] = $data;
939		}
940
941		return $result;
942	}
943
944	/**
945	 * Format host interfaces.
946	 *
947	 * @param array $interfaces
948	 *
949	 * @return array
950	 */
951	protected function formatHostInterfaces(array $interfaces) {
952		$result = [];
953
954		CArrayHelper::sort($interfaces, ['type', 'ip', 'dns', 'port']);
955
956		foreach ($interfaces as $interface) {
957			$result[] = [
958				'default' => $interface['main'],
959				'type' => $interface['type'],
960				'useip' => $interface['useip'],
961				'ip' => $interface['ip'],
962				'dns' => $interface['dns'],
963				'port' => $interface['port'],
964				'details' => $interface['details'],
965				'interface_ref' => $interface['interface_ref']
966			];
967		}
968
969		return $result;
970	}
971
972	/**
973	 * Format host prototype interfaces.
974	 *
975	 * @param array $interfaces
976	 *
977	 * @return array
978	 */
979	protected function formatHostPrototypeInterfaces(array $interfaces): array {
980		$result = [];
981
982		CArrayHelper::sort($interfaces, ['type', 'ip', 'dns', 'port']);
983
984		foreach ($interfaces as $num => $interface) {
985			$result[$num] = [
986				'default' => $interface['main'],
987				'type' => $interface['type'],
988				'useip' => $interface['useip'],
989				'ip' => $interface['ip'],
990				'dns' => $interface['dns'],
991				'port' => $interface['port']
992			];
993
994			if ($interface['type'] == INTERFACE_TYPE_SNMP) {
995				$result[$num]['details'] = $interface['details'];
996			}
997		}
998
999		return $result;
1000	}
1001
1002	/**
1003	 * Format groups.
1004	 *
1005	 * @param array $groups
1006	 *
1007	 * @return array
1008	 */
1009	protected function formatGroups(array $groups) {
1010		$result = [];
1011
1012		CArrayHelper::sort($groups, ['name']);
1013
1014		foreach ($groups as $group) {
1015			$result[] = [
1016				'uuid' => $group['uuid'],
1017				'name' => $group['name']
1018			];
1019		}
1020
1021		return $result;
1022	}
1023
1024	/**
1025	 * Format items.
1026	 *
1027	 * @param array $items
1028	 * @param array $simple_triggers
1029	 *
1030	 * @return array
1031	 */
1032	protected function formatItems(array $items, array $simple_triggers) {
1033		$result = [];
1034
1035		CArrayHelper::sort($items, ['key_']);
1036
1037		foreach ($items as $item) {
1038			$data = [
1039				'uuid' => $item['uuid'],
1040				'name' => $item['name'],
1041				'type' => $item['type'],
1042				'snmp_oid' => $item['snmp_oid'],
1043				'key' => $item['key_'],
1044				'delay' => $item['delay'],
1045				'history' => $item['history'],
1046				'trends' => $item['trends'],
1047				'status' => $item['status'],
1048				'value_type' => $item['value_type'],
1049				'allowed_hosts' => $item['trapper_hosts'],
1050				'units' => $item['units'],
1051				'params' => $item['params'],
1052				'ipmi_sensor' => $item['ipmi_sensor'],
1053				'authtype' => $item['authtype'],
1054				'username' => $item['username'],
1055				'password' => $item['password'],
1056				'publickey' => $item['publickey'],
1057				'privatekey' => $item['privatekey'],
1058				'description' => $item['description'],
1059				'inventory_link' => $item['inventory_link'],
1060				'valuemap' => $item['valuemap'],
1061				'logtimefmt' => $item['logtimefmt'],
1062				'preprocessing' => self::formatPreprocessingSteps($item['preprocessing']),
1063				'jmx_endpoint' => $item['jmx_endpoint'],
1064				'timeout' => $item['timeout'],
1065				'url' => $item['url'],
1066				'query_fields' => $item['query_fields'],
1067				'parameters' => $item['parameters'],
1068				'posts' => $item['posts'],
1069				'status_codes' => $item['status_codes'],
1070				'follow_redirects' => $item['follow_redirects'],
1071				'post_type' => $item['post_type'],
1072				'http_proxy' => $item['http_proxy'],
1073				'headers' => $item['headers'],
1074				'retrieve_mode' => $item['retrieve_mode'],
1075				'request_method' => $item['request_method'],
1076				'output_format' => $item['output_format'],
1077				'allow_traps' => $item['allow_traps'],
1078				'ssl_cert_file' => $item['ssl_cert_file'],
1079				'ssl_key_file' => $item['ssl_key_file'],
1080				'ssl_key_password' => $item['ssl_key_password'],
1081				'tags' => $this->formatTags($item['tags']),
1082				'verify_peer' => $item['verify_peer'],
1083				'verify_host' => $item['verify_host']
1084			];
1085
1086			$master_item = ($item['type'] == ITEM_TYPE_DEPENDENT) ? ['key' => $item['master_item']['key_']] : [];
1087
1088			if ($item['flags'] == ZBX_FLAG_DISCOVERY_PROTOTYPE) {
1089				$data['discover'] = $item['discover'];
1090			}
1091
1092			$data['master_item'] = $master_item;
1093
1094			if (isset($item['interface_ref'])) {
1095				$data['interface_ref'] = $item['interface_ref'];
1096			}
1097
1098			if ($item['query_fields']) {
1099				$query_fields = [];
1100
1101				foreach ($item['query_fields'] as $query_field) {
1102					$query_fields[] = [
1103						'name' => key($query_field),
1104						'value' => reset($query_field)
1105					];
1106				}
1107
1108				$data['query_fields'] = $query_fields;
1109			}
1110
1111			if ($item['headers']) {
1112				$headers = [];
1113
1114				foreach ($item['headers'] as $name => $value) {
1115					$headers[] = compact('name', 'value');
1116				}
1117
1118				$data['headers'] = $headers;
1119			}
1120
1121			if ($simple_triggers) {
1122				$triggers = [];
1123				foreach ($simple_triggers as $simple_trigger) {
1124					if (bccomp($item['itemid'], $simple_trigger['items'][0]['itemid']) == 0) {
1125						$triggers[] = $simple_trigger;
1126					}
1127				}
1128
1129				if ($triggers) {
1130					$key = array_key_exists('discoveryRule', $item) ? 'trigger_prototypes' : 'triggers';
1131					$data[$key] = $this->formatTriggers($triggers);
1132				}
1133			}
1134
1135			$result[] = $data;
1136		}
1137
1138		return $result;
1139	}
1140
1141	/**
1142	 * Format macros.
1143	 *
1144	 * @param array $macros
1145	 *
1146	 * @return array
1147	 */
1148	protected function formatMacros(array $macros) {
1149		$result = [];
1150
1151		$macros = order_macros($macros, 'macro');
1152
1153		foreach ($macros as $macro) {
1154			$result[] = [
1155				'macro' => $macro['macro'],
1156				'type' => $macro['type'],
1157				'value' => array_key_exists('value', $macro) ? $macro['value'] : '',
1158				'description' => $macro['description']
1159			];
1160		}
1161
1162		return $result;
1163	}
1164
1165	/**
1166	 * Format trigger dependencies.
1167	 *
1168	 * @param array $dependencies
1169	 *
1170	 * @return array
1171	 */
1172	protected function formatDependencies(array $dependencies) {
1173		$result = [];
1174
1175		CArrayHelper::sort($dependencies, ['description', 'expression', 'recovery_expression']);
1176
1177		foreach ($dependencies as $dependency) {
1178			$result[] = [
1179				'name' => $dependency['description'],
1180				'expression' => $dependency['expression'],
1181				'recovery_expression' => $dependency['recovery_expression']
1182			];
1183		}
1184
1185		return $result;
1186	}
1187
1188	/**
1189	 * Format tags.
1190	 *
1191	 * @param array $tags
1192	 *
1193	 * @return array
1194	 */
1195	protected function formatTags(array $tags) {
1196		$result = [];
1197		$fields = [
1198			'tag' => true,
1199			'value' => true,
1200			'operator' => true
1201		];
1202
1203		CArrayHelper::sort($tags, ['tag', 'value']);
1204
1205		foreach ($tags as $tag) {
1206			$result[] = array_intersect_key($tag, $fields);
1207		}
1208
1209		return $result;
1210	}
1211
1212	/**
1213	 * Format dashboards.
1214	 *
1215	 * @param array $dashboards
1216	 *
1217	 * @return array
1218	 */
1219	protected function formatDashboards(array $dashboards) {
1220		$result = [];
1221
1222		CArrayHelper::sort($dashboards, ['name']);
1223
1224		foreach ($dashboards as $dashboard) {
1225			$result[] = [
1226				'uuid' => $dashboard['uuid'],
1227				'name' => $dashboard['name'],
1228				'display_period' => $dashboard['display_period'],
1229				'auto_start' => $dashboard['auto_start'],
1230				'pages' => $this->formatDashboardPages($dashboard['pages'])
1231			];
1232		}
1233
1234		return $result;
1235	}
1236
1237	/**
1238	 * Format dashboard pages.
1239	 *
1240	 * @param array $dashboard_pages
1241	 *
1242	 * @return array
1243	 */
1244	protected function formatDashboardPages(array $dashboard_pages) {
1245		$result = [];
1246
1247		foreach ($dashboard_pages as $dashboard_page) {
1248			$result[] = [
1249				'name' => $dashboard_page['name'],
1250				'display_period' => $dashboard_page['display_period'],
1251				'widgets' => $this->formatWidgets($dashboard_page['widgets'])
1252			];
1253		}
1254
1255		return $result;
1256	}
1257
1258	/**
1259	 * Format widgets.
1260	 *
1261	 * @param array $widgets
1262	 *
1263	 * @return array
1264	 */
1265	protected function formatWidgets(array $widgets) {
1266		$result = [];
1267
1268		CArrayHelper::sort($widgets, ['name']);
1269
1270		foreach ($widgets as $widget) {
1271			$result[] = [
1272				'type' => $widget['type'],
1273				'name' => $widget['name'],
1274				'x' => $widget['x'],
1275				'y' => $widget['y'],
1276				'width' => $widget['width'],
1277				'height' => $widget['height'],
1278				'hide_header' => $widget['view_mode'],
1279				'fields' => $this->formatWidgetFields($widget['fields'])
1280			];
1281		}
1282
1283		return $result;
1284	}
1285
1286	/**
1287	 * Format widget fields.
1288	 *
1289	 * @param array $widgets
1290	 *
1291	 * @return array
1292	 */
1293	protected function formatWidgetFields(array $fields) {
1294		$result = [];
1295
1296		CArrayHelper::sort($fields, ['type']);
1297
1298		foreach ($fields as $field) {
1299			$result[] = [
1300				'type' => $field['type'],
1301				'name' => $field['name'],
1302				'value' => $field['value']
1303			];
1304		}
1305
1306		return $result;
1307	}
1308
1309	/**
1310	 * Format graph items.
1311	 *
1312	 * @param array $graphItems
1313	 *
1314	 * @return array
1315	 */
1316	protected function formatGraphItems(array $graphItems) {
1317		$result = [];
1318
1319		CArrayHelper::sort($graphItems, ['sortorder']);
1320
1321		foreach ($graphItems as $graphItem) {
1322			$result[] = [
1323				'sortorder'=> $graphItem['sortorder'],
1324				'drawtype'=> $graphItem['drawtype'],
1325				'color'=> $graphItem['color'],
1326				'yaxisside'=> $graphItem['yaxisside'],
1327				'calc_fnc'=> $graphItem['calc_fnc'],
1328				'type'=> $graphItem['type'],
1329				'item'=> $graphItem['itemid']
1330			];
1331		}
1332
1333		return $result;
1334	}
1335
1336	/**
1337	 * Format map urls.
1338	 *
1339	 * @param array $urls
1340	 *
1341	 * @return array
1342	 */
1343	protected function formatMapUrls(array $urls) {
1344		$result = [];
1345
1346		CArrayHelper::sort($urls, ['name', 'url']);
1347
1348		foreach ($urls as $url) {
1349			$result[] = [
1350				'name' => $url['name'],
1351				'url' => $url['url'],
1352				'elementtype' => $url['elementtype']
1353			];
1354		}
1355
1356		return $result;
1357	}
1358
1359	/**
1360	 * Format map element urls.
1361	 *
1362	 * @param array $urls
1363	 *
1364	 * @return array
1365	 */
1366	protected function formatMapElementUrls(array $urls) {
1367		$result = [];
1368
1369		CArrayHelper::sort($urls, ['name', 'url']);
1370
1371		foreach ($urls as $url) {
1372			$result[] = [
1373				'name' => $url['name'],
1374				'url' => $url['url']
1375			];
1376		}
1377
1378		return $result;
1379	}
1380
1381	/**
1382	 * Format map links.
1383	 *
1384	 * @param array $links			Map links
1385	 * @param array $selements		Map elements
1386	 *
1387	 * @return array
1388	 */
1389	protected function formatMapLinks(array $links, array $selements) {
1390		$result = [];
1391
1392		// Get array where key is selementid and value is sort position.
1393		$flipped_selements = [];
1394		$selements = array_values($selements);
1395
1396		foreach ($selements as $key => $item) {
1397			if (array_key_exists('selementid', $item)) {
1398				$flipped_selements[$item['selementid']] = $key;
1399			}
1400		}
1401
1402		foreach ($links as &$link) {
1403			$link['selementpos1'] = $flipped_selements[$link['selementid1']];
1404			$link['selementpos2'] = $flipped_selements[$link['selementid2']];
1405
1406			// Sort selements by position asc.
1407			if ($link['selementpos2'] < $link['selementpos1']) {
1408				[$link['selementpos1'], $link['selementpos2']] = [$link['selementpos2'], $link['selementpos1']];
1409			}
1410		}
1411		unset($link);
1412
1413		CArrayHelper::sort($links, ['selementpos1', 'selementpos2']);
1414
1415		foreach ($links as $link) {
1416			$result[] = [
1417				'drawtype' => $link['drawtype'],
1418				'color' => $link['color'],
1419				'label' => $link['label'],
1420				'selementid1' => $link['selementid1'],
1421				'selementid2' => $link['selementid2'],
1422				'linktriggers' => $this->formatMapLinkTriggers($link['linktriggers'])
1423			];
1424		}
1425
1426		return $result;
1427	}
1428
1429	/**
1430	 * Format map link triggers.
1431	 *
1432	 * @param array $linktriggers
1433	 *
1434	 * @return array
1435	 */
1436	protected function formatMapLinkTriggers(array $linktriggers) {
1437		$result = [];
1438
1439		foreach ($linktriggers as &$linktrigger) {
1440			$linktrigger['description'] = $linktrigger['triggerid']['description'];
1441			$linktrigger['expression'] = $linktrigger['triggerid']['expression'];
1442			$linktrigger['recovery_expression'] = $linktrigger['triggerid']['recovery_expression'];
1443		}
1444		unset($linktrigger);
1445
1446		CArrayHelper::sort($linktriggers, ['description', 'expression', 'recovery_expression']);
1447
1448		foreach ($linktriggers as $linktrigger) {
1449			$result[] = [
1450				'drawtype' => $linktrigger['drawtype'],
1451				'color' => $linktrigger['color'],
1452				'trigger' => $linktrigger['triggerid']
1453			];
1454		}
1455
1456		return $result;
1457	}
1458
1459	/**
1460	 * Format map elements.
1461	 *
1462	 * @param array $elements
1463	 *
1464	 * @return array
1465	 */
1466	protected function formatMapElements(array $elements) {
1467		$result = [];
1468
1469		CArrayHelper::sort($elements, ['y', 'x']);
1470
1471		foreach ($elements as $element) {
1472			$result[] = [
1473				'elementtype' => $element['elementtype'],
1474				'label' => $element['label'],
1475				'label_location' => $element['label_location'],
1476				'x' => $element['x'],
1477				'y' => $element['y'],
1478				'elementsubtype' => $element['elementsubtype'],
1479				'areatype' => $element['areatype'],
1480				'width' => $element['width'],
1481				'height' => $element['height'],
1482				'viewtype' => $element['viewtype'],
1483				'use_iconmap' => $element['use_iconmap'],
1484				'selementid' => $element['selementid'],
1485				'elements' => $element['elements'],
1486				'icon_off' => $element['iconid_off'],
1487				'icon_on' => $element['iconid_on'],
1488				'icon_disabled' => $element['iconid_disabled'],
1489				'icon_maintenance' => $element['iconid_maintenance'],
1490				'urls' => $this->formatMapElementUrls($element['urls']),
1491				'evaltype' => $element['evaltype'],
1492				'tags' => $this->formatTags($element['tags'])
1493			];
1494		}
1495
1496		return $result;
1497	}
1498}
1499