Passed
Push — master ( e6ef09...54cffe )
by Roeland
49:25 queued 37:20
created

Group   F

Complexity

Total Complexity 75

Size/Duplication

Total Lines 375
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 156
dl 0
loc 375
rs 2.4
c 0
b 0
f 0
wmc 75

18 Methods

Rating   Name   Duplication   Size   Complexity  
A getGID() 0 2 1
B removeUser() 0 26 10
B delete() 0 24 7
A canAddUser() 0 7 3
B addUser() 0 27 7
A countDisabled() 0 13 4
A hideFromCollaboration() 0 4 2
A getBackendNames() 0 11 3
A getUsers() 0 19 4
A canRemoveUser() 0 7 3
A setDisplayName() 0 12 5
A getVerifiedUsers() 0 12 4
A __construct() 0 7 1
A searchDisplayName() 0 10 4
A inGroup() 0 11 4
A getDisplayName() 0 14 5
A count() 0 13 4
A searchUsers() 0 10 4

How to fix   Complexity   

Complex Class

Complex classes like Group often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Group, and based on these observations, apply Extract Interface, too.

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 Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author John Molakvoæ (skjnldsv) <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Vincent Petry <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC\Group;
34
35
use OC\Hooks\PublicEmitter;
36
use OCP\Group\Backend\ICountDisabledInGroup;
37
use OCP\Group\Backend\IGetDisplayNameBackend;
38
use OCP\Group\Backend\IHideFromCollaborationBackend;
39
use OCP\Group\Backend\INamedBackend;
40
use OCP\Group\Backend\ISetDisplayNameBackend;
41
use OCP\GroupInterface;
42
use OCP\IGroup;
43
use OCP\IUser;
44
use OCP\IUserManager;
45
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
46
use Symfony\Component\EventDispatcher\GenericEvent;
47
48
class Group implements IGroup {
49
	/** @var null|string  */
50
	protected $displayName;
51
52
	/** @var string */
53
	private $gid;
54
55
	/** @var \OC\User\User[] */
56
	private $users = [];
57
58
	/** @var bool */
59
	private $usersLoaded;
60
61
	/** @var Backend[] */
62
	private $backends;
63
	/** @var EventDispatcherInterface */
64
	private $dispatcher;
65
	/** @var \OC\User\Manager|IUserManager  */
66
	private $userManager;
67
	/** @var PublicEmitter */
68
	private $emitter;
69
70
71
	/**
72
	 * @param string $gid
73
	 * @param Backend[] $backends
74
	 * @param EventDispatcherInterface $dispatcher
75
	 * @param IUserManager $userManager
76
	 * @param PublicEmitter $emitter
77
	 * @param string $displayName
78
	 */
79
	public function __construct(string $gid, array $backends, EventDispatcherInterface $dispatcher, IUserManager $userManager, PublicEmitter $emitter = null, ?string $displayName = null) {
80
		$this->gid = $gid;
81
		$this->backends = $backends;
82
		$this->dispatcher = $dispatcher;
83
		$this->userManager = $userManager;
84
		$this->emitter = $emitter;
85
		$this->displayName = $displayName;
86
	}
87
88
	public function getGID() {
89
		return $this->gid;
90
	}
91
92
	public function getDisplayName() {
93
		if (is_null($this->displayName)) {
94
			foreach ($this->backends as $backend) {
95
				if ($backend instanceof IGetDisplayNameBackend) {
96
					$displayName = $backend->getDisplayName($this->gid);
97
					if (trim($displayName) !== '') {
98
						$this->displayName = $displayName;
99
						return $this->displayName;
100
					}
101
				}
102
			}
103
			return $this->gid;
104
		}
105
		return $this->displayName;
106
	}
107
108
	public function setDisplayName(string $displayName): bool {
109
		$displayName = trim($displayName);
110
		if ($displayName !== '') {
111
			foreach ($this->backends as $backend) {
112
				if (($backend instanceof ISetDisplayNameBackend)
113
					&& $backend->setDisplayName($this->gid, $displayName)) {
114
					$this->displayName = $displayName;
115
					return true;
116
				}
117
			}
118
		}
119
		return false;
120
	}
121
122
	/**
123
	 * get all users in the group
124
	 *
125
	 * @return \OC\User\User[]
126
	 */
127
	public function getUsers() {
128
		if ($this->usersLoaded) {
129
			return $this->users;
130
		}
131
132
		$userIds = [];
133
		foreach ($this->backends as $backend) {
134
			$diff = array_diff(
135
				$backend->usersInGroup($this->gid),
136
				$userIds
137
			);
138
			if ($diff) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $diff of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
139
				$userIds = array_merge($userIds, $diff);
140
			}
141
		}
142
143
		$this->users = $this->getVerifiedUsers($userIds);
144
		$this->usersLoaded = true;
145
		return $this->users;
146
	}
