Passed
Push — master ( 62399c...5c21d1 )
by John
10:04
created

Group::setDisplayName()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 3
nop 1
dl 0
loc 12
rs 9.6111
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 Lukas Reschke <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Robin McCorkell <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Vincent Petry <[email protected]>
13
 * @author John Molakvoæ <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Group;
32
33
use OCP\Group\Backend\IGetDisplayNameBackend;
34
use OCP\Group\Backend\IHideFromCollaborationBackend;
35
use OC\Hooks\PublicEmitter;
36
use OCP\Group\Backend\ISetDisplayNameBackend;
37
use OCP\GroupInterface;
38
use OCP\IGroup;
39
use OCP\IUser;
40
use OCP\Group\Backend\ICountDisabledInGroup;
41
use OCP\IUserManager;
42
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43
use Symfony\Component\EventDispatcher\GenericEvent;
44
45
class Group implements IGroup {
46
	/** @var null|string  */
47
	protected $displayName;
48
49
	/** @var string */
50
	private $gid;
51
52
	/** @var \OC\User\User[] */
53
	private $users = array();
54
55
	/** @var bool */
56
	private $usersLoaded;
57
58
	/** @var Backend[] */
59
	private $backends;
60
	/** @var EventDispatcherInterface */
61
	private $dispatcher;
62
	/** @var \OC\User\Manager|IUserManager  */
63
	private $userManager;
64
	/** @var PublicEmitter */
65
	private $emitter;
66
67
68
	/**
69
	 * @param string $gid
70
	 * @param Backend[] $backends
71
	 * @param EventDispatcherInterface $dispatcher
72
	 * @param IUserManager $userManager
73
	 * @param PublicEmitter $emitter
74
	 * @param string $displayName
75
	 */
76
	public function __construct(string $gid, array $backends, EventDispatcherInterface $dispatcher, IUserManager $userManager, PublicEmitter $emitter = null, ?string $displayName = null) {
77
		$this->gid = $gid;
78
		$this->backends = $backends;
79
		$this->dispatcher = $dispatcher;
80
		$this->userManager = $userManager;
81
		$this->emitter = $emitter;
82
		$this->displayName = $displayName;
83
	}
84
85
	public function getGID() {
86
		return $this->gid;
87
	}
88
89
	public function getDisplayName() {
90
		if (is_null($this->displayName)) {
91
			foreach ($this->backends as $backend) {
92
				if ($backend instanceof IGetDisplayNameBackend) {
93
					$displayName = $backend->getDisplayName($this->gid);
94
					if (trim($displayName) !== '') {
95
						$this->displayName = $displayName;
96
						return $this->displayName;
97
					}
98
				}
99
			}
100
			return $this->gid;
101
		}
102
		return $this->displayName;
103
	}
104
105
	public function setDisplayName(string $displayName): bool {
106
		$displayName = trim($displayName);
107
		if ($displayName !== '') {
108
			foreach ($this->backends as $backend) {
109
				if (($backend instanceof ISetDisplayNameBackend)
110
					&& $backend->setDisplayName($this->gid, $displayName)) {
111
					$this->displayName = $displayName;
112
					return true;
113
				}
114
			}
115
		}
116
		return false;
117
	}
118
119
	/**
120
	 * get all users in the group
121
	 *
122
	 * @return \OC\User\User[]
123
	 */
124
	public function getUsers() {
125
		if ($this->usersLoaded) {
126
			return $this->users;
127
		}
128
129
		$userIds = array();
130
		foreach ($this->backends as $backend) {
131
			$diff = array_diff(
132
				$backend->usersInGroup($this->gid),
133
				$userIds
134
			);
135
			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...
136
				$userIds = array_merge($userIds, $diff);
137
			}
138
		}
139
140
		$this->users = $this->getVerifiedUsers($userIds);
141
		$this->usersLoaded = true;
142
		return $this->users;
143
	}
144
145
	/**
146
	 * check if a user is in the group
147
	 *
148
	 * @param IUser $user
149
	 * @return bool
150
	 */
151
	public function inGroup(IUser $user) {
152
		if (isset($this->users[$user->getUID()])) {
153
			return true;
154
		}
155
		foreach ($this->backends as $backend) {
156
			if ($backend->inGroup($user->getUID(), $this->gid)) {
157
				$this->users[$user->getUID()] = $user;
158
				return true;
159
			}
160
		}
161
		return false;
162
	}
