Completed
Push — master ( d72bce...460524 )
by Maxence
01:56 queued 01:54
created

MembersService::onUserRemoved()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * Circles - bring cloud-users closer
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\Circles\Service;
28
29
30
use Exception;
31
use OC\User\NoUserException;
32
use OCA\Circles\Db\CirclesRequest;
33
use OCA\Circles\Db\MembersRequest;
34
use OCA\Circles\Db\SharesRequest;
35
use OCA\Circles\Exceptions\CircleTypeNotValidException;
36
use OCA\Circles\Exceptions\EmailAccountInvalidFormatException;
37
use OCA\Circles\Exceptions\GroupDoesNotExistException;
38
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
39
use OCA\Circles\Exceptions\MembersLimitException;
40
use OCA\Circles\Model\Circle;
41
use OCA\Circles\Model\Member;
42
use OCP\IL10N;
43
use OCP\IUserManager;
44
45
class MembersService {
46
47
	/** @var string */
48
	private $userId;
49
50
	/** @var IL10N */
51
	private $l10n;
52
53
	/** @var IUserManager */
54
	private $userManager;
55
56
	/** @var ConfigService */
57
	private $configService;
58
59
	/** @var CirclesRequest */
60
	private $circlesRequest;
61
62
	/** @var MembersRequest */
63
	private $membersRequest;
64
65
	/** @var SharesRequest */
66
	private $sharesRequest;
67
68
	/** @var CirclesService */
69
	private $circlesService;
70
71
	/** @var EventsService */
72
	private $eventsService;
73
74
	/** @var MiscService */
75
	private $miscService;
76
77
	/**
78
	 * MembersService constructor.
79
	 *
80
	 * @param string $userId
81
	 * @param IL10N $l10n
82
	 * @param IUserManager $userManager
83
	 * @param ConfigService $configService
84
	 * @param CirclesRequest $circlesRequest
85
	 * @param MembersRequest $membersRequest
86
	 * @param SharesRequest $sharesRequest
87
	 * @param CirclesService $circlesService
88
	 * @param EventsService $eventsService
89
	 * @param MiscService $miscService
90
	 */
91
	public function __construct(
92
		$userId, IL10N $l10n, IUserManager $userManager, ConfigService $configService,
93
		CirclesRequest $circlesRequest, MembersRequest $membersRequest,
94
		SharesRequest $sharesRequest, CirclesService $circlesService, EventsService $eventsService,
95
		MiscService $miscService
96
	) {
97
		$this->userId = $userId;
98
		$this->l10n = $l10n;
99
		$this->userManager = $userManager;
100
		$this->configService = $configService;
101
		$this->circlesRequest = $circlesRequest;
102
		$this->membersRequest = $membersRequest;
103
		$this->sharesRequest = $sharesRequest;
104
		$this->circlesService = $circlesService;
105
		$this->eventsService = $eventsService;
106
		$this->miscService = $miscService;
107
	}
108
109
110
	/**
111
	 * addMember();
112
	 *
113
	 * add a new member to a circle.
114
	 *
115
	 * @param string $circleUniqueId
116
	 * @param $ident
117
	 * @param int $type
118
	 *
119
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Member[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
120
	 * @throws \Exception
121
	 */
122 View Code Duplication
	public function addMember($circleUniqueId, $ident, $type) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
123
124
		try {
125
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
126
			$circle->getHigherViewer()
127
				   ->hasToBeModerator();
128
129
			if (!$this->addMassiveMembers($circle, $ident, $type)) {
130
				$this->addSingleMember($circle, $ident, $type);
131
			}
132
		} catch (\Exception $e) {
133
			throw $e;
134
		}
135
136
		return $this->membersRequest->getMembers(
137
			$circle->getUniqueId(), $circle->getHigherViewer()
138
		);
139
	}
140
141
142
	/**
143
	 * add a single member to a circle.
144
	 *
145
	 * @param Circle $circle
146
	 * @param string $ident
147
	 * @param int $type
148
	 *
149
	 * @throws MemberAlreadyExistsException
150
	 * @throws Exception
151
	 */
