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 containing methods for operations with value maps.
24 */
25class CValueMap extends CApiService {
26
27	public const ACCESS_RULES = [
28		'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
29		'create' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
30		'update' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN],
31		'delete' => ['min_user_type' => USER_TYPE_ZABBIX_ADMIN]
32	];
33
34	protected $tableName = 'valuemap';
35	protected $tableAlias = 'vm';
36	protected $sortColumns = ['valuemapid', 'name'];
37
38	/**
39	 * Get value maps.
40	 *
41	 * @param array  $options
42	 *
43	 * @return array
44	 */
45	public function get($options = []) {
46		$api_input_rules = ['type' => API_OBJECT, 'fields' => [
47			// filter
48			'valuemapids' =>			['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
49			'hostids' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE, 'default' => null],
50			'filter' =>					['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [
51				'valuemapid' =>				['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
52				'hostid' =>					['type' => API_IDS, 'flags' => API_ALLOW_NULL | API_NORMALIZE],
53				'name' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
54			]],
55			'search' =>					['type' => API_OBJECT, 'flags' => API_ALLOW_NULL, 'default' => null, 'fields' => [
56				'name' =>					['type' => API_STRINGS_UTF8, 'flags' => API_ALLOW_NULL | API_NORMALIZE]
57			]],
58			'searchByAny' =>			['type' => API_BOOLEAN, 'default' => false],
59			'startSearch' =>			['type' => API_FLAG, 'default' => false],
60			'excludeSearch' =>			['type' => API_FLAG, 'default' => false],
61			'searchWildcardsEnabled' =>	['type' => API_BOOLEAN, 'default' => false],
62			// output
63			'output' =>					['type' => API_OUTPUT, 'in' => implode(',', ['valuemapid', 'uuid', 'name', 'hostid']), 'default' => API_OUTPUT_EXTEND],
64			'selectMappings' =>			['type' => API_OUTPUT, 'flags' => API_ALLOW_NULL | API_ALLOW_COUNT, 'in' => implode(',', ['type', 'value', 'newvalue']), 'default' => null],
65			'countOutput' =>			['type' => API_FLAG, 'default' => false],
66			// sort and limit
67			'sortfield' =>				['type' => API_STRINGS_UTF8, 'flags' => API_NORMALIZE, 'in' => implode(',', $this->sortColumns), 'uniq' => true, 'default' => []],
68			'sortorder' =>				['type' => API_SORTORDER, 'default' => []],
69			'limit' =>					['type' => API_INT32, 'flags' => API_ALLOW_NULL, 'in' => '1:'.ZBX_MAX_INT32, 'default' => null],
70			// flags
71			'editable' =>				['type' => API_BOOLEAN, 'default' => false],
72			'preservekeys' =>			['type' => API_BOOLEAN, 'default' => false]
73		]];
74		if (!CApiInputValidator::validate($api_input_rules, $options, '/', $error)) {
75			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
76		}
77
78		$db_valuemaps = [];
79		$sql_parts = $this->createSelectQueryParts($this->tableName(), $this->tableAlias(), $options);
80
81		// Permission check.
82		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
83			$permission = $options['editable'] ? PERM_READ_WRITE : PERM_READ;
84			$userGroups = getUserGroupsByUserId(self::$userData['userid']);
85
86			$sql_parts['where'][] = 'EXISTS ('.
87				'SELECT NULL'.
88				' FROM hosts_groups hgg'.
89					' JOIN rights r'.
90						' ON r.id=hgg.groupid'.
91							' AND '.dbConditionInt('r.groupid', $userGroups).
92				' WHERE vm.hostid=hgg.hostid'.
93				' GROUP BY hgg.hostid'.
94				' HAVING MIN(r.permission)>'.PERM_DENY.
95					' AND MAX(r.permission)>='.zbx_dbstr($permission).
96				')';
97		}
98
99		// hostids
100		if ($options['hostids'] !== null) {
101			$sql_parts['where']['hostid'] = dbConditionInt('vm.hostid', $options['hostids']);
102		}
103
104		$result = DBselect(self::createSelectQueryFromParts($sql_parts), $options['limit']);
105
106		while ($row = DBfetch($result)) {
107			if ($options['countOutput']) {
108				return $row['rowscount'];
109			}
110
111			$db_valuemaps[$row['valuemapid']] = $row;
112		}
113
114		if ($db_valuemaps) {
115			$db_valuemaps = $this->addRelatedObjects($options, $db_valuemaps);
116			$db_valuemaps = $this->unsetExtraFields($db_valuemaps, ['valuemapid'], $options['output']);
117
118			if (!$options['preservekeys']) {
119				$db_valuemaps = zbx_cleanHashes($db_valuemaps);
120			}
121		}
122
123		return $db_valuemaps;
124	}
125
126	/**
127	 * @param array $valuemaps
128	 *
129	 * @return array
130	 *
131	 * @throws APIException
132	 */
133	public function create(array $valuemaps) {
134		$this->validateCreate($valuemaps);
135
136		$valuemapids = DB::insertBatch('valuemap', $valuemaps);
137
138		$mappings = [];
139
140		foreach ($valuemaps as $index => &$valuemap) {
141			$valuemap['valuemapid'] = $valuemapids[$index];
142			$sortorder = 0;
143
144			foreach ($valuemap['mappings'] as $mapping) {
145				$mappings[] = [
146					'type' => array_key_exists('type', $mapping) ? $mapping['type'] : VALUEMAP_MAPPING_TYPE_EQUAL,
147					'valuemapid' => $valuemap['valuemapid'],
148					'value' => array_key_exists('value', $mapping) ? $mapping['value'] : '',
149					'newvalue' => $mapping['newvalue'],
150					'sortorder' => $sortorder++
151				];
152			}
153		}
154		unset($valuemap);
155
156		DB::insertBatch('valuemap_mapping', $mappings);
157
158		$this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_VALUE_MAP, $valuemaps);
159
160		return ['valuemapids' => $valuemapids];
161	}
162
163	/**
164	 * @param array $valuemap
165	 *
166	 * @return array
167	 */
168	public function update(array $valuemaps) {
169		$this->validateUpdate($valuemaps, $db_valuemaps);
170
171		$upd_valuemaps = [];
172		$valuemaps_mappings = [];
173
174		foreach ($valuemaps as $valuemap) {
175			$valuemapid = $valuemap['valuemapid'];
176
177			$db_valuemap = $db_valuemaps[$valuemapid];
178
179			if (array_key_exists('name', $valuemap) && $valuemap['name'] !== $db_valuemap['name']) {
180				$upd_valuemaps[] = [
181					'values' => ['name' => $valuemap['name']],
182					'where' => ['valuemapid' => $valuemap['valuemapid']]
183				];
184			}
185
186			if (array_key_exists('mappings', $valuemap)) {
187				$valuemaps_mappings[$valuemapid] = [];
188				$sortorder = 0;
189
190				foreach ($valuemap['mappings'] as $mapping) {
191					$mapping += ['type' => VALUEMAP_MAPPING_TYPE_EQUAL, 'value' => ''];
192					$valuemaps_mappings[$valuemapid][] = [
193						'type' => $mapping['type'],
194						'value' => $mapping['value'],
195						'newvalue' => $mapping['newvalue'],
196						'sortorder' => $sortorder++
197					];
198				}
199			}
200		}
201
202		if ($upd_valuemaps) {
203			DB::update('valuemap', $upd_valuemaps);
204		}
205
206		if ($valuemaps_mappings) {
207			$db_mappings = DB::select('valuemap_mapping', [
208				'output' => ['valuemap_mappingid', 'valuemapid', 'type', 'value', 'newvalue', 'sortorder'],
209				'filter' => ['valuemapid' => array_keys($valuemaps_mappings)]
210			]);
211			CArrayHelper::sort($db_mappings, [['field' => 'sortorder', 'order' => ZBX_SORT_UP]]);
212
213			$ins_mapings = [];
214			$upd_mapings = [];
215			$del_mapingids = [];
216			$valuemapid_db_mappings = array_fill_keys(array_keys($valuemaps_mappings), []);
217
218			foreach ($db_mappings as $db_mapping) {
219				$valuemapid_db_mappings[$db_mapping['valuemapid']][] = $db_mapping;
220			}
221
222			foreach ($valuemaps_mappings as $valuemapid => $mappings) {
223				$db_mappings = &$valuemapid_db_mappings[$valuemapid];
224
225				foreach ($mappings as $mapping) {
226					$exists = false;
227
228					foreach ($db_mappings as $i => $db_mapping) {
229						if ($db_mapping['type'] == $mapping['type'] && $db_mapping['value'] == $mapping['value']) {
230							$exists = true;
231							break;
232						}
233					}
234
235					if (!$exists) {
236						$ins_mapings[] = ['valuemapid' => $valuemapid] + $mapping;
237						continue;
238					}
239
240					$update_fields = array_diff_assoc($mapping, $db_mapping);
241
242					if ($update_fields) {
243						$upd_mapings[] = [
244							'values' => $update_fields,
245							'where' => ['valuemap_mappingid' => $db_mapping['valuemap_mappingid']]
246						];
247					}
248
249					unset($db_mappings[$i]);
250				}
251			}
252			unset($db_mappings);
253
254			foreach ($valuemapid_db_mappings as $db_mappings) {
255				if ($db_mappings) {
256					$del_mapingids = array_merge($del_mapingids, array_column($db_mappings, 'valuemap_mappingid'));
257				}
258			}
259
260			if ($del_mapingids) {
261				DB::delete('valuemap_mapping', ['valuemap_mappingid' => $del_mapingids]);
262			}
263
264			if ($upd_mapings) {
265				DB::update('valuemap_mapping', $upd_mapings);
266			}
267
268			if ($ins_mapings) {
269				DB::insertBatch('valuemap_mapping', $ins_mapings);
270			}
271		}
272
273		$this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_VALUE_MAP, $valuemaps, $db_valuemaps);
274
275		return ['valuemapids' => array_column($valuemaps, 'valuemapid')];
276	}
277
278	/**
279	 * @param array $valuemapids
280	 *
281	 * @return array
282	 */
283	public function delete(array $valuemapids) {
284		$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
285		if (!CApiInputValidator::validate($api_input_rules, $valuemapids, '/', $error)) {
286			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
287		}
288
289		$db_valuemaps = $this->get([
290			'output' => ['valuemapid', 'name'],
291			'valuemapids' => $valuemapids,
292			'editable' => true,
293			'preservekeys' => true
294		]);
295
296		foreach ($valuemapids as $valuemapid) {
297			if (!array_key_exists($valuemapid, $db_valuemaps)) {
298				self::exception(ZBX_API_ERROR_PERMISSIONS,
299					_('No permissions to referred object or it does not exist!')
300				);
301			}
302		}
303
304		DB::update('items', [[
305			'values' => ['valuemapid' => 0],
306			'where' => ['valuemapid' => $valuemapids]
307		]]);
308
309		$this->deleteByIds($valuemapids);
310
311		$this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_VALUE_MAP, $db_valuemaps);
312
313		return ['valuemapids' => $valuemapids];
314	}
315
316	/**
317	 * Check for duplicated value maps.
318	 *
319	 * @param array $names_by_hostid
320	 *
321	 * @throws APIException  if value map already exists.
322	 */
323	private function checkDuplicates(array $names_by_hostid) {
324		$sql_where = [];
325		foreach ($names_by_hostid as $hostid => $names) {
326			$sql_where[] = '(vm.hostid='.$hostid.' AND '.dbConditionString('vm.name', $names).')';
327		}
328
329		$db_valuemaps = DBfetchArray(
330			DBselect('SELECT vm.name FROM valuemap vm WHERE '.implode(' OR ', $sql_where), 1)
331		);
332
333		if ($db_valuemaps) {
334			self::exception(ZBX_API_ERROR_PARAMETERS,
335				_s('Value map "%1$s" already exists.', $db_valuemaps[0]['name'])
336			);
337		}
338	}
339
340	/**
341	 * @param array $valuemaps
342	 *
343	 * @throws APIException if the input is invalid.
344	 */
345	private function validateCreate(array &$valuemaps) {
346		$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['uuid'], ['hostid', 'name']], 'fields' => [
347			'hostid' =>		['type' => API_ID, 'flags' => API_REQUIRED | API_NOT_EMPTY],
348			'uuid' =>		['type' => API_UUID],
349			'name' =>		['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap', 'name')],
350			'mappings' =>	['type' => API_OBJECTS, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'fields' => [
351				'type' =>		['type' => API_INT32, 'default' => VALUEMAP_MAPPING_TYPE_EQUAL, 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP, VALUEMAP_MAPPING_TYPE_DEFAULT])],
352				'value' =>		['type' => API_MULTIPLE, 'rules' => [
353					[
354						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL])],
355						'type' => API_STRING_UTF8,
356						'length' => DB::getFieldLength('valuemap_mapping', 'value')
357					],
358					[
359						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL])],
360						'type' => API_FLOAT,
361						'length' => DB::getFieldLength('valuemap_mapping', 'value')
362					],
363					[
364						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_IN_RANGE])],
365						'type' => API_NUMERIC_RANGES,
366						'flags' => API_NOT_EMPTY,
367						'length' => DB::getFieldLength('valuemap_mapping', 'value')
368					],
369					[
370						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_REGEXP])],
371						'type' => API_REGEX,
372						'flags' => API_NOT_EMPTY,
373						'length' => DB::getFieldLength('valuemap_mapping', 'value')
374					],
375					[
376						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_DEFAULT])],
377						'type' => API_STRING_UTF8
378					]
379				]],
380				'newvalue' =>	['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap_mapping', 'newvalue')]
381			]]
382		]];
383		if (!CApiInputValidator::validate($api_input_rules, $valuemaps, '/', $error)) {
384			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
385		}
386
387		$this->validateValuemapMappings($valuemaps);
388		$hostids = [];
389
390		foreach ($valuemaps as $valuemap) {
391			$hostids[$valuemap['hostid']] = true;
392		}
393
394		$db_hosts = API::Host()->get([
395			'output' => ['status'],
396			'hostids' => array_keys($hostids),
397			'templated_hosts' => true,
398			'editable' => true,
399			'preservekeys' => true
400		]);
401
402		$names_by_hostid = [];
403
404		foreach ($valuemaps as $valuemap) {
405			// check permissions by hostid
406			if (!array_key_exists($valuemap['hostid'], $db_hosts)) {
407				self::exception(ZBX_API_ERROR_PERMISSIONS,
408					_('No permissions to referred object or it does not exist!')
409				);
410			}
411
412			$names_by_hostid[$valuemap['hostid']][] = $valuemap['name'];
413		}
414
415		$this->checkAndAddUuid($valuemaps, $db_hosts);
416		$this->checkDuplicates($names_by_hostid);
417	}
418
419	/**
420	 * Check that only value maps on templates have UUID. Add UUID to all value maps on templates, if it doesn't exist.
421	 *
422	 * @param array $valuemaps_to_create
423	 * @param array $db_hosts
424	 *
425	 * @throws APIException
426	 */
427	protected function checkAndAddUuid(array &$valuemaps_to_create, array $db_hosts): void {
428		foreach ($valuemaps_to_create as $index => &$valuemap) {
429			if ($db_hosts[$valuemap['hostid']]['status'] != HOST_STATUS_TEMPLATE
430					&& array_key_exists('uuid', $valuemap)) {
431				self::exception(ZBX_API_ERROR_PARAMETERS,
432					_s('Invalid parameter "%1$s": %2$s.', '/'.($index + 1), _s('unexpected parameter "%1$s"', 'uuid'))
433				);
434			}
435
436			if ($db_hosts[$valuemap['hostid']]['status'] == HOST_STATUS_TEMPLATE
437					&& !array_key_exists('uuid', $valuemap)) {
438				$valuemap['uuid'] = generateUuidV4();
439			}
440		}
441		unset($valuemap);
442
443		$db_uuid = DB::select('valuemap', [
444			'output' => ['uuid'],
445			'filter' => ['uuid' => array_column($valuemaps_to_create, 'uuid')],
446			'limit' => 1
447		]);
448
449		if ($db_uuid) {
450			self::exception(ZBX_API_ERROR_PARAMETERS,
451				_s('Entry with UUID "%1$s" already exists.', $db_uuid[0]['uuid'])
452			);
453		}
454	}
455
456	/**
457	 * @param array $valuemaps
458	 * @param array $db_valuemaps
459	 *
460	 * @throws APIException if the input is invalid.
461	 */
462	private function validateUpdate(array &$valuemaps, array &$db_valuemaps = null) {
463		$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['valuemapid']], 'fields' => [
464			'valuemapid' =>	['type' => API_ID, 'flags' => API_REQUIRED],
465			'name' =>		['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap', 'name')],
466			'mappings' =>	['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY, 'fields' => [
467				'type' =>		['type' => API_INT32, 'default' => VALUEMAP_MAPPING_TYPE_EQUAL, 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP, VALUEMAP_MAPPING_TYPE_DEFAULT])],
468				'value' =>		['type' => API_MULTIPLE, 'rules' => [
469					[
470						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_EQUAL])],
471						'type' => API_STRING_UTF8,
472						'length' => DB::getFieldLength('valuemap_mapping', 'value')
473					],
474					[
475						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_GREATER_EQUAL, VALUEMAP_MAPPING_TYPE_LESS_EQUAL])],
476						'type' => API_FLOAT,
477						'length' => DB::getFieldLength('valuemap_mapping', 'value')
478					],
479					[
480						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_IN_RANGE])],
481						'type' => API_NUMERIC_RANGES,
482						'flags' => API_NOT_EMPTY,
483						'length' => DB::getFieldLength('valuemap_mapping', 'value')
484					],
485					[
486						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_REGEXP])],
487						'type' => API_REGEX,
488						'flags' => API_NOT_EMPTY,
489						'length' => DB::getFieldLength('valuemap_mapping', 'value')
490					],
491					[
492						'if' => ['field' => 'type', 'in' => implode(',', [VALUEMAP_MAPPING_TYPE_DEFAULT])],
493						'type' => API_STRING_UTF8
494					]
495				]],
496				'newvalue' =>	['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('valuemap_mapping', 'newvalue')]
497			]]
498		]];
499		if (!CApiInputValidator::validate($api_input_rules, $valuemaps, '/', $error)) {
500			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
501		}
502
503		$this->validateValuemapMappings($valuemaps);
504		$db_valuemaps = $this->get([
505			'output' => ['valuemapid', 'hostid', 'name'],
506			'valuemapids' => array_column($valuemaps, 'valuemapid'),
507			'editable' => true,
508			'preservekeys' => true
509		]);
510
511		foreach ($valuemaps as $valuemap) {
512			if (!array_key_exists($valuemap['valuemapid'], $db_valuemaps)) {
513				self::exception(ZBX_API_ERROR_PERMISSIONS,
514					_('No permissions to referred object or it does not exist!')
515				);
516			}
517		}
518
519		$valuemaps = $this->extendObjectsByKey($valuemaps, $db_valuemaps, 'valuemapid', ['hostid']);
520
521		$api_input_rules = ['type' => API_OBJECTS, 'uniq' => [['hostid', 'name']], 'fields' => [
522			'hostid' =>	['type' => API_ID],
523			'name' =>	['type' => API_STRING_UTF8]
524		]];
525
526		if (!CApiInputValidator::validateUniqueness($api_input_rules, $valuemaps, '/', $error)) {
527			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
528		}
529
530		$names_by_hostid = [];
531
532		foreach ($valuemaps as $valuemap) {
533			$db_valuemap = $db_valuemaps[$valuemap['valuemapid']];
534
535			if (array_key_exists('name', $valuemap) && $valuemap['name'] !== $db_valuemap['name']) {
536				$names_by_hostid[$valuemap['hostid']][] = $valuemap['name'];
537			}
538		}
539
540		if ($names_by_hostid) {
541			$this->checkDuplicates($names_by_hostid);
542		}
543	}
544
545	/**
546	 * Validate uniqueness of mapping value in value maps, type VALUEMAP_MAPPING_TYPE_DEFAULT can be defined only once
547	 * per value map mappings.
548	 *
549	 * @param array $valuemaps  Array of valuemaps
550	 *
551	 * @throws Exception when non uniquw
552	 */
553	protected function validateValuemapMappings(array $valuemaps) {
554		$i = 0;
555		$error = '';
556
557		foreach ($valuemaps as $valuemap) {
558			$i++;
559
560			if (!array_key_exists('mappings', $valuemap)) {
561				continue;
562			}
563
564			$type_uniq = array_fill_keys([VALUEMAP_MAPPING_TYPE_EQUAL, VALUEMAP_MAPPING_TYPE_GREATER_EQUAL,
565					VALUEMAP_MAPPING_TYPE_LESS_EQUAL, VALUEMAP_MAPPING_TYPE_IN_RANGE, VALUEMAP_MAPPING_TYPE_REGEXP
566				], []
567			);
568			$has_default = false;
569
570			foreach (array_values($valuemap['mappings']) as $j => $mapping) {
571				$type = array_key_exists('type', $mapping) ? $mapping['type'] : VALUEMAP_MAPPING_TYPE_EQUAL;
572				$value = array_key_exists('value', $mapping) ? (string) $mapping['value'] : '';
573
574				if ($has_default && $type == VALUEMAP_MAPPING_TYPE_DEFAULT) {
575					$error = _s('value %1$s already exists', '(type)=('.VALUEMAP_MAPPING_TYPE_DEFAULT.')');
576				}
577				elseif (!array_key_exists('value', $mapping) && $type != VALUEMAP_MAPPING_TYPE_DEFAULT) {
578					$error = _s('the parameter "%1$s" is missing', 'value');
579				}
580				elseif ($value !== '' && $type == VALUEMAP_MAPPING_TYPE_DEFAULT) {
581					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
582						sprintf('/%1$s/mappings/%2$s/value', $i, $j + 1),
583						_('should be empty')
584					));
585				}
586				elseif ($type != VALUEMAP_MAPPING_TYPE_DEFAULT && array_key_exists($value, $type_uniq[$type])) {
587					$error = _s('value %1$s already exists', '(value)=('.$value.')');
588				}
589
590				if ($error !== '') {
591					self::exception(ZBX_API_ERROR_PARAMETERS, _s('Invalid parameter "%1$s": %2$s.',
592						sprintf('/%1$s/mappings/%2$s', $i, $j + 1),
593						$error
594					));
595				}
596
597				$has_default = ($has_default || $type == VALUEMAP_MAPPING_TYPE_DEFAULT);
598				$type_uniq[$type][$value] = true;
599			}
600		}
601	}
602
603	protected function addRelatedObjects(array $options, array $db_valuemaps) {
604		$db_valuemaps = parent::addRelatedObjects($options, $db_valuemaps);
605
606		// Select mappings for value map.
607		if ($options['selectMappings'] !== null) {
608			$def_mappings = ($options['selectMappings'] == API_OUTPUT_COUNT) ? '0' : [];
609
610			foreach ($db_valuemaps as $valuemapid => $db_valuemap) {
611				$db_valuemaps[$valuemapid]['mappings'] = $def_mappings;
612			}
613
614			if ($options['selectMappings'] == API_OUTPUT_COUNT) {
615				$db_mappings = DBselect(
616					'SELECT m.valuemapid,COUNT(*) AS cnt'.
617					' FROM valuemap_mapping m'.
618					' WHERE '.dbConditionInt('m.valuemapid', array_keys($db_valuemaps)).
619					' GROUP BY m.valuemapid'
620				);
621
622				while ($db_mapping = DBfetch($db_mappings)) {
623					$db_valuemaps[$db_mapping['valuemapid']]['mappings'] = $db_mapping['cnt'];
624				}
625			}
626			else {
627				$db_mappings = API::getApiService()->select('valuemap_mapping', [
628					'output' => $this->outputExtend($options['selectMappings'], ['valuemapid',
629						'valuemap_mappingid', 'sortorder'
630					]),
631					'filter' => ['valuemapid' => array_keys($db_valuemaps)]
632				]);
633				CArrayHelper::sort($db_mappings, [['field' => 'sortorder', 'order' => ZBX_SORT_UP]]);
634
635				foreach ($db_mappings as $db_mapping) {
636					$valuemapid = $db_mapping['valuemapid'];
637					unset($db_mapping['valuemap_mappingid'], $db_mapping['valuemapid'], $db_mapping['sortorder']);
638
639					$db_valuemaps[$valuemapid]['mappings'][] = $db_mapping;
640				}
641			}
642		}
643
644		return $db_valuemaps;
645	}
646}
647