Passed
Push — master ( cd7cec...c4157e )
by
unknown
15:30 queued 17s
created

Manager::getDisplayName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Bernhard Posselt <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author John Molakvoæ <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Knut Ahlers <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author macjohnny <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Robin McCorkell <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Roman Kreisel <[email protected]>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Vincent Petry <[email protected]>
22
 * @author Vinicius Cubas Brand <[email protected]>
23
 * @author voxsim "Simon Vocella"
24
 *
25
 * @license AGPL-3.0
26
 *
27
 * This code is free software: you can redistribute it and/or modify
28
 * it under the terms of the GNU Affero General Public License, version 3,
29
 * as published by the Free Software Foundation.
30
 *
31
 * This program is distributed in the hope that it will be useful,
32
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34
 * GNU Affero General Public License for more details.
35
 *
36
 * You should have received a copy of the GNU Affero General Public License, version 3,
37
 * along with this program. If not, see <http://www.gnu.org/licenses/>
38
 *
39
 */
40
namespace OC\Group;
41
42
use OC\Hooks\PublicEmitter;
43
use OCP\EventDispatcher\IEventDispatcher;
44
use OCP\GroupInterface;
45
use OCP\ICacheFactory;
46
use OCP\IGroup;
47
use OCP\IGroupManager;
48
use OCP\IUser;
49
use Psr\Log\LoggerInterface;
50
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
51
52
/**
53
 * Class Manager
54
 *
55
 * Hooks available in scope \OC\Group:
56
 * - preAddUser(\OC\Group\Group $group, \OC\User\User $user)
57
 * - postAddUser(\OC\Group\Group $group, \OC\User\User $user)
58
 * - preRemoveUser(\OC\Group\Group $group, \OC\User\User $user)
59
 * - postRemoveUser(\OC\Group\Group $group, \OC\User\User $user)
60
 * - preDelete(\OC\Group\Group $group)
61
 * - postDelete(\OC\Group\Group $group)
62
 * - preCreate(string $groupId)
63
 * - postCreate(\OC\Group\Group $group)
64
 *
65
 * @package OC\Group
66
 */