152
	private function addSingleMember(Circle $circle, $ident, $type) {
153
		$this->verifyIdentBasedOnItsType($ident, $type);
154
155
		$member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type);
156
		$member->hasToBeInviteAble();
157
158
		$this->circlesService->checkThatCircleIsNotFull($circle);
159
160
		$this->addMemberBasedOnItsType($circle, $member);
161
162
		$this->membersRequest->updateMember($member);
163
		$this->eventsService->onMemberNew($circle, $member);
164
	}
165
166
167
	/**
168
	 * add a bunch of users to a circle based on the type of the 'bunch'
169
	 *
170
	 * @param Circle $circle
171
	 * @param string $ident
172
	 * @param int $type
173
	 *
174
	 * @return bool
175
	 * @throws Exception
176
	 */
177
	private function addMassiveMembers(Circle $circle, $ident, $type) {
178
179
		if ($type === Member::TYPE_GROUP) {
180
			return $this->addGroupMembers($circle, $ident);
181
		}
182
183
		if ($type === Member::TYPE_USER) {
184
			return $this->addMassiveMails($circle, $ident);
185
		}
186
187
		return false;
188
	}
189
190
191
	/**
192
	 * add a new member based on its type.
193
	 *
194
	 * @param Circle $circle
195
	 * @param Member $member
196
	 *
197
	 * @throws Exception
198
	 */
199
	private function addMemberBasedOnItsType(Circle $circle, Member &$member) {
200
		$this->addLocalMember($circle, $member);
201
		$this->addEmailAddress($member);
202
		$this->addContact($member);
203
	}
204
205
206
	/**
207
	 * @param Circle $circle
208
	 * @param Member $member
209
	 *
210
	 * @throws \Exception
211
	 */
212
	private function addLocalMember(Circle $circle, Member $member) {
213
214
		if ($member->getType() !== Member::TYPE_USER) {
215
			return;
216
		}
217
218
		$member->inviteToCircle($circle->getType());
219
	}
220
221
222
	/**
223
	 * add mail address as contact.
224
	 *
225
	 * @param Member $member
226
	 *
227
	 * @throws \Exception
228
	 */
229
	private function addEmailAddress(Member $member) {
230
231
		if ($member->getType() !== Member::TYPE_MAIL) {
232
			return;
233
		}
234
235
		$member->addMemberToCircle();
236
	}
237
238
239
	/**
240
	 * Add contact as member.
241
	 *
242
	 * @param Member $member
243
	 *
244
	 * @throws \Exception
245
	 */
246
	private function addContact(Member $member) {
247
248
		if ($member->getType() !== Member::TYPE_CONTACT) {
249
			return;
250
		}
251
252
		$member->addMemberToCircle();
253
	}
254
255
256
	/**
257
	 * Verify the availability of an ident, based on its type.
258
	 *
259
	 * @param string $ident
260
	 * @param int $type
261
	 *
262
	 * @throws Exception
263
	 */
264
	private function verifyIdentBasedOnItsType(&$ident, $type) {
265
		$this->verifyIdentLocalMember($ident, $type);
266
		$this->verifyIdentEmailAddress($ident, $type);
267
		$this->verifyIdentContact($ident, $type);
268
	}
269
270
271
	/**
272
	 * Verify if a local account is valid.
273
	 *
274
	 * @param $ident
275
	 * @param $type
276
	 *
277
	 * @throws NoUserException
278
	 */
279
	private function verifyIdentLocalMember(&$ident, $type) {
280
		if ($type !== Member::TYPE_USER) {
281
			return;
282
		}
283
284
		try {
285
			$ident = $this->miscService->getRealUserId($ident);
286
		} catch (NoUserException $e) {
0 ignored issues
show
Bug introduced by
The class OC\User\NoUserException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
287
			throw new NoUserException($this->l10n->t("This user does not exist"));
288
		}
289
	}