163
164
	/**
165
	 * add a user to the group
166
	 *
167
	 * @param IUser $user
168
	 */
169
	public function addUser(IUser $user) {
170
		if ($this->inGroup($user)) {
171
			return;
172
		}
173
174
		$this->dispatcher->dispatch(IGroup::class . '::preAddUser', new GenericEvent($this, [
0 ignored issues
show
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

174
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ IGroup::class . '::preAddUser', 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

174
		$this->dispatcher->/** @scrutinizer ignore-call */ 
175
                     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...
175
			'user' => $user,
176
		]));
177
178
		if ($this->emitter) {
179
			$this->emitter->emit('\OC\Group', 'preAddUser', array($this, $user));
180
		}
181
		foreach ($this->backends as $backend) {
182
			if ($backend->implementsActions(\OC\Group\Backend::ADD_TO_GROUP)) {
183
				$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

183
				$backend->/** @scrutinizer ignore-call */ 
184
              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...
184
				if ($this->users) {
185
					$this->users[$user->getUID()] = $user;
186
				}
187
188
				$this->dispatcher->dispatch(IGroup::class . '::postAddUser', new GenericEvent($this, [
189
					'user' => $user,
190
				]));
191
192
				if ($this->emitter) {
193
					$this->emitter->emit('\OC\Group', 'postAddUser', array($this, $user));
194
				}
195
				return;
196
			}
197
		}
198
	}
199
200
	/**
201
	 * remove a user from the group
202
	 *
203
	 * @param \OC\User\User $user
204
	 */
205
	public function removeUser($user) {
206
		$result = false;
207
		$this->dispatcher->dispatch(IGroup::class . '::preRemoveUser', 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

207
		$this->dispatcher->/** @scrutinizer ignore-call */ 
208
                     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...
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

207
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ IGroup::class . '::preRemoveUser', new GenericEvent($this, [
Loading history...
208
			'user' => $user,
209
		]));
210
		if ($this->emitter) {
211
			$this->emitter->emit('\OC\Group', 'preRemoveUser', array($this, $user));
212
		}
213
		foreach ($this->backends as $backend) {
214
			if ($backend->implementsActions(\OC\Group\Backend::REMOVE_FROM_GOUP) and $backend->inGroup($user->getUID(), $this->gid)) {
215
				$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

215
				$backend->/** @scrutinizer ignore-call */ 
216
              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...
216
				$result = true;
217
			}
218
		}
219
		if ($result) {
220
			$this->dispatcher->dispatch(IGroup::class . '::postRemoveUser', new GenericEvent($this, [
221
				'user' => $user,
222
			]));
223
			if ($this->emitter) {
224
				$this->emitter->emit('\OC\Group', 'postRemoveUser', array($this, $user));
225
			}
226
			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...
227
				foreach ($this->users as $index => $groupUser) {
228
					if ($groupUser->getUID() === $user->getUID()) {
229
						unset($this->users[$index]);
230
						return;
231
					}
232
				}
233
			}
234
		}
235
	}
236
237
	/**
238
	 * search for users in the group by userid
239
	 *
240
	 * @param string $search
241
	 * @param int $limit
242
	 * @param int $offset
243
	 * @return \OC\User\User[]
244
	 */
245
	public function searchUsers($search, $limit = null, $offset = null) {
246
		$users = array();
247
		foreach ($this->backends as $backend) {
248
			$userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset);
249
			$users += $this->getVerifiedUsers($userIds);
250
			if (!is_null($limit) and $limit <= 0) {
251
				return $users;
252
			}
253
		}
254
		return $users;
255
	}
256
257
	/**
258
	 * returns the number of users matching the search string
259
	 *
260
	 * @param string $search
261
	 * @return int|bool
262
	 */
263
	public function count($search = '') {
264
		$users = false;
265
		foreach ($this->backends as $backend) {
266
			if($backend->implementsActions(\OC\Group\Backend::COUNT_USERS)) {
267
				if($users === false) {
268
					//we could directly add to a bool variable, but this would
269
					//be ugly
270
					$users = 0;
271
				}
272
				$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

272
				$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...
273
			}
274
		}
275
		return $users;
276
	}
