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 user groups.
24 */
25class CUserGroup extends CApiService {
26
27	public const ACCESS_RULES = [
28		'get' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
29		'create' => ['min_user_type' => USER_TYPE_SUPER_ADMIN],
30		'update' => ['min_user_type' => USER_TYPE_SUPER_ADMIN],
31		'delete' => ['min_user_type' => USER_TYPE_SUPER_ADMIN]
32	];
33
34	protected $tableName = 'usrgrp';
35	protected $tableAlias = 'g';
36	protected $sortColumns = ['usrgrpid', 'name'];
37
38	/**
39	 * Get user groups.
40	 *
41	 * @param array  $options
42	 * @param array  $options['usrgrpids']
43	 * @param array  $options['userids']
44	 * @param bool   $options['status']
45	 * @param bool   $options['selectUsers']
46	 * @param int    $options['count']
47	 * @param string $options['pattern']
48	 * @param int    $options['limit']
49	 * @param string $options['order']
50	 *
51	 * @return array
52	 */
53	public function get($options = []) {
54		$result = [];
55
56		$sqlParts = [
57			'select'	=> ['usrgrp' => 'g.usrgrpid'],
58			'from'		=> ['usrgrp' => 'usrgrp g'],
59			'where'		=> [],
60			'order'		=> [],
61			'limit'		=> null
62		];
63
64		$defOptions = [
65			'usrgrpids'					=> null,
66			'userids'					=> null,
67			'status'					=> null,
68			// filter
69			'filter'					=> null,
70			'search'					=> null,
71			'searchByAny'				=> null,
72			'startSearch'				=> false,
73			'excludeSearch'				=> false,
74			'searchWildcardsEnabled'	=> null,
75			// output
76			'editable'					=> false,
77			'output'					=> API_OUTPUT_EXTEND,
78			'selectUsers'				=> null,
79			'selectRights'				=> null,
80			'selectTagFilters'			=> null,
81			'countOutput'				=> false,
82			'preservekeys'				=> false,
83			'sortfield'					=> '',
84			'sortorder'					=> '',
85			'limit'						=> null
86		];
87
88		$options = zbx_array_merge($defOptions, $options);
89
90		// permissions
91		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
92			if (!$options['editable']) {
93				$sqlParts['where'][] = 'g.usrgrpid IN ('.
94					'SELECT uug.usrgrpid'.
95					' FROM users_groups uug'.
96					' WHERE uug.userid='.self::$userData['userid'].
97				')';
98			}
99			else {
100				return [];
101			}
102		}
103
104		// usrgrpids
105		if (!is_null($options['usrgrpids'])) {
106			zbx_value2array($options['usrgrpids']);
107
108			$sqlParts['where'][] = dbConditionInt('g.usrgrpid', $options['usrgrpids']);
109		}
110
111		// userids
112		if (!is_null($options['userids'])) {
113			zbx_value2array($options['userids']);
114
115			$sqlParts['from']['users_groups'] = 'users_groups ug';
116			$sqlParts['where'][] = dbConditionInt('ug.userid', $options['userids']);
117			$sqlParts['where']['gug'] = 'g.usrgrpid=ug.usrgrpid';
118		}
119
120		// status
121		if (!is_null($options['status'])) {
122			$sqlParts['where'][] = 'g.users_status='.zbx_dbstr($options['status']);
123		}
124
125		// filter
126		if (is_array($options['filter'])) {
127			$this->dbFilter('usrgrp g', $options, $sqlParts);
128		}
129
130		// search
131		if (is_array($options['search'])) {
132			zbx_db_search('usrgrp g', $options, $sqlParts);
133		}
134
135		// limit
136		if (zbx_ctype_digit($options['limit']) && $options['limit']) {
137			$sqlParts['limit'] = $options['limit'];
138		}
139
140		$sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
141		$sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts);
142		$res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']);
143		while ($usrgrp = DBfetch($res)) {
144			if ($options['countOutput']) {
145				$result = $usrgrp['rowscount'];
146			}
147			else {
148				$result[$usrgrp['usrgrpid']] = $usrgrp;
149			}
150		}
151
152		if ($options['countOutput']) {
153			return $result;
154		}
155
156		if ($result) {
157			$result = $this->addRelatedObjects($options, $result);
158		}
159
160		// removing keys (hash -> array)
161		if (!$options['preservekeys']) {
162			$result = zbx_cleanHashes($result);
163		}
164
165		return $result;
166	}
167
168	/**
169	 * @param array  $usrgrps
170	 *
171	 * @return array
172	 */
173	public function create(array $usrgrps) {
174		$this->validateCreate($usrgrps);
175
176		$ins_usrgrps = [];
177
178		foreach ($usrgrps as $usrgrp) {
179			unset($usrgrp['rights'], $usrgrp['userids']);
180			$ins_usrgrps[] = $usrgrp;
181		}
182		$usrgrpids = DB::insert('usrgrp', $ins_usrgrps);
183
184		foreach ($usrgrps as $index => &$usrgrp) {
185			$usrgrp['usrgrpid'] = $usrgrpids[$index];
186		}
187		unset($usrgrp);
188
189		$this->updateRights($usrgrps, __FUNCTION__);
190		$this->updateTagFilters($usrgrps, __FUNCTION__);
191		$this->updateUsersGroups($usrgrps, __FUNCTION__);
192
193		$this->addAuditBulk(AUDIT_ACTION_ADD, AUDIT_RESOURCE_USER_GROUP, $usrgrps);
194
195		return ['usrgrpids' => $usrgrpids];
196	}
197
198	/**
199	 * @param array $usrgrps
200	 *
201	 * @throws APIException if the input is invalid.
202	 */
203	private function validateCreate(array &$usrgrps) {
204		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
205			self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only Super Admins can create user groups.'));
206		}
207
208		$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['name']], 'fields' => [
209			'name' =>			['type' => API_STRING_UTF8, 'flags' => API_REQUIRED | API_NOT_EMPTY, 'length' => DB::getFieldLength('usrgrp', 'name')],
210			'debug_mode' =>		['type' => API_INT32, 'in' => implode(',', [GROUP_DEBUG_MODE_DISABLED, GROUP_DEBUG_MODE_ENABLED])],
211			'gui_access' =>		['type' => API_INT32, 'in' => implode(',', [GROUP_GUI_ACCESS_SYSTEM, GROUP_GUI_ACCESS_INTERNAL, GROUP_GUI_ACCESS_LDAP, GROUP_GUI_ACCESS_DISABLED])],
212			'users_status' =>	['type' => API_INT32, 'in' => implode(',', [GROUP_STATUS_ENABLED, GROUP_STATUS_DISABLED])],
213			'rights' =>			['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['id']], 'fields' => [
214				'id' =>				['type' => API_ID, 'flags' => API_REQUIRED],
215				'permission' =>		['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [PERM_DENY, PERM_READ, PERM_READ_WRITE])]
216			]],
217			'tag_filters' =>	['type' => API_OBJECTS, 'uniq' => [['groupid', 'tag', 'value']], 'fields' => [
218				'groupid' =>		['type' => API_ID, 'flags' => API_REQUIRED],
219				'tag' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('tag_filter', 'tag'), 'default' => DB::getDefault('tag_filter', 'tag')],
220				'value' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('tag_filter', 'value'), 'default' => DB::getDefault('tag_filter', 'value')]
221			]],
222			'userids' =>		['type' => API_IDS, 'flags' => API_NORMALIZE]
223		]];
224		if (!CApiInputValidator::validate($api_input_rules, $usrgrps, '/', $error)) {
225			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
226		}
227
228		$this->checkDuplicates(zbx_objectValues($usrgrps, 'name'));
229		$this->checkUsers($usrgrps);
230		$this->checkHimself($usrgrps, __FUNCTION__);
231		$this->checkHostGroups($usrgrps);
232		$this->checkTagFilters($usrgrps);
233	}
234
235	/**
236	 * @param array  $usrgrps
237	 *
238	 * @return array
239	 */
240	public function update($usrgrps) {
241		$this->validateUpdate($usrgrps, $db_usrgrps);
242
243		$upd_usrgrps = [];
244
245		foreach ($usrgrps as $usrgrp) {
246			$db_usrgrp = $db_usrgrps[$usrgrp['usrgrpid']];
247
248			$upd_usrgrp = [];
249
250			if (array_key_exists('name', $usrgrp) && $usrgrp['name'] !== $db_usrgrp['name']) {
251				$upd_usrgrp['name'] = $usrgrp['name'];
252			}
253			if (array_key_exists('debug_mode', $usrgrp) && $usrgrp['debug_mode'] != $db_usrgrp['debug_mode']) {
254				$upd_usrgrp['debug_mode'] = $usrgrp['debug_mode'];
255			}
256			if (array_key_exists('gui_access', $usrgrp) && $usrgrp['gui_access'] != $db_usrgrp['gui_access']) {
257				$upd_usrgrp['gui_access'] = $usrgrp['gui_access'];
258			}
259			if (array_key_exists('users_status', $usrgrp) && $usrgrp['users_status'] != $db_usrgrp['users_status']) {
260				$upd_usrgrp['users_status'] = $usrgrp['users_status'];
261			}
262
263			if ($upd_usrgrp) {
264				$upd_usrgrps[] = [
265					'values' => $upd_usrgrp,
266					'where' => ['usrgrpid' => $usrgrp['usrgrpid']]
267				];
268			}
269		}
270
271		if ($upd_usrgrps) {
272			DB::update('usrgrp', $upd_usrgrps);
273		}
274
275		$this->updateRights($usrgrps, __FUNCTION__);
276		$this->updateTagFilters($usrgrps, __FUNCTION__);
277		$this->updateUsersGroups($usrgrps, __FUNCTION__);
278
279		$this->addAuditBulk(AUDIT_ACTION_UPDATE, AUDIT_RESOURCE_USER_GROUP, $usrgrps, $db_usrgrps);
280
281		return ['usrgrpids'=> zbx_objectValues($usrgrps, 'usrgrpid')];
282	}
283
284	/**
285	 * @param array $usrgrps
286	 * @param array $db_usrgrps
287	 *
288	 * @throws APIException if the input is invalid.
289	 */
290	private function validateUpdate(array &$usrgrps, array &$db_usrgrps = null) {
291		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
292			self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only Super Admins can update user groups.'));
293		}
294
295		$api_input_rules = ['type' => API_OBJECTS, 'flags' => API_NOT_EMPTY | API_NORMALIZE, 'uniq' => [['usrgrpid'], ['name']], 'fields' => [
296			'usrgrpid' =>		['type' => API_ID, 'flags' => API_REQUIRED],
297			'name' =>			['type' => API_STRING_UTF8, 'flags' => API_NOT_EMPTY, 'length' => DB::getFieldLength('usrgrp', 'name')],
298			'debug_mode' =>		['type' => API_INT32, 'in' => implode(',', [GROUP_DEBUG_MODE_DISABLED, GROUP_DEBUG_MODE_ENABLED])],
299			'gui_access' =>		['type' => API_INT32, 'in' => implode(',', [GROUP_GUI_ACCESS_SYSTEM, GROUP_GUI_ACCESS_INTERNAL, GROUP_GUI_ACCESS_LDAP, GROUP_GUI_ACCESS_DISABLED])],
300			'users_status' =>	['type' => API_INT32, 'in' => implode(',', [GROUP_STATUS_ENABLED, GROUP_STATUS_DISABLED])],
301			'rights' =>			['type' => API_OBJECTS, 'flags' => API_NORMALIZE, 'uniq' => [['id']], 'fields' => [
302				'id' =>				['type' => API_ID, 'flags' => API_REQUIRED],
303				'permission' =>		['type' => API_INT32, 'flags' => API_REQUIRED, 'in' => implode(',', [PERM_DENY, PERM_READ, PERM_READ_WRITE])]
304			]],
305			'tag_filters' =>	['type' => API_OBJECTS, 'uniq' => [['groupid', 'tag', 'value']], 'fields' => [
306				'groupid' =>		['type' => API_ID, 'flags' => API_REQUIRED],
307				'tag' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('tag_filter', 'tag'), 'default' => DB::getDefault('tag_filter', 'tag')],
308				'value' =>			['type' => API_STRING_UTF8, 'length' => DB::getFieldLength('tag_filter', 'value'), 'default' => DB::getDefault('tag_filter', 'value')]
309			]],
310			'userids' =>		['type' => API_IDS, 'flags' => API_NORMALIZE]
311		]];
312		if (!CApiInputValidator::validate($api_input_rules, $usrgrps, '/', $error)) {
313			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
314		}
315
316		// Check user group names.
317		$db_usrgrps = DB::select('usrgrp', [
318			'output' => ['usrgrpid', 'name', 'debug_mode', 'gui_access', 'users_status'],
319			'usrgrpids' => zbx_objectValues($usrgrps, 'usrgrpid'),
320			'preservekeys' => true
321		]);
322
323		$names = [];
324
325		foreach ($usrgrps as $usrgrp) {
326			// Check if this user group exists.
327			if (!array_key_exists($usrgrp['usrgrpid'], $db_usrgrps)) {
328				self::exception(ZBX_API_ERROR_PERMISSIONS,
329					_('No permissions to referred object or it does not exist!')
330				);
331			}
332
333			$db_usrgrp = $db_usrgrps[$usrgrp['usrgrpid']];
334
335			if (array_key_exists('name', $usrgrp) && $usrgrp['name'] !== $db_usrgrp['name']) {
336				$names[] = $usrgrp['name'];
337			}
338		}
339
340		if ($names) {
341			$this->checkDuplicates($names);
342		}
343		$this->checkUsers($usrgrps);
344		$this->checkHimself($usrgrps, __FUNCTION__, $db_usrgrps);
345		$this->checkUsersWithoutGroups($usrgrps);
346		$this->checkHostGroups($usrgrps);
347		$this->checkTagFilters($usrgrps);
348	}
349
350	/**
351	 * Check for duplicated user groups.
352	 *
353	 * @param array  $names
354	 *
355	 * @throws APIException  if user group already exists.
356	 */
357	private function checkDuplicates(array $names) {
358		$db_usrgrps = DB::select('usrgrp', [
359			'output' => ['name'],
360			'filter' => ['name' => $names],
361			'limit' => 1
362		]);
363
364		if ($db_usrgrps) {
365			self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group "%1$s" already exists.', $db_usrgrps[0]['name']));
366		}
367	}
368
369	/**
370	 * Check for valid users.
371	 *
372	 * @param array  $usrgrps
373	 * @param array  $usrgrps[]['userids']   (optional)
374	 *
375	 * @throws APIException
376	 */
377	private function checkUsers(array $usrgrps) {
378		$userids = [];
379
380		foreach ($usrgrps as $usrgrp) {
381			if (array_key_exists('userids', $usrgrp)) {
382				foreach ($usrgrp['userids'] as $userid) {
383					$userids[$userid] = true;
384				}
385			}
386		}
387
388		if (!$userids) {
389			return;
390		}
391
392		$userids = array_keys($userids);
393
394		$db_users = DB::select('users', [
395			'output' => [],
396			'userids' => $userids,
397			'preservekeys' => true
398		]);
399
400		foreach ($userids as $userid) {
401			if (!array_key_exists($userid, $db_users)) {
402				self::exception(ZBX_API_ERROR_PARAMETERS, _s('User with ID "%1$s" is not available.', $userid));
403			}
404		}
405	}
406
407	/**
408	 * Check for valid host grups.
409	 *
410	 * @param array  $usrgrps
411	 * @param array  $usrgrps[]['rights']   (optional)
412	 *
413	 * @throws APIException
414	 */
415	private function checkHostGroups(array $usrgrps) {
416		$groupids = [];
417
418		foreach ($usrgrps as $usrgrp) {
419			if (array_key_exists('rights', $usrgrp)) {
420				foreach ($usrgrp['rights'] as $right) {
421					$groupids[$right['id']] = true;
422				}
423			}
424			if (array_key_exists('tag_filters', $usrgrp)) {
425				foreach ($usrgrp['tag_filters'] as $tag_filter) {
426					$groupids[$tag_filter['groupid']] = true;
427				}
428			}
429		}
430
431		if (!$groupids) {
432			return;
433		}
434
435		$groupids = array_keys($groupids);
436
437		$db_groups = DB::select('hstgrp', [
438			'output' => [],
439			'groupids' => $groupids,
440			'preservekeys' => true
441		]);
442
443		foreach ($groupids as $groupid) {
444			if (!array_key_exists($groupid, $db_groups)) {
445				self::exception(ZBX_API_ERROR_PARAMETERS, _s('Host group with ID "%1$s" is not available.', $groupid));
446			}
447		}
448	}
449
450	/**
451	 * Tag filter validation.
452	 *
453	 * @param array  $usrgrps
454	 *
455	 * @throws APIException
456	 */
457	private function checkTagFilters(array $usrgrps) {
458		foreach ($usrgrps as $usrgrp) {
459			if (array_key_exists('tag_filters', $usrgrp)) {
460				foreach ($usrgrp['tag_filters'] as $tag_filter) {
461					if ($tag_filter['tag'] === '' && $tag_filter['value'] !== '') {
462						self::exception(ZBX_API_ERROR_PARAMETERS,
463							_s('Incorrect value for field "%1$s": %2$s.', _('tag'), _('cannot be empty'))
464						);
465					}
466				}
467			}
468		}
469	}
470
471	/**
472	 * Auxiliary function for checkHimself().
473	 * Returns true if user group has GROUP_GUI_ACCESS_DISABLED or GROUP_STATUS_DISABLED states.
474	 *
475	 * @param array  $usrgrp
476	 * @param string $method
477	 * @param array  $db_usrgrps
478	 *
479	 * @return bool
480	 */
481	private static function userGroupDisabled(array $usrgrp, $method, array $db_usrgrps = null) {
482		$gui_access = array_key_exists('gui_access', $usrgrp)
483			? $usrgrp['gui_access']
484			: ($method === 'validateCreate' ? GROUP_GUI_ACCESS_SYSTEM : $db_usrgrps[$usrgrp['usrgrpid']]['gui_access']);
485		$users_status = array_key_exists('users_status', $usrgrp)
486			? $usrgrp['users_status']
487			: ($method === 'validateCreate' ? GROUP_STATUS_ENABLED : $db_usrgrps[$usrgrp['usrgrpid']]['users_status']);
488
489		return ($gui_access == GROUP_GUI_ACCESS_DISABLED || $users_status == GROUP_STATUS_DISABLED);
490	}
491
492	/**
493	 * Additional check to exclude an opportunity to deactivate himself.
494	 *
495	 * @param array  $usrgrps
496	 * @param string $method
497	 * @param array  $db_usrgrps
498	 *
499	 * @throws APIException
500	 */
501	private function checkHimself(array $usrgrps, $method, array $db_usrgrps = null) {
502		if ($method === 'validateUpdate') {
503			$groups_users = [];
504
505			foreach ($usrgrps as $usrgrp) {
506				if (self::userGroupDisabled($usrgrp, $method, $db_usrgrps) && !array_key_exists('userids', $usrgrp)) {
507					$groups_users[$usrgrp['usrgrpid']] = [];
508				}
509			}
510
511			if ($groups_users) {
512				$db_users_groups = DB::select('users_groups', [
513					'output' => ['usrgrpid', 'userid'],
514					'filter' => ['usrgrpid' => array_keys($groups_users)]
515				]);
516
517				foreach ($db_users_groups as $db_user_group) {
518					$groups_users[$db_user_group['usrgrpid']][] = $db_user_group['userid'];
519				}
520
521				foreach ($usrgrps as &$usrgrp) {
522					if (self::userGroupDisabled($usrgrp, $method, $db_usrgrps)
523							&& !array_key_exists('userids', $usrgrp)) {
524						$usrgrp['userids'] = $groups_users[$usrgrp['usrgrpid']];
525					}
526				}
527				unset($usrgrp);
528			}
529		}
530
531		foreach ($usrgrps as $usrgrp) {
532			if (self::userGroupDisabled($usrgrp, $method, $db_usrgrps)
533					&& array_key_exists('userids', $usrgrp)
534					&& uint_in_array(self::$userData['userid'], $usrgrp['userids'])) {
535				self::exception(ZBX_API_ERROR_PARAMETERS,
536					_('User cannot add himself to a disabled group or a group with disabled GUI access.')
537				);
538			}
539		}
540	}
541
542	/**
543	 * Check to exclude an opportunity to leave user without user groups.
544	 *
545	 * @param array  $usrgrps
546	 * @param array  $usrgrps[]['usrgrpid']
547	 * @param array  $usrgrps[]['userids']   (optional)
548	 *
549	 * @throws APIException
550	 */
551	private function checkUsersWithoutGroups(array $usrgrps) {
552		$users_groups = [];
553
554		foreach ($usrgrps as $usrgrp) {
555			if (array_key_exists('userids', $usrgrp)) {
556				$users_groups[$usrgrp['usrgrpid']] = [];
557
558				foreach ($usrgrp['userids'] as $userid) {
559					$users_groups[$usrgrp['usrgrpid']][$userid] = true;
560				}
561			}
562		}
563
564		if (!$users_groups) {
565			return;
566		}
567
568		$db_users_groups = DB::select('users_groups', [
569			'output' => ['usrgrpid', 'userid'],
570			'filter' => ['usrgrpid' => array_keys($users_groups)]
571		]);
572
573		$ins_userids = [];
574		$del_userids = [];
575
576		foreach ($db_users_groups as $db_user_group) {
577			if (array_key_exists($db_user_group['userid'], $users_groups[$db_user_group['usrgrpid']])) {
578				unset($users_groups[$db_user_group['usrgrpid']][$db_user_group['userid']]);
579			}
580			else {
581				if (!array_key_exists($db_user_group['userid'], $del_userids)) {
582					$del_userids[$db_user_group['userid']] = 0;
583				}
584				$del_userids[$db_user_group['userid']]++;
585			}
586		}
587
588		foreach ($users_groups as $usrgrpid => $userids) {
589			foreach (array_keys($userids) as $userid) {
590				$ins_userids[$userid] = true;
591			}
592		}
593
594		$del_userids = array_diff_key($del_userids, $ins_userids);
595
596		if (!$del_userids) {
597			return;
598		}
599
600		$db_users = DBselect(
601			'SELECT u.userid,u.username,count(ug.usrgrpid) as usrgrp_num'.
602			' FROM users u,users_groups ug'.
603			' WHERE u.userid=ug.userid'.
604				' AND '.dbConditionInt('u.userid', array_keys($del_userids)).
605			' GROUP BY u.userid,u.username'
606		);
607
608		while ($db_user = DBfetch($db_users)) {
609			if ($db_user['usrgrp_num'] == $del_userids[$db_user['userid']]) {
610				self::exception(ZBX_API_ERROR_PARAMETERS,
611					_s('User "%1$s" cannot be without user group.', $db_user['username'])
612				);
613			}
614		}
615	}
616
617	/**
618	 * Update table "rights".
619	 *
620	 * @param array  $usrgrps
621	 * @param string $method
622	 */
623	private function updateRights(array $usrgrps, $method) {
624		$rights = [];
625
626		foreach ($usrgrps as $usrgrp) {
627			if (array_key_exists('rights', $usrgrp)) {
628				$rights[$usrgrp['usrgrpid']] = [];
629
630				foreach ($usrgrp['rights'] as $right) {
631					$rights[$usrgrp['usrgrpid']][$right['id']] = $right['permission'];
632				}
633			}
634		}
635
636		if (!$rights) {
637			return;
638		}
639
640		$db_rights = ($method === 'update')
641			? DB::select('rights', [
642				'output' => ['rightid', 'groupid', 'id', 'permission'],
643				'filter' => ['groupid' => array_keys($rights)]
644			])
645			: [];
646
647		$ins_rights = [];
648		$upd_rights = [];
649		$del_rightids = [];
650
651		foreach ($db_rights as $db_right) {
652			if (array_key_exists($db_right['groupid'], $rights)
653					&& array_key_exists($db_right['id'], $rights[$db_right['groupid']])) {
654				if ($db_right['permission'] != $rights[$db_right['groupid']][$db_right['id']]) {
655					$upd_rights[] = [
656						'values' => ['permission' => $rights[$db_right['groupid']][$db_right['id']]],
657						'where' => ['rightid' => $db_right['rightid']]
658					];
659				}
660				unset($rights[$db_right['groupid']][$db_right['id']]);
661			}
662			else {
663				$del_rightids[] = $db_right['rightid'];
664			}
665		}
666
667		foreach ($rights as $groupid => $usrgrp_rights) {
668			foreach ($usrgrp_rights as $id => $permission) {
669				$ins_rights[] = [
670					'groupid' => $groupid,
671					'id' => $id,
672					'permission' => $permission
673				];
674			}
675		}
676
677		if ($ins_rights) {
678			DB::insertBatch('rights', $ins_rights);
679		}
680
681		if ($upd_rights) {
682			DB::update('rights', $upd_rights);
683		}
684
685		if ($del_rightids) {
686			DB::delete('rights', ['rightid' => $del_rightids]);
687		}
688	}
689
690	/**
691	 * Update table "tag_filter".
692	 *
693	 * @param array  $usrgrps
694	 * @param string $method
695	 */
696	private function updateTagFilters(array $usrgrps, $method) {
697		$tag_filters = [];
698
699		foreach ($usrgrps as $usrgrp) {
700			if (array_key_exists('tag_filters', $usrgrp)) {
701				$tag_filters[$usrgrp['usrgrpid']] = [];
702
703				foreach ($usrgrp['tag_filters'] as $tag_filter) {
704					$tag_filter['usrgrpid'] = $usrgrp['usrgrpid'];
705					$tag_filters[$usrgrp['usrgrpid']][] = $tag_filter;
706				}
707				CArrayHelper::sort($tag_filters[$usrgrp['usrgrpid']], ['groupid', 'tag', 'value']);
708			}
709		}
710
711		if (!$tag_filters) {
712			return;
713		}
714
715		$db_tag_filters = ($method === 'update')
716			? DB::select('tag_filter', [
717				'output' => ['tag_filterid', 'usrgrpid', 'groupid', 'tag', 'value'],
718				'filter' => ['usrgrpid' => array_keys($tag_filters)]
719			])
720			: [];
721		CArrayHelper::sort($db_tag_filters, ['usrgrpid', 'groupid', 'tag', 'value']);
722
723		$ins_tag_filters = [];
724		$upd_tag_filters = [];
725		$del_tag_filterids = [];
726
727		foreach ($db_tag_filters as $db_tag_filter) {
728			if ($tag_filters[$db_tag_filter['usrgrpid']]) {
729				$tag_filter = array_shift($tag_filters[$db_tag_filter['usrgrpid']]);
730
731				$upd_tag_filter = [];
732
733				if (bccomp($tag_filter['groupid'], $db_tag_filter['groupid']) != 0) {
734					$upd_tag_filter['groupid'] = $tag_filter['groupid'];
735				}
736				if ($tag_filter['tag'] !== $db_tag_filter['tag']) {
737					$upd_tag_filter['tag'] = $tag_filter['tag'];
738				}
739				if ($tag_filter['value'] !== $db_tag_filter['value']) {
740					$upd_tag_filter['value'] = $tag_filter['value'];
741				}
742
743				if ($upd_tag_filter) {
744					$upd_tag_filters[] = [
745						'values' => $upd_tag_filter,
746						'where' => ['tag_filterid' => $db_tag_filter['tag_filterid']]
747					];
748				}
749			}
750			else {
751				$del_tag_filterids[] = $db_tag_filter['tag_filterid'];
752			}
753		}
754
755		foreach ($usrgrps as $usrgrp) {
756			$ins_tag_filters = array_merge($ins_tag_filters, $tag_filters[$usrgrp['usrgrpid']]);
757		}
758
759		if ($ins_tag_filters) {
760			DB::insertBatch('tag_filter', array_values($ins_tag_filters));
761		}
762
763		if ($upd_tag_filters) {
764			DB::update('tag_filter', array_values($upd_tag_filters));
765		}
766
767		if ($del_tag_filterids) {
768			DB::delete('tag_filter', ['tag_filterid' => $del_tag_filterids]);
769		}
770	}
771
772	/**
773	 * Update table "users_groups".
774	 *
775	 * @param array  $usrgrps
776	 * @param string $method
777	 */
778	private function updateUsersGroups(array $usrgrps, $method) {
779		$users_groups = [];
780
781		foreach ($usrgrps as $usrgrp) {
782			if (array_key_exists('userids', $usrgrp)) {
783				$users_groups[$usrgrp['usrgrpid']] = [];
784
785				foreach ($usrgrp['userids'] as $userid) {
786					$users_groups[$usrgrp['usrgrpid']][$userid] = true;
787				}
788			}
789		}
790
791		if (!$users_groups) {
792			return;
793		}
794
795		$db_users_groups = ($method === 'update')
796			? DB::select('users_groups', [
797				'output' => ['id', 'usrgrpid', 'userid'],
798				'filter' => ['usrgrpid' => array_keys($users_groups)]
799			])
800			: [];
801
802		$ins_users_groups = [];
803		$del_ids = [];
804
805		foreach ($db_users_groups as $db_user_group) {
806			if (array_key_exists($db_user_group['userid'], $users_groups[$db_user_group['usrgrpid']])) {
807				unset($users_groups[$db_user_group['usrgrpid']][$db_user_group['userid']]);
808			}
809			else {
810				$del_ids[] = $db_user_group['id'];
811			}
812		}
813
814		foreach ($users_groups as $usrgrpid => $userids) {
815			foreach (array_keys($userids) as $userid) {
816				$ins_users_groups[] = [
817					'usrgrpid' => $usrgrpid,
818					'userid' => $userid
819				];
820			}
821		}
822
823		if ($ins_users_groups) {
824			DB::insertBatch('users_groups', $ins_users_groups);
825		}
826
827		if ($del_ids) {
828			DB::delete('users_groups', ['id' => $del_ids]);
829		}
830	}
831
832	/**
833	 * @param array $usrgrpids
834	 *
835	 * @return array
836	 */
837	public function delete(array $usrgrpids) {
838		$this->validateDelete($usrgrpids, $db_usrgrps);
839
840		DB::delete('rights', ['groupid' => $usrgrpids]);
841		DB::delete('users_groups', ['usrgrpid' => $usrgrpids]);
842		DB::delete('usrgrp', ['usrgrpid' => $usrgrpids]);
843
844		$this->addAuditBulk(AUDIT_ACTION_DELETE, AUDIT_RESOURCE_USER_GROUP, $db_usrgrps);
845
846		return ['usrgrpids' => $usrgrpids];
847	}
848
849	/**
850	 * @throws APIException
851	 *
852	 * @param array $usrgrpids
853	 * @param array $db_usrgrps
854	 */
855	protected function validateDelete(array &$usrgrpids, array &$db_usrgrps = null) {
856		if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
857			self::exception(ZBX_API_ERROR_PERMISSIONS, _('Only Super Admins can delete user groups.'));
858		}
859
860		$api_input_rules = ['type' => API_IDS, 'flags' => API_NOT_EMPTY, 'uniq' => true];
861		if (!CApiInputValidator::validate($api_input_rules, $usrgrpids, '/', $error)) {
862			self::exception(ZBX_API_ERROR_PARAMETERS, $error);
863		}
864
865		$db_usrgrps = DB::select('usrgrp', [
866			'output' => ['usrgrpid', 'name'],
867			'usrgrpids' => $usrgrpids,
868			'preservekeys' => true
869		]);
870
871		$usrgrps = [];
872
873		foreach ($usrgrpids as $usrgrpid) {
874			// Check if this user group exists.
875			if (!array_key_exists($usrgrpid, $db_usrgrps)) {
876				self::exception(ZBX_API_ERROR_PERMISSIONS,
877					_('No permissions to referred object or it does not exist!')
878				);
879			}
880
881			$usrgrps[] = [
882				'usrgrpid' => $usrgrpid,
883				'userids' => []
884			];
885		}
886
887		// Check if user groups are used in actions.
888		$db_actions = DBselect(
889			'SELECT a.name,og.usrgrpid'.
890			' FROM opmessage_grp og,operations o,actions a'.
891			' WHERE og.operationid=o.operationid'.
892				' AND o.actionid=a.actionid'.
893				' AND '.dbConditionInt('og.usrgrpid', $usrgrpids),
894			1
895		);
896
897		if ($db_action = DBfetch($db_actions)) {
898			self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group "%1$s" is used in "%2$s" action.',
899				$db_usrgrps[$db_action['usrgrpid']]['name'], $db_action['name']
900			));
901		}
902
903		// Check if user groups are used in scripts.
904		$db_scripts = DB::select('scripts', [
905			'output' => ['name', 'usrgrpid'],
906			'filter' => ['usrgrpid' => $usrgrpids],
907			'limit' => 1
908		]);
909
910		if ($db_scripts) {
911			self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group "%1$s" is used in script "%2$s".',
912				$db_usrgrps[$db_scripts[0]['usrgrpid']]['name'], $db_scripts[0]['name']
913			));
914		}
915
916		// Check if user group are used in config.
917		if (array_key_exists(CSettingsHelper::get(CSettingsHelper::ALERT_USRGRPID), $db_usrgrps)) {
918			self::exception(ZBX_API_ERROR_PARAMETERS,
919				_s('User group "%1$s" is used in configuration for database down messages.', $db_usrgrps[CSettingsHelper::get(CSettingsHelper::ALERT_USRGRPID)]['name'])
920			);
921		}
922
923		// Check if user groups are used in scheduled reports.
924		$db_reports = DBselect(
925			'SELECT r.name,rug.usrgrpid'.
926			' FROM report r,report_usrgrp rug'.
927			' WHERE r.reportid=rug.reportid'.
928				' AND '.dbConditionInt('rug.usrgrpid', $usrgrpids),
929			1
930		);
931
932		if ($db_report = DBfetch($db_reports)) {
933			self::exception(ZBX_API_ERROR_PARAMETERS, _s('User group "%1$s" is report "%2$s" recipient.',
934				$db_usrgrps[$db_report['usrgrpid']]['name'], $db_report['name']
935			));
936		}
937
938		$this->checkUsersWithoutGroups($usrgrps);
939	}
940
941	protected function addRelatedObjects(array $options, array $result) {
942		$result = parent::addRelatedObjects($options, $result);
943
944		// adding users
945		if ($options['selectUsers'] !== null && $options['selectUsers'] != API_OUTPUT_COUNT) {
946			$dbUsers = [];
947			$relationMap = $this->createRelationMap($result, 'usrgrpid', 'userid', 'users_groups');
948			$related_ids = $relationMap->getRelatedIds();
949
950			if ($related_ids) {
951				$get_access = ($this->outputIsRequested('gui_access', $options['selectUsers'])
952					|| $this->outputIsRequested('debug_mode', $options['selectUsers'])
953					|| $this->outputIsRequested('users_status', $options['selectUsers'])) ? true : null;
954
955				$dbUsers = API::User()->get([
956					'output' => $options['selectUsers'],
957					'userids' => $related_ids,
958					'getAccess' => $get_access,
959					'preservekeys' => true
960				]);
961			}
962
963			$result = $relationMap->mapMany($result, $dbUsers, 'users');
964		}
965
966		// adding usergroup rights
967		if ($options['selectRights'] !== null && $options['selectRights'] != API_OUTPUT_COUNT) {
968			$db_rights = [];
969			$relationMap = $this->createRelationMap($result, 'groupid', 'rightid', 'rights');
970			$related_ids = $relationMap->getRelatedIds();
971
972			if ($related_ids) {
973				if (is_array($options['selectRights'])) {
974					$pk_field = $this->pk('rights');
975
976					$output_fields = [
977						$pk_field => $this->fieldId($pk_field, 'r')
978					];
979
980					foreach ($options['selectRights'] as $field) {
981						if ($this->hasField($field, 'rights')) {
982							$output_fields[$field] = $this->fieldId($field, 'r');
983						}
984					}
985
986					$output_fields = implode(',', $output_fields);
987				}
988				else {
989					$output_fields = 'r.*';
990				}
991
992				$db_rights = DBfetchArray(DBselect(
993					'SELECT '.$output_fields.
994					' FROM rights r'.
995					' WHERE '.dbConditionInt('r.rightid', $related_ids).
996						((self::$userData['type'] == USER_TYPE_SUPER_ADMIN) ? '' : ' AND r.permission>'.PERM_DENY)
997				));
998				$db_rights = zbx_toHash($db_rights, 'rightid');
999
1000				foreach ($db_rights as &$db_right) {
1001					unset($db_right['rightid'], $db_right['groupid']);
1002				}
1003				unset($db_right);
1004			}
1005
1006			$result = $relationMap->mapMany($result, $db_rights, 'rights');
1007		}
1008
1009		// Adding usergroup tag filters.
1010		if ($options['selectTagFilters'] !== null && $options['selectTagFilters'] != API_OUTPUT_COUNT) {
1011			foreach ($result as &$usrgrp) {
1012				$usrgrp['tag_filters'] = [];
1013			}
1014			unset($usrgrp);
1015
1016			if (is_array($options['selectTagFilters'])) {
1017				$output_fields = [];
1018
1019				foreach ($this->outputExtend($options['selectTagFilters'], ['usrgrpid']) as $field) {
1020					if ($this->hasField($field, 'tag_filter')) {
1021						$output_fields[$field] = $this->fieldId($field, 't');
1022					}
1023				}
1024
1025				$output_fields = implode(',', $output_fields);
1026			}
1027			else {
1028				$output_fields = 't.*';
1029			}
1030
1031			$db_tag_filters = DBselect(
1032				'SELECT '.$output_fields.
1033				' FROM tag_filter t'.
1034				' WHERE '.dbConditionInt('t.usrgrpid', array_keys($result))
1035			);
1036
1037			while ($db_tag_filter = DBfetch($db_tag_filters)) {
1038				$usrgrpid = $db_tag_filter['usrgrpid'];
1039				unset($db_tag_filter['tag_filterid'], $db_tag_filter['usrgrpid']);
1040
1041				$result[$usrgrpid]['tag_filters'][] = $db_tag_filter;
1042			}
1043		}
1044
1045		return $result;
1046	}
1047}
1048