290
291
292
	/**
293
	 * Verify if a mail have a valid format.
294
	 *
295
	 * @param $ident
296
	 * @param $type
297
	 *
298
	 * @throws EmailAccountInvalidFormatException
299
	 */
300
	private function verifyIdentEmailAddress(&$ident, $type) {
301
		if ($type !== Member::TYPE_MAIL) {
302
			return;
303
		}
304
305
		if (!filter_var($ident, FILTER_VALIDATE_EMAIL)) {
306
			throw new EmailAccountInvalidFormatException(
307
				$this->l10n->t('Email format is not valid')
308
			);
309
		}
310
	}
311
312
313
	/**
314
	 * Verify if a contact exist in current user address books.
315
	 *
316
	 * @param $ident
317
	 * @param $type
318
	 *
319
	 * @throws NoUserException
320
	 */
321
	private function verifyIdentContact(&$ident, $type) {
322
		if ($type !== Member::TYPE_CONTACT) {
323
			return;
324
		}
325
326
		$tmpContact = $this->userId . ':' . $ident;
327
		$result = MiscService::getContactData($tmpContact);
328
		if (empty($result)) {
329
			throw new NoUserException($this->l10n->t("This contact is not available"));
330
		}
331
332
		$ident = $tmpContact;
333
	}
334
335
336
	/**
337
	 * @param Circle $circle
338
	 * @param string $groupId
339
	 *
340
	 * @return bool
341
	 * @throws \Exception
342
	 */
343
	private function addGroupMembers(Circle $circle, $groupId) {
344
345
		$group = \OC::$server->getGroupManager()
346
							 ->get($groupId);
347
		if ($group === null) {
348
			throw new GroupDoesNotExistException($this->l10n->t('This group does not exist'));
349
		}
350
351
		foreach ($group->getUsers() as $user) {
352
			try {
353
				$this->addSingleMember($circle, $user->getUID(), Member::TYPE_USER);
354
			} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
355
			} catch (\Exception $e) {
356
				throw $e;
357
			}
358
		}
359
360
		return true;
361
	}
362
363
364
	/**
365
	 * @param Circle $circle
366
	 * @param string $mails
367
	 *
368
	 * @return bool
369
	 */
370
	private function addMassiveMails(Circle $circle, $mails) {
371
372
		foreach (explode(' ', trim($mails)) as $mail) {
373
			if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
374
				return false;
375
			}
376
		}
377
378
		foreach (explode(' ', trim($mails)) as $mail) {
379
			try {
380
				$this->addMember($circle->getUniqueId(), $mail, Member::TYPE_MAIL);
381
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
382
			}
383
		}
384
385
		return true;
386
	}
387
388
389
	/**
390
	 * getMember();
391
	 *
392
	 * Will return any data of a user related to a circle (as a Member). User can be a 'non-member'
393
	 * Viewer needs to be at least Member of the Circle
394
	 *
395
	 * @param $circleId
396
	 * @param $userId
397
	 * @param $type
398
	 * @param bool $forceAll
399
	 *
400
	 * @return Member
401
	 * @throws Exception
402
	 */
403
	public function getMember($circleId, $userId, $type, $forceAll = false) {
404
405
		try {
406
			if (!$forceAll) {
407
				$this->circlesRequest->getCircle($circleId, $this->userId)
408
									 ->getHigherViewer()
409
									 ->hasToBeMember();
410
			}
411
412
			$member = $this->membersRequest->forceGetMember($circleId, $userId, $type);
413
			$member->setNote('');
414
415
			return $member;
416
		} catch (\Exception $e) {
417
			throw $e;
418
		}
419
	}
420
421
422
	/**
423
	 * @param string $circleUniqueId
424
	 * @param string $name
425
	 * @param int $type
426
	 * @param int $level
427
	 *
428
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Member[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
429
	 * @throws \Exception
430
	 */
