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 CMapImporter extends CImporter {
23
24	/**
25	 * Import maps.
26	 *
27	 * @param array $maps
28	 */
29	public function import(array $maps) {
30		$maps = zbx_toHash($maps, 'name');
31
32		$maps = $this->resolveMapElementReferences($maps);
33
34		/*
35		 * Get all importable maps with removed elements and links. First import maps and then update maps with
36		 * elements and links from import file. This way we make sure we are able to resolve any references
37		 * between maps and links that are imported.
38		 */
39		$mapsWithoutElements = $this->getMapsWithoutElements($maps);
40
41		$mapsToProcess = ['createMissing' => [], 'updateExisting' => []];
42
43		foreach ($mapsWithoutElements as $mapName => $mapWithoutElements) {
44			$mapId = $this->referencer->resolveMap($mapWithoutElements['name']);
45			if ($mapId) {
46				// Update sysmapid in source map too.
47				$mapWithoutElements['sysmapid'] = $mapId;
48				$maps[$mapName]['sysmapid'] = $mapId;
49
50				$mapsToProcess['updateExisting'][] = $mapWithoutElements;
51			}
52			else {
53				$mapsToProcess['createMissing'][] = $mapWithoutElements;
54			}
55		}
56
57		if ($this->options['maps']['createMissing'] && $mapsToProcess['createMissing']) {
58			$newMapIds = API::Map()->create($mapsToProcess['createMissing']);
59			foreach ($mapsToProcess['createMissing'] as $num => $map) {
60				$mapId = $newMapIds['sysmapids'][$num];
61				$this->referencer->addMapRef($map['name'], $mapId);
62
63				$maps[$map['name']]['sysmapid'] = $mapId;
64			}
65		}
66
67		if ($this->options['maps']['updateExisting'] && $mapsToProcess['updateExisting']) {
68			API::Map()->update($mapsToProcess['updateExisting']);
69		}
70
71		// Form an array of maps that need to be updated with elements and links, respecting the create/update options.
72		$mapsToUpdate = [];
73		foreach ($mapsToProcess as $mapActionKey => $mapArray) {
74			if ($this->options['maps'][$mapActionKey] && $mapsToProcess[$mapActionKey]) {
75				foreach ($mapArray as $mapItem) {
76					$map = [
77						'sysmapid' => $maps[$mapItem['name']]['sysmapid'],
78						'name' => $mapItem['name'],
79						'shapes' => $maps[$mapItem['name']]['shapes'],
80						'lines' => $maps[$mapItem['name']]['lines'],
81						'selements' => $maps[$mapItem['name']]['selements'],
82						'links' => $maps[$mapItem['name']]['links']
83					];
84					$map = $this->resolveMapReferences($map);
85
86					// Remove the map name so API does not make an update query to the database.
87					unset($map['name']);
88					$mapsToUpdate[] = $map;
89				}
90			}
91		}
92
93		if ($mapsToUpdate) {
94			API::Map()->update($mapsToUpdate);
95		}
96	}
97
98	/**
99	 * Return maps without their elements.
100	 *
101	 * @param array $maps
102	 *
103	 * @return array
104	 */
105	protected function getMapsWithoutElements(array $maps) {
106		foreach ($maps as &$map) {
107			if (array_key_exists('selements', $map)) {
108				unset($map['selements']);
109			}
110			if (array_key_exists('links', $map)) {
111				unset($map['links']);
112			}
113		}
114		unset($map);
115
116		return $maps;
117	}
118
119	/**
120	 * Change all references in map to database ids.
121	 *
122	 * @throws Exception
123	 *
124	 * @param array $map
125	 *
126	 * @return array
127	 */
128	protected function resolveMapReferences(array $map) {
129		if (isset($map['selements'])) {
130			foreach ($map['selements'] as &$selement) {
131				switch ($selement['elementtype']) {
132					case SYSMAP_ELEMENT_TYPE_MAP:
133						$selement['elements'][0]['sysmapid'] = $this->referencer->resolveMap($selement['elements'][0]['name']);
134						if (!$selement['elements'][0]['sysmapid']) {
135							throw new Exception(_s('Cannot find map "%1$s" used in map "%2$s".',
136								$selement['elements'][0]['name'], $map['name']));
137						}
138
139						unset($selement['elements'][0]['name']);
140						break;
141
142					case SYSMAP_ELEMENT_TYPE_HOST_GROUP:
143						$selement['elements'][0]['groupid'] = $this->referencer->resolveGroup($selement['elements'][0]['name']);
144						if (!$selement['elements'][0]['groupid']) {
145							throw new Exception(_s('Cannot find group "%1$s" used in map "%2$s".',
146								$selement['elements'][0]['name'], $map['name']));
147						}
148
149						unset($selement['elements'][0]['name']);
150						break;
151
152					case SYSMAP_ELEMENT_TYPE_HOST:
153						$selement['elements'][0]['hostid'] = $this->referencer->resolveHost($selement['elements'][0]['host']);
154						if (!$selement['elements'][0]['hostid']) {
155							throw new Exception(_s('Cannot find host "%1$s" used in map "%2$s".',
156								$selement['elements'][0]['host'], $map['name']));
157						}
158
159						unset($selement['elements'][0]['host']);
160						break;
161
162					case SYSMAP_ELEMENT_TYPE_TRIGGER:
163						foreach ($selement['elements'] as &$element) {
164							$element['triggerid'] = $this->referencer->resolveTrigger($element['description'],
165								$element['expression'], $element['recovery_expression']
166							);
167
168							if (!$element['triggerid']) {
169								throw new Exception(_s(
170									'Cannot find trigger "%1$s" used in map "%2$s".',
171									$element['description'],
172									$map['name']
173								));
174							}
175
176							unset($element['description'], $element['expression'], $element['recovery_expression']);
177						}
178						unset($element);
179						break;
180
181					case SYSMAP_ELEMENT_TYPE_IMAGE:
182						unset($selement['elements']);
183						break;
184				}
185
186				$icons = [
187					'icon_off' => 'iconid_off',
188					'icon_on' => 'iconid_on',
189					'icon_disabled' => 'iconid_disabled',
190					'icon_maintenance' => 'iconid_maintenance'
191				];
192				foreach ($icons as $element => $field) {
193					if (!empty($selement[$element])) {
194						$image = getImageByIdent($selement[$element]);
195						if (!$image) {
196							throw new Exception(_s('Cannot find icon "%1$s" used in map "%2$s".',
197								$selement[$element]['name'], $map['name']));
198						}
199						$selement[$field] = $image['imageid'];
200					}
201				}
202			}
203			unset($selement);
204		}
205
206		if (isset($map['links'])) {
207			foreach ($map['links'] as &$link) {
208				if (!$link['linktriggers']) {
209					unset($link['linktriggers']);
210					continue;
211				}
212
213				foreach ($link['linktriggers'] as &$linkTrigger) {
214					$trigger = $linkTrigger['trigger'];
215					$triggerId = $this->referencer->resolveTrigger($trigger['description'], $trigger['expression'],
216						$trigger['recovery_expression']
217					);
218
219					if (!$triggerId) {
220						throw new Exception(_s(
221							'Cannot find trigger "%1$s" used in map "%2$s".',
222							$trigger['description'],
223							$map['name']
224						));
225					}
226
227					$linkTrigger['triggerid'] = $triggerId;
228				}
229				unset($linkTrigger);
230			}
231			unset($link);
232		}
233
234		return $map;
235	}
236
237	/**
238	 * Resolves the iconmap and background images for the maps.
239	 *
240	 * @throws Exception if icon map or background image is not found.
241	 *
242	 * @param array $maps
243	 *
244	 * @return array
245	 */
246	protected function resolveMapElementReferences(array $maps) {
247		foreach ($maps as &$map) {
248			if (isset($map['iconmap']) && $map['iconmap']) {
249				$map['iconmapid'] = $this->referencer->resolveIconMap($map['iconmap']['name']);
250
251				if (!$map['iconmapid']) {
252					throw new Exception(_s('Cannot find icon map "%1$s" used in map "%2$s".',
253						$map['iconmap']['name'], $map['name']
254					));
255				}
256			}
257
258			if (isset($map['background']) && $map['background']) {
259				$image = getImageByIdent($map['background']);
260
261				if (!$image) {
262					throw new Exception(_s('Cannot find background image "%1$s" used in map "%2$s".',
263						$map['background']['name'], $map['name']
264					));
265				}
266				$map['backgroundid'] = $image['imageid'];
267			}
268		}
269		unset($map);
270
271		return $maps;
272	}
273}
274