277
278
	/**
279
	 * returns the number of disabled users
280
	 *
281
	 * @return int|bool
282
	 */
283
	public function countDisabled() {
284
		$users = false;
285
		foreach ($this->backends as $backend) {
286
			if($backend instanceOf ICountDisabledInGroup) {
287
				if($users === false) {
288
					//we could directly add to a bool variable, but this would
289
					//be ugly
290
					$users = 0;
291
				}
292
				$users += $backend->countDisabledInGroup($this->gid);
293
			}
294
		}
295
		return $users;
296
	}
297
298
	/**
299
	 * search for users in the group by displayname
300
	 *
301
	 * @param string $search
302
	 * @param int $limit
303
	 * @param int $offset
304
	 * @return \OC\User\User[]
305
	 */
306
	public function searchDisplayName($search, $limit = null, $offset = null) {
307
		$users = array();
308
		foreach ($this->backends as $backend) {
309
			$userIds = $backend->usersInGroup($this->gid, $search, $limit, $offset);
310
			$users = $this->getVerifiedUsers($userIds);
311
			if (!is_null($limit) and $limit <= 0) {
312
				return array_values($users);
313
			}
314
		}
315
		return array_values($users);
316
	}
317
318
	/**
319
	 * delete the group
320
	 *
321
	 * @return bool
322
	 */
323
	public function delete() {
324
		// Prevent users from deleting group admin
325
		if ($this->getGID() === 'admin') {
326
			return false;
327
		}
328
329
		$result = false;
330
		$this->dispatcher->dispatch(IGroup::class . '::preDelete', new GenericEvent($this));
0 ignored issues
show
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

330
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ IGroup::class . '::preDelete', 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...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

330
		$this->dispatcher->/** @scrutinizer ignore-call */ 
331
                     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...
331
		if ($this->emitter) {
332
			$this->emitter->emit('\OC\Group', 'preDelete', array($this));
333
		}
334
		foreach ($this->backends as $backend) {
335
			if ($backend->implementsActions(\OC\Group\Backend::DELETE_GROUP)) {
336
				$result = true;
337
				$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

337
				$backend->/** @scrutinizer ignore-call */ 
338
              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...
338
			}
339
		}
340
		if ($result) {
341
			$this->dispatcher->dispatch(IGroup::class . '::postDelete', new GenericEvent($this));
342
			if ($this->emitter) {
343
				$this->emitter->emit('\OC\Group', 'postDelete', array($this));
344
			}
345
		}
346
		return $result;
347
	}
348
349
	/**
350
	 * returns all the Users from an array that really exists
351
	 * @param string[] $userIds an array containing user IDs
352
	 * @return \OC\User\User[] an Array with the userId as Key and \OC\User\User as value
353
	 */
354
	private function getVerifiedUsers($userIds) {
355
		if (!is_array($userIds)) {
0 ignored issues
show
introduced by
The condition is_array($userIds) is always true.
Loading history...
356
			return array();
357
		}
358
		$users = array();
359
		foreach ($userIds as $userId) {
360
			$user = $this->userManager->get($userId);
361
			if (!is_null($user)) {
362
				$users[$userId] = $user;
363
			}
364
		}
365
		return $users;
366
	}
367
368
	/**
369
	 * @return bool
370
	 * @since 14.0.0
371
	 */
372
	public function canRemoveUser() {
373
		foreach ($this->backends as $backend) {
374
			if ($backend->implementsActions(GroupInterface::REMOVE_FROM_GOUP)) {
375
				return true;
376
			}
377
		}
378
		return false;
379
	}
380
381
	/**
382
	 * @return bool
383
	 * @since 14.0.0
384
	 */
385
	public function canAddUser() {
386
		foreach ($this->backends as $backend) {
387
			if ($backend->implementsActions(GroupInterface::ADD_TO_GROUP)) {
388
				return true;
389
			}
390
		}
391
		return false;
392
	}
393
394
	/**
395
	 * @return bool
396
	 * @since 16.0.0
397
	 */
398
	public function hideFromCollaboration(): bool {
399
		return array_reduce($this->backends, function(bool $hide, GroupInterface $backend) {
400
			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...
401
		}, false);
402
	}
403
}
404