431
	public function levelMember($circleUniqueId, $name, $type, $level) {
432
433
		$level = (int)$level;
434
		try {
435
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
436
			if ($circle->getType() === Circle::CIRCLES_PERSONAL) {
437
				throw new CircleTypeNotValidException(
438
					$this->l10n->t('You cannot edit level in a personal circle')
439
				);
440
			}
441
442
			$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type);
443
			$member->levelHasToBeEditable();
444
			$this->updateMemberLevel($circle, $member, $level);
445
446
			return $this->membersRequest->getMembers(
447
				$circle->getUniqueId(), $circle->getHigherViewer()
448
			);
449
		} catch (\Exception $e) {
450
			throw $e;
451
		}
452
453
	}
454
455
456
	/**
457
	 * @param Circle $circle
458
	 * @param Member $member
459
	 * @param $level
460
	 *
461
	 * @throws Exception
462
	 */
463
	private function updateMemberLevel(Circle $circle, Member $member, $level) {
464
		if ($member->getLevel() === $level) {
465
			return;
466
		}
467
468
		if ($level === Member::LEVEL_OWNER) {
469
			$this->switchOwner($circle, $member);
470
		} else {
471
			$this->editMemberLevel($circle, $member, $level);
472
		}
473
474
		$this->eventsService->onMemberLevel($circle, $member);
475
	}
476
477
478
	/**
479
	 * @param Circle $circle
480
	 * @param Member $member
481
	 * @param $level
482
	 *
483
	 * @throws \Exception
484
	 */
485 View Code Duplication
	private function editMemberLevel(Circle $circle, Member &$member, $level) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
486
		try {
487
			$isMod = $circle->getHigherViewer();
488
			$isMod->hasToBeModerator();
489
			$isMod->hasToBeHigherLevel($level);
490
491
			$member->hasToBeMember();
492
			$member->cantBeOwner();
493
			$isMod->hasToBeHigherLevel($member->getLevel());
494
495
			$member->setLevel($level);
496
			$this->membersRequest->updateMember($member);
497
		} catch (\Exception $e) {
498
			throw $e;
499
		}
500
501
	}
502
503
	/**
504
	 * @param Circle $circle
505
	 * @param Member $member
506
	 *
507
	 * @throws \Exception
508
	 */
509
	private function switchOwner(Circle $circle, Member &$member) {
510
		try {
511
			$isMod = $circle->getHigherViewer();
512
513
			// should already be possible from an NCAdmin, but not enabled in the frontend.
514
			$this->circlesService->hasToBeOwner($isMod);
515
516
			$member->hasToBeMember();
517
			$member->cantBeOwner();
518
519
			$member->setLevel(Member::LEVEL_OWNER);
520
			$this->membersRequest->updateMember($member);
521
522
			$isMod->setLevel(Member::LEVEL_ADMIN);
523
			$this->membersRequest->updateMember($isMod);
524
525
		} catch (\Exception $e) {
526
			throw $e;
527
		}
528
	}
529
530
531
	/**
532
	 * @param string $circleUniqueId
533
	 * @param string $name
534
	 * @param $type
535
	 *
536
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Member[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
537
	 * @throws \Exception
538
	 */
539
	public function removeMember($circleUniqueId, $name, $type) {
540
541
		try {
542
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
543
			$circle->getHigherViewer()
544
				   ->hasToBeModerator();
545
546
			$member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type);
547
			$member->hasToBeMemberOrAlmost();
548
			$member->cantBeOwner();
549
550
			$circle->getHigherViewer()
551
				   ->hasToBeHigherLevel($member->getLevel());
552
		} catch (\Exception $e) {
553
			throw $e;
554
		}
555
556
		$this->eventsService->onMemberLeaving($circle, $member);
557
558
		$this->membersRequest->removeMember($member);
559
		$this->sharesRequest->removeSharesFromMember($member);
560
561
		return $this->membersRequest->getMembers(
562
			$circle->getUniqueId(), $circle->getHigherViewer()
563
		);
564
	}
565
566
567
	/**
568
	 * When a user is removed, remove him from all Circles
569
	 *
570
	 * @param $userId
571
	 */
572
	public function onUserRemoved($userId) {
573
		$this->membersRequest->removeAllMembershipsFromUser($userId);
574
	}
575
576
577
}
578