67
class Manager extends PublicEmitter implements IGroupManager {
0 ignored issues
show
Deprecated Code introduced by
The class OC\Hooks\PublicEmitter has been deprecated: 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

67
class Manager extends /** @scrutinizer ignore-deprecated */ PublicEmitter implements IGroupManager {
Loading history...
68
	/** @var GroupInterface[] */
69
	private $backends = [];
70
71
	/** @var \OC\User\Manager */
72
	private $userManager;
73
	/** @var EventDispatcherInterface */
74
	private $dispatcher;
75
	private LoggerInterface $logger;
76
77
	/** @var \OC\Group\Group[] */
78
	private $cachedGroups = [];
79
80
	/** @var (string[])[] */
81
	private $cachedUserGroups = [];
82
83
	/** @var \OC\SubAdmin */
84
	private $subAdmin = null;
85
86
	private DisplayNameCache $displayNameCache;
87
88
	public function __construct(\OC\User\Manager $userManager,
89
								EventDispatcherInterface $dispatcher,
90
								LoggerInterface $logger,
91
								ICacheFactory $cacheFactory) {
92
		$this->userManager = $userManager;
93
		$this->dispatcher = $dispatcher;
94
		$this->logger = $logger;
95
		$this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
96
97
		$cachedGroups = &$this->cachedGroups;
98
		$cachedUserGroups = &$this->cachedUserGroups;
99
		$this->listen('\OC\Group', 'postDelete', function ($group) use (&$cachedGroups, &$cachedUserGroups) {
100
			/**
101
			 * @var \OC\Group\Group $group
102
			 */
103
			unset($cachedGroups[$group->getGID()]);
104
			$cachedUserGroups = [];
105
		});
106
		$this->listen('\OC\Group', 'postAddUser', function ($group) use (&$cachedUserGroups) {
0 ignored issues
show
Unused Code introduced by
The parameter $group is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

106
		$this->listen('\OC\Group', 'postAddUser', function (/** @scrutinizer ignore-unused */ $group) use (&$cachedUserGroups) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
107
			/**
108
			 * @var \OC\Group\Group $group
109
			 */
110
			$cachedUserGroups = [];
111
		});
112
		$this->listen('\OC\Group', 'postRemoveUser', function ($group) use (&$cachedUserGroups) {
0 ignored issues
show
Unused Code introduced by
The parameter $group is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

112
		$this->listen('\OC\Group', 'postRemoveUser', function (/** @scrutinizer ignore-unused */ $group) use (&$cachedUserGroups) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
113
			/**
114
			 * @var \OC\Group\Group $group
115
			 */
116
			$cachedUserGroups = [];
117
		});
118
	}
119
120
	/**
121
	 * Checks whether a given backend is used
122
	 *
123
	 * @param string $backendClass Full classname including complete namespace
124
	 * @return bool
125
	 */
126
	public function isBackendUsed($backendClass) {
127
		$backendClass = strtolower(ltrim($backendClass, '\\'));
128
129
		foreach ($this->backends as $backend) {
130
			if (strtolower(get_class($backend)) === $backendClass) {
131
				return true;
132
			}
133
		}
134
135
		return false;
136
	}
137
138
	/**
139
	 * @param \OCP\GroupInterface $backend
140
	 */
141
	public function addBackend($backend) {
142
		$this->backends[] = $backend;
143
		$this->clearCaches();
144
	}
145
146
	public function clearBackends() {
147
		$this->backends = [];
148
		$this->clearCaches();
149
	}
150
151
	/**
152
	 * Get the active backends
153
	 *
154
	 * @return \OCP\GroupInterface[]
155
	 */
156
	public function getBackends() {
157
		return $this->backends;
158
	}
159
160
161
	protected function clearCaches() {
162
		$this->cachedGroups = [];
163
		$this->cachedUserGroups = [];
164
	}
165
166
	/**
167
	 * @param string $gid
168
	 * @return IGroup|null
169
	 */
170
	public function get($gid) {
171
		if (isset($this->cachedGroups[$gid])) {
172
			return $this->cachedGroups[$gid];
173
		}
174
		return $this->getGroupObject($gid);
175
	}
176
177
	/**
178
	 * @param string $gid
179
	 * @param string $displayName
180
	 * @return \OCP\IGroup|null
181
	 */
182
	protected function getGroupObject($gid, $displayName = null) {
183
		$backends = [];
184
		foreach ($this->backends as $backend) {
185
			if ($backend->implementsActions(Backend::GROUP_DETAILS)) {
186
				$groupData = $backend->getGroupDetails($gid);
0 ignored issues
show
Bug introduced by
The method getGroupDetails() does not exist on OCP\GroupInterface. Did you maybe mean getGroups()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
				/** @scrutinizer ignore-call */ 
187
    $groupData = $backend->getGroupDetails($gid);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
187
				if (is_array($groupData) && !empty($groupData)) {
188
					// take the display name from the first backend that has a non-null one
189
					if (is_null($displayName) && isset($groupData['displayName'])) {
190
						$displayName = $groupData['displayName'];
191
					}
192
					$backends[] = $backend;
193
				}
194
			} elseif ($backend->groupExists($gid)) {
195
				$backends[] = $backend;
196
			}
197
		}
198
		if (count($backends) === 0) {
199
			return null;
200
		}
201
		$this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
202
		return $this->cachedGroups[$gid];
203
	}
204
205
	/**
206
	 * @param string $gid
207
	 * @return bool
208
	 */
209
	public function groupExists($gid) {
210
		return $this->get($gid) instanceof IGroup;
211
	}
212
213
	/**
214
	 * @param string $gid
215
	 * @return IGroup|null
216
	 */
217
	public function createGroup($gid) {
218
		if ($gid === '' || $gid === null) {
219
			return null;
220
		} elseif ($group = $this->get($gid)) {
221
			return $group;
222
		} else {
223
			$this->emit('\OC\Group', 'preCreate', [$gid]);
224
			foreach ($this->backends as $backend) {
225
				if ($backend->implementsActions(Backend::CREATE_GROUP)) {
226
					if ($backend->createGroup($gid)) {
0 ignored issues
show
Bug introduced by
The method createGroup() does not exist on OCP\GroupInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OCP\Group\Backend\ABackend or OC\Group\Backend. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

226
					if ($backend->/** @scrutinizer ignore-call */ createGroup($gid)) {
Loading history...
227
						$group = $this->getGroupObject($gid);
228
						$this->emit('\OC\Group', 'postCreate', [$group]);
229
						return $group;
230
					}
231
				}
232
			}
233
			return null;
234
		}
235
	}
236
237
	/**
238
	 * @param string $search
239
	 * @param int $limit
240
	 * @param int $offset
241
	 * @return \OC\Group\Group[]
242
	 */
243
	public function search($search, $limit = null, $offset = null) {
244
		$groups = [];
245
		foreach ($this->backends as $backend) {
246
			$groupIds = $backend->getGroups($search, $limit, $offset);
247
			foreach ($groupIds as $groupId) {
248
				$aGroup = $this->get($groupId);
249
				if ($aGroup instanceof IGroup) {
250
					$groups[$groupId] = $aGroup;
251
				} else {
252
					$this->logger->debug('Group "' . $groupId . '" was returned by search but not found through direct access', ['app' => 'core']);
253
				}
254
			}
255
			if (!is_null($limit) and $limit <= 0) {
256
				return array_values($groups);
257
			}
258
		}
259
		return array_values($groups);
260
	}
261
262
	/**
263
	 * @param IUser|null $user
264
	 * @return \OC\Group\Group[]
265
	 */
266
	public function getUserGroups(IUser $user = null) {
267
		if (!$user instanceof IUser) {
268
			return [];
269
		}
270
		return $this->getUserIdGroups($user->getUID());
271
	}
272
273
	/**
274
	 * @param string $uid the user id
275
	 * @return \OC\Group\Group[]
276
	 */
277
	public function getUserIdGroups(string $uid): array {
278
		$groups = [];
279
280
		foreach ($this->getUserIdGroupIds($uid) as $groupId) {
281
			$aGroup = $this->get($groupId);
282
			if ($aGroup instanceof IGroup) {
283
				$groups[$groupId] = $aGroup;
284
			} else {
285
				$this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', ['app' => 'core']);
286
			}
287
		}
288
289
		return $groups;
290
	}
291
292
	/**
293
	 * Checks if a userId is in the admin group
294
	 *
295
	 * @param string $userId
296
	 * @return bool if admin
297
	 */
298
	public function isAdmin($userId) {
299
		foreach ($this->backends as $backend) {
300
			if ($backend->implementsActions(Backend::IS_ADMIN) && $backend->isAdmin($userId)) {
0 ignored issues
show
Bug introduced by
The method isAdmin() does not exist on OCP\GroupInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

300
			if ($backend->implementsActions(Backend::IS_ADMIN) && $backend->/** @scrutinizer ignore-call */ isAdmin($userId)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
301
				return true;
302
			}
303
		}
304
		return $this->isInGroup($userId, 'admin');
305
	}
306
307
	/**
308
	 * Checks if a userId is in a group
309
	 *
310
	 * @param string $userId
311
	 * @param string $group
312
	 * @return bool if in group
313
	 */
314
	public function isInGroup($userId, $group) {
315
		return array_search($group, $this->getUserIdGroupIds($userId)) !== false;
316
	}
317
318
	/**
319
	 * get a list of group ids for a user
320
	 *
321
	 * @param IUser $user
322
	 * @return string[] with group ids
323
	 */
324
	public function getUserGroupIds(IUser $user): array {
325
		return $this->getUserIdGroupIds($user->getUID());
326
	}
327
328
	/**
329
	 * @param string $uid the user id
330
	 * @return string[]
331
	 */
332
	private function getUserIdGroupIds(string $uid): array {
333
		if (!isset($this->cachedUserGroups[$uid])) {
334
			$groups = [];
335
			foreach ($this->backends as $backend) {
336
				if ($groupIds = $backend->getUserGroups($uid)) {
337
					$groups = array_merge($groups, $groupIds);
338
				}
339
			}
340
			$this->cachedUserGroups[$uid] = $groups;
341
		}
342
343
		return $this->cachedUserGroups[$uid];
344
	}
345
346
	/**
347
	 * @param string $groupId
348
	 * @return ?string
349
	 */
350
	public function getDisplayName(string $groupId): ?string {
351
		return $this->displayNameCache->getDisplayName($groupId);
352
	}
353
354
	/**
355
	 * get an array of groupid and displayName for a user
356
	 *
357
	 * @param IUser $user
358
	 * @return array ['displayName' => displayname]
359
	 */
360
	public function getUserGroupNames(IUser $user) {
361
		return array_map(function ($group) {
362
			return ['displayName' => $this->displayNameCache->getDisplayName($group->getGID())];
363
		}, $this->getUserGroups($user));
364
	}
365
366
	/**
367
	 * get a list of all display names in a group
368
	 *
369
	 * @param string $gid
370
	 * @param string $search
371
	 * @param int $limit
372
	 * @param int $offset
373
	 * @return array an array of display names (value) and user ids (key)
374
	 */
375
	public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) {
376
		$group = $this->get($gid);
377
		if (is_null($group)) {
378
			return [];
379
		}
380
381
		$search = trim($search);
382
		$groupUsers = [];
383
384
		if (!empty($search)) {
385
			// only user backends have the capability to do a complex search for users
386
			$searchOffset = 0;
387
			$searchLimit = $limit * 100;
388
			if ($limit === -1) {
389
				$searchLimit = 500;
390
			}
391
392
			do {
393
				$filteredUsers = $this->userManager->searchDisplayName($search, $searchLimit, $searchOffset);
394
				foreach ($filteredUsers as $filteredUser) {
395
					if ($group->inGroup($filteredUser)) {
396
						$groupUsers[] = $filteredUser;
397
					}
398
				}
399
				$searchOffset += $searchLimit;
400
			} while (count($groupUsers) < $searchLimit + $offset && count($filteredUsers) >= $searchLimit);
401
402
			if ($limit === -1) {
403
				$groupUsers = array_slice($groupUsers, $offset);
404
			} else {
405
				$groupUsers = array_slice($groupUsers, $offset, $limit);
406
			}
407
		} else {
408
			$groupUsers = $group->searchUsers('', $limit, $offset);
409
		}
410
411
		$matchingUsers = [];
412
		foreach ($groupUsers as $groupUser) {
413
			$matchingUsers[(string) $groupUser->getUID()] = $groupUser->getDisplayName();
414
		}
415
		return $matchingUsers;
416
	}
417
418
	/**
419
	 * @return \OC\SubAdmin
420
	 */
421
	public function getSubAdmin() {
422
		if (!$this->subAdmin) {
423
			$this->subAdmin = new \OC\SubAdmin(
424
				$this->userManager,
425
				$this,
426
				\OC::$server->getDatabaseConnection(),
427
				\OC::$server->get(IEventDispatcher::class)
428
			);
429
		}
430
431
		return $this->subAdmin;
432
	}
433
}
434