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