147
148
	/**
149
	 * check if a user is in the group
150
	 *
151
	 * @param IUser $user
152
	 * @return bool
153
	 */
154
	public function inGroup(IUser $user) {
155
		if (isset($this->users[$user->getUID()])) {
156
			return true;
157
		}
158
		foreach ($this->backends as $backend) {
159
			if ($backend->inGroup($user->getUID(), $this->gid)) {
160
				$this->users[$user->getUID()] = $user;
161
				return true;
162
			}
163
		}
164
		return false;
165
	}
166
167
	/**
168
	 * add a user to the group
169
	 *
170
	 * @param IUser $user
171
	 */
172
	public function addUser(IUser $user) {
173
		if ($this->inGroup($user)) {
174
			return;
175
		}
176
177
		$this->dispatcher->dispatch(IGroup::class . '::preAddUser', new GenericEvent($this, [
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...array('user' => $user)). ( Ignorable by Annotation )

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

177
		$this->dispatcher->/** @scrutinizer ignore-call */ 
178
                     dispatch(IGroup::class . '::preAddUser', new GenericEvent($this, [

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\IGroup::class . '::preAddUser' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

177
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ IGroup::class . '::preAddUser', new GenericEvent($this, [
Loading history...
178
			'user' => $user,
179
		]));
180
181
		if ($this->emitter) {
182
			$this->emitter->emit('\OC\Group', 'preAddUser', [$this, $user]);
183
		}
184
		foreach ($this->backends as $backend) {
185
			if ($backend->implementsActions(\OC\Group\Backend::ADD_TO_GROUP)) {
186
				$backend->addToGroup($user->getUID(), $this->gid);
0 ignored issues
show
Bug introduced by
The method addToGroup() does not exist on OC\Group\Backend. ( Ignorable by Annotation )

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

186
				$backend->/** @scrutinizer ignore-call */ 
187
              addToGroup($user->getUID(), $this->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 ($this->users) {
188
					$this->users[$user->getUID()] = $user;
189
				}
190
191
				$this->dispatcher->dispatch(IGroup::class . '::postAddUser', new GenericEvent($this, [
192
					'user' => $user,
193
				]));
194
195
				if ($this->emitter) {
196
					$this->emitter->emit('\OC\Group', 'postAddUser', [$this, $user]);
197
				}
198
				return;
199
			}
200
		}
201
	}
202
203
	/**
204
	 * remove a user from the group
205
	 *
206
	 * @param \OC\User\User $user
207
	 */
208
	public function removeUser($user) {
209
		$result = false;
210
		$this->dispatcher->dispatch(IGroup::class . '::preRemoveUser', new GenericEvent($this, [
0 ignored issues
show
Bug introduced by
OCP\IGroup::class . '::preRemoveUser' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

210
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ IGroup::class . '::preRemoveUser', new GenericEvent($this, [
Loading history...
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...array('user' => $user)). ( Ignorable by Annotation )

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

210
		$this->dispatcher->/** @scrutinizer ignore-call */ 
211
                     dispatch(IGroup::class . '::preRemoveUser', new GenericEvent($this, [

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
211
			'user' => $user,
212
		]));
213
		if ($this->emitter) {
214
			$this->emitter->emit('\OC\Group', 'preRemoveUser', [$this, $user]);
215
		}
216
		foreach ($this->backends as $backend) {
217
			if ($backend->implementsActions(\OC\Group\Backend::REMOVE_FROM_GOUP) and $backend->inGroup($user->getUID(), $this->gid)) {
218
				$backend->removeFromGroup($user->getUID(), $this->gid);
0 ignored issues
show
Bug introduced by
The method removeFromGroup() does not exist on OC\Group\Backend. ( Ignorable by Annotation )

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

218
				$backend->/** @scrutinizer ignore-call */ 
219
              removeFromGroup($user->getUID(), $this->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...
219
				$result = true;
220
			}
221
		}
222
		if ($result) {
223
			$this->dispatcher->dispatch(IGroup::class . '::postRemoveUser', new GenericEvent($this, [
224
				'user' => $user,
225
			]));
226
			if ($this->emitter) {
227
				$this->emitter->emit('\OC\Group', 'postRemoveUser', [$this, $user]);
228
			}
229
			if ($this->users) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->users of type OC\User\User[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
230
				foreach ($this->users as $index => $groupUser) {
231
					if ($groupUser->getUID() === $user->getUID()) {
232
						unset($this->users[$index]);
233
						return;
234
					}
235
				}
236
			}
237
		}
238
	}
239
240
	/**
241
	 * search for users in the group by userid
242
	 *
243
	 * @param string $search
244
	 * @param int $limit
245
	 * @param int $offset
246
	 * @return \OC\User\User[]
247
	 */
248
	public function searchUsers($search, $limit = null, $offset = null) {
249
		$users = [];
250
		foreach ($this->backends as $backend) {
251
			$userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset);
252
			$users += $this->getVerifiedUsers($userIds);
253
			if (!is_null($limit) and $limit <= 0) {
254
				return $users;
255
			}
256
		}
257
		return $users;
258
	}
259
260
	/**
261
	 * returns the number of users matching the search string
262
	 *
263
	 * @param string $search
264
	 * @return int|bool
265
	 */
266
	public function count($search = '') {
267
		$users = false;
268
		foreach ($this->backends as $backend) {
269
			if ($backend->implementsActions(\OC\Group\Backend::COUNT_USERS)) {
270
				if ($users === false) {
271
					//we could directly add to a bool variable, but this would
272
					//be ugly
273
					$users = 0;
274
				}
275
				$users += $backend->countUsersInGroup($this->gid, $search);
0 ignored issues
show
Bug introduced by
The method countUsersInGroup() does not exist on OC\Group\Backend. ( Ignorable by Annotation )

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

275
				$users += $backend->/** @scrutinizer ignore-call */ countUsersInGroup($this->gid, $search);

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...
276
			}
277
		}
278
		return $users;
279
	}
280
281
	/**
282
	 * returns the number of disabled users
283
	 *
284
	 * @return int|bool
285
	 */
286
	public function countDisabled() {
287
		$users = false;
288
		foreach ($this->backends as $backend) {
289
			if ($backend instanceof ICountDisabledInGroup) {
290
				if ($users === false) {
291
					//we could directly add to a bool variable, but this would
292
					//be ugly
293
					$users = 0;
294
				}
295
				$users += $backend->countDisabledInGroup($this->gid);
296
			}
297
		}
298
		return $users;
299
	}
300
301
	/**
302
	 * search for users in the group by displayname
303
	 *
304
	 * @param string $search
305
	 * @param int $limit
306
	 * @param int $offset
307
	 * @return \OC\User\User[]
308
	 */
309
	public function searchDisplayName($search, $limit = null, $offset = null) {
310
		$users = [];
311
		foreach ($this->backends as $backend) {
312
			$userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset);
313
			$users = $this->getVerifiedUsers($userIds);
314
			if (!is_null($limit) and $limit <= 0) {
315
				return array_values($users);
316
			}
317
		}
318
		return array_values($users);
319
	}
320
321
	/**
322
	 * Get the names of the backend classes the group is connected to
323
	 *
324
	 * @return string[]
325
	 */
326
	public function getBackendNames() {
327
		$backends = [];
328
		foreach ($this->backends as $backend) {
329
			if ($backend instanceof INamedBackend) {
330
				$backends[] = $backend->getBackendName();
331
			} else {
332
				$backends[] = get_class($backend);
333
			}
334
		}
335
336
		return $backends;
337
	}
338
339
	/**
340
	 * delete the group
341
	 *
342
	 * @return bool
343
	 */
344
	public function delete() {
345
		// Prevent users from deleting group admin
346
		if ($this->getGID() === 'admin') {
347
			return false;
348
		}
349
350
		$result = false;
351
		$this->dispatcher->dispatch(IGroup::class . '::preDelete', new GenericEvent($this));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...her\GenericEvent($this). ( Ignorable by Annotation )

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

351
		$this->dispatcher->/** @scrutinizer ignore-call */ 
352
                     dispatch(IGroup::class . '::preDelete', new GenericEvent($this));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
OCP\IGroup::class . '::preDelete' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

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

351
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ IGroup::class . '::preDelete', new GenericEvent($this));
Loading history...
352
		if ($this->emitter) {
353
			$this->emitter->emit('\OC\Group', 'preDelete', [$this]);
354
		}
355
		foreach ($this->backends as $backend) {
356
			if ($backend->implementsActions(\OC\Group\Backend::DELETE_GROUP)) {
357
				$result = true;
358
				$backend->deleteGroup($this->gid);
0 ignored issues
show
Bug introduced by
The method deleteGroup() does not exist on OC\Group\Backend. ( Ignorable by Annotation )

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

358
				$backend->/** @scrutinizer ignore-call */ 
359
              deleteGroup($this->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...
359
			}
360
		}
361
		if ($result) {
362
			$this->dispatcher->dispatch(IGroup::class . '::postDelete', new GenericEvent($this));
363
			if ($this->emitter) {
364
				$this->emitter->emit('\OC\Group', 'postDelete', [$this]);
365
			}
366
		}
367
		return $result;
368
	}
369
370
	/**
371
	 * returns all the Users from an array that really exists
372
	 * @param string[] $userIds an array containing user IDs
373
	 * @return \OC\User\User[] an Array with the userId as Key and \OC\User\User as value
374
	 */
375
	private function getVerifiedUsers($userIds) {
376
		if (!is_array($userIds)) {
0 ignored issues
show
introduced by
The condition is_array($userIds) is always true.
Loading history...
377
			return [];
378
		}
379
		$users = [];
380
		foreach ($userIds as $userId) {
381
			$user = $this->userManager->get($userId);
382
			if (!is_null($user)) {
383
				$users[$userId] = $user;
384
			}
385
		}
386
		return $users;
387
	}
388
389
	/**
390
	 * @return bool
391
	 * @since 14.0.0
392
	 */
393
	public function canRemoveUser() {
394
		foreach ($this->backends as $backend) {
395
			if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) {
396
				return true;
397
			}
398
		}
399
		return false;
400
	}
401
402
	/**
403
	 * @return bool
404
	 * @since 14.0.0
405
	 */
406
	public function canAddUser() {
407
		foreach ($this->backends as $backend) {
408
			if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) {
409
				return true;
410
			}
411
		}
412
		return false;
413
	}
414
415
	/**
416
	 * @return bool
417
	 * @since 16.0.0
418
	 */
419
	public function hideFromCollaboration(): bool {
420
		return array_reduce($this->backends, function (bool $hide, GroupInterface $backend) {
421
			return $hide | ($backend instanceof IHideFromCollaborationBackend && $backend->hideGroup($this->gid));
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
422
		}, false);
423
	}
424
}
425