Completed
Pull Request — master (#416)
by Maxence
02:01
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;
32
use OC\User\NoUserException;
33
use OCA\Circles\Circles\FileSharingBroadcaster;
34
use OCA\Circles\Db\CirclesRequest;
35
use OCA\Circles\Db\MembersRequest;
36
use OCA\Circles\Db\SharesRequest;
37
use OCA\Circles\Db\TokensRequest;
38
use OCA\Circles\Exceptions\CircleDoesNotExistException;
39
use OCA\Circles\Exceptions\CircleTypeNotValidException;
40
use OCA\Circles\Exceptions\ConfigNoCircleAvailableException;
41
use OCA\Circles\Exceptions\EmailAccountInvalidFormatException;
42
use OCA\Circles\Exceptions\GroupDoesNotExistException;
43
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
44
use OCA\Circles\Exceptions\MemberDoesNotExistException;
45
use OCA\Circles\Exceptions\MemberIsNotModeratorException;
46
use OCA\Circles\Exceptions\MemberIsOwnerException;
47
use OCA\Circles\Exceptions\MemberTypeCantEditLevelException;
48
use OCA\Circles\Exceptions\ModeratorIsNotHighEnoughException;
49
use OCA\Circles\Model\Circle;
50
use OCA\Circles\Model\Member;
51
use OCP\IL10N;
52
use OCP\IUserManager;
53
54
55
/**
56
 * Class MembersService
57
 *
58
 * @package OCA\Circles\Service
59
 */
60
class MembersService {
61
62
	/** @var string */
63
	private $userId;
64
65
	/** @var IL10N */
66
	private $l10n;
67
68
	/** @var IUserManager */
69
	private $userManager;
70
71
	/** @var ConfigService */
72
	private $configService;
73
74
	/** @var CirclesRequest */
75
	private $circlesRequest;
76
77
	/** @var MembersRequest */
78
	private $membersRequest;
79
80
	/** @var SharesRequest */
81
	private $sharesRequest;
82
83
	/** @var TokensRequest */
84
	private $tokensRequest;
85
86
	/** @var CirclesService */
87
	private $circlesService;
88
89
	/** @var EventsService */
90
	private $eventsService;
91
92
	/** @var FileSharingBroadcaster */
93
	private $fileSharingBroadcaster;
94
95
	/** @var MiscService */
96
	private $miscService;
97
98
	/**
99
	 * MembersService constructor.
100
	 *
101
	 * @param string $userId
102
	 * @param IL10N $l10n
103
	 * @param IUserManager $userManager
104
	 * @param ConfigService $configService
105
	 * @param CirclesRequest $circlesRequest
106
	 * @param MembersRequest $membersRequest
107
	 * @param SharesRequest $sharesRequest
108
	 * @param TokensRequest $tokensRequest
109
	 * @param CirclesService $circlesService
110
	 * @param EventsService $eventsService
111
	 * @param FileSharingBroadcaster $fileSharingBroadcaster
112
	 * @param MiscService $miscService
113
	 */
114 View Code Duplication
	public function __construct(
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...
115
		$userId, IL10N $l10n, IUserManager $userManager, ConfigService $configService,
116
		CirclesRequest $circlesRequest, MembersRequest $membersRequest, SharesRequest $sharesRequest,
117
		TokensRequest $tokensRequest, CirclesService $circlesService, EventsService $eventsService,
118
		FileSharingBroadcaster $fileSharingBroadcaster, MiscService $miscService
119
	) {
120
		$this->userId = $userId;
121
		$this->l10n = $l10n;
122
		$this->userManager = $userManager;
123
		$this->configService = $configService;
124
		$this->circlesRequest = $circlesRequest;
125
		$this->membersRequest = $membersRequest;
126
		$this->sharesRequest = $sharesRequest;
127
		$this->tokensRequest = $tokensRequest;
128
		$this->circlesService = $circlesService;
129
		$this->eventsService = $eventsService;
130
		$this->fileSharingBroadcaster = $fileSharingBroadcaster;
131
		$this->miscService = $miscService;
132
	}
133
134
135
	/**
136
	 * addMember();
137
	 *
138
	 * add a new member to a circle.
139
	 *
140
	 * @param string $circleUniqueId
141
	 * @param $ident
142
	 * @param int $type
143
	 *
144
	 * @param bool $force
145
	 *
146
	 * @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...
147
	 * @throws Exception
148
	 */
149
	public function addMember($circleUniqueId, $ident, $type, bool $force = false) {
150
151 View Code Duplication
		if ($force === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
152
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
153
		} else {
154
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
155
			$circle->getHigherViewer()
156
				   ->hasToBeModerator();
157
		}
158
159
		if (!$this->addMassiveMembers($circle, $ident, $type)) {
160
			$this->addSingleMember($circle, $ident, $type);
161
		}
162
163
		return $this->membersRequest->getMembers($circle->getUniqueId(), $circle->getHigherViewer(), $force);
164
	}
165
166
167
	/**
168
	 * add a single member to a circle.
169
	 *
170
	 * @param Circle $circle
171
	 * @param string $ident
172
	 * @param int $type
173
	 *
174
	 * @throws MemberAlreadyExistsException
175
	 * @throws Exception
176
	 */
177
	private function addSingleMember(Circle $circle, $ident, $type) {
178
		$this->verifyIdentBasedOnItsType($ident, $type);
179
180
		$member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type);
181
		$member->hasToBeInviteAble();
182
183
		$this->circlesService->checkThatCircleIsNotFull($circle);
184
185
		$this->addMemberBasedOnItsType($circle, $member);
186
187
		$this->membersRequest->updateMember($member);
188
		$this->fileSharingBroadcaster->sendMailAboutExistingShares($circle, $member);
189
190
		$this->eventsService->onMemberNew($circle, $member);
191
	}
192
193
194
	/**
195
	 * add a bunch of users to a circle based on the type of the 'bunch'
196
	 *
197
	 * @param Circle $circle
198
	 * @param string $ident
199
	 * @param int $type
200
	 *
201
	 * @return bool
202
	 * @throws Exception
203
	 */
204
	private function addMassiveMembers(Circle $circle, $ident, $type) {
205
206
		if ($type === Member::TYPE_GROUP) {
207
			return $this->addGroupMembers($circle, $ident);
208
		}
209
210
		if ($type === Member::TYPE_USER) {
211
			return $this->addMassiveMails($circle, $ident);
212
		}
213
214
		return false;
215
	}
216
217
218
	/**
219
	 * add a new member based on its type.
220
	 *
221
	 * @param Circle $circle
222
	 * @param Member $member
223
	 *
224
	 * @throws Exception
225
	 */
226
	private function addMemberBasedOnItsType(Circle $circle, Member &$member) {
227
		$this->addLocalMember($circle, $member);
228
		$this->addEmailAddress($member);
229
		$this->addContact($member);
230
	}
231
232
233
	/**
234
	 * @param Circle $circle
235
	 * @param Member $member
236
	 *
237
	 * @throws Exception
238
	 */
239
	private function addLocalMember(Circle $circle, Member $member) {
240
241
		if ($member->getType() !== Member::TYPE_USER) {
242
			return;
243
		}
244
245
		$member->inviteToCircle($circle->getType());
246
247
		if ($circle->getType() === Circle::CIRCLES_CLOSED && $this->configService->isInvitationSkipped()) {
248
			$member->joinCircle($circle->getType());
249
		}
250
	}
251
252
253
	/**
254
	 * add mail address as contact.
255
	 *
256
	 * @param Member $member
257
	 *
258
	 * @throws Exception
259
	 */
260
	private function addEmailAddress(Member $member) {
261
262
		if ($member->getType() !== Member::TYPE_MAIL) {
263
			return;
264
		}
265
266
		$member->addMemberToCircle();
267
	}
268
269
270
	/**
271
	 * Add contact as member.
272
	 *
273
	 * @param Member $member
274
	 *
275
	 * @throws Exception
276
	 */
277
	private function addContact(Member $member) {
278
279
		if ($member->getType() !== Member::TYPE_CONTACT) {
280
			return;
281
		}
282
283
		$member->addMemberToCircle();
284
	}
285
286
287
	/**
288
	 * Verify the availability of an ident, based on its type.
289
	 *
290
	 * @param string $ident
291
	 * @param int $type
292
	 *
293
	 * @throws Exception
294
	 */
295
	private function verifyIdentBasedOnItsType(&$ident, $type) {
296
		$this->verifyIdentLocalMember($ident, $type);
297
		$this->verifyIdentEmailAddress($ident, $type);
298
		$this->verifyIdentContact($ident, $type);
299
	}
300
301
302
	/**
303
	 * Verify if a local account is valid.
304
	 *
305
	 * @param $ident
306
	 * @param $type
307
	 *
308
	 * @throws NoUserException
309
	 */
310
	private function verifyIdentLocalMember(&$ident, $type) {
311
		if ($type !== Member::TYPE_USER) {
312
			return;
313
		}
314
315
		try {
316
			$ident = $this->miscService->getRealUserId($ident);
317
		} 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...
318
			throw new NoUserException($this->l10n->t("This user does not exist"));
319
		}
320
	}
321
322
323
	/**
324
	 * Verify if a mail have a valid format.
325
	 *
326
	 * @param $ident
327
	 * @param $type
328
	 *
329
	 * @throws EmailAccountInvalidFormatException
330
	 */
331
	private function verifyIdentEmailAddress(&$ident, $type) {
332
333
		if ($type !== Member::TYPE_MAIL) {
334
			return;
335
		}
336
337
		if ($this->configService->isAccountOnly()) {
338
			throw new EmailAccountInvalidFormatException(
339
				$this->l10n->t('You cannot add a mail address as member of your Circle')
340
			);
341
		}
342
343
		if (!filter_var($ident, FILTER_VALIDATE_EMAIL)) {
344
			throw new EmailAccountInvalidFormatException(
345
				$this->l10n->t('Email format is not valid')
346
			);
347
		}
348
	}
349
350
351
	/**
352
	 * Verify if a contact exist in current user address books.
353
	 *
354
	 * @param $ident
355
	 * @param $type
356
	 *
357
	 * @throws NoUserException
358
	 * @throws EmailAccountInvalidFormatException
359
	 */
360
	private function verifyIdentContact(&$ident, $type) {
361
		if ($type !== Member::TYPE_CONTACT) {
362
			return;
363
		}
364
365
		if ($this->configService->isAccountOnly()) {
366
			throw new EmailAccountInvalidFormatException(
367
				$this->l10n->t('You cannot add a contact as member of your Circle')
368
			);
369
		}
370
371
		$tmpContact = $this->userId . ':' . $ident;
372
		$result = MiscService::getContactData($tmpContact);
373
		if (empty($result)) {
374
			throw new NoUserException($this->l10n->t("This contact is not available"));
375
		}
376
377
		$ident = $tmpContact;
378
	}
379
380
381
	/**
382
	 * @param Circle $circle
383
	 * @param string $groupId
384
	 *
385
	 * @return bool
386
	 * @throws Exception
387
	 */
388
	private function addGroupMembers(Circle $circle, $groupId) {
389
390
		$group = OC::$server->getGroupManager()
391
							->get($groupId);
392
		if ($group === null) {
393
			throw new GroupDoesNotExistException($this->l10n->t('This group does not exist'));
394
		}
395
396
		foreach ($group->getUsers() as $user) {
397
			try {
398
				$this->addSingleMember($circle, $user->getUID(), Member::TYPE_USER);
399
			} catch (MemberAlreadyExistsException $e) {
400
			} catch (Exception $e) {
401
				throw $e;
402
			}
403
		}
404
405
		return true;
406
	}
407
408
409
	/**
410
	 * @param Circle $circle
411
	 * @param string $mails
412
	 *
413
	 * @return bool
414
	 */
415
	private function addMassiveMails(Circle $circle, $mails) {
416
417
		$mails = trim($mails);
418
		if (substr($mails, 0, 6) !== 'mails:') {
419
			return false;
420
		}
421
422
		$mails = substr($mails, 6);
423
		foreach (explode(' ', $mails) as $mail) {
424
			if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
425
				continue;
426
			}
427
428
			try {
429
				$this->addMember($circle->getUniqueId(), $mail, Member::TYPE_MAIL);
430
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
431
			}
432
		}
433
434
		return true;
435
	}
436
437
438
	/**
439
	 * getMember();
440
	 *
441
	 * Will return any data of a user related to a circle (as a Member). User can be a 'non-member'
442
	 * Viewer needs to be at least Member of the Circle
443
	 *
444
	 * @param $circleId
445
	 * @param $userId
446
	 * @param $type
447
	 * @param bool $forceAll
448
	 *
449
	 * @return Member
450
	 * @throws CircleDoesNotExistException
451
	 * @throws ConfigNoCircleAvailableException
452
	 * @throws MemberDoesNotExistException
453
	 */
454
	public function getMember($circleId, $userId, $type, $forceAll = false) {
455
		if (!$forceAll) {
456
			$this->circlesRequest->getCircle($circleId, $this->userId)
457
								 ->getHigherViewer()
458
								 ->hasToBeMember();
459
		}
460
461
		$member = $this->membersRequest->forceGetMember($circleId, $userId, $type);
462
		$member->setNote('');
463
464
		return $member;
465
	}
466
467
468
	/**
469
	 * @param string $memberId
470
	 *
471
	 * @return Member
472
	 * @throws MemberDoesNotExistException
473
	 */
474
	public function getMemberById(string $memberId): Member {
475
		return $this->membersRequest->forceGetMemberById($memberId);
476
	}
477
478
479
	/**
480
	 * @param string $circleUniqueId
481
	 * @param string $name
482
	 * @param int $type
483
	 * @param int $level
484
	 * @param bool $force
485
	 *
486
	 * @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...
487
	 * @throws CircleDoesNotExistException
488
	 * @throws CircleTypeNotValidException
489
	 * @throws ConfigNoCircleAvailableException
490
	 * @throws MemberDoesNotExistException
491
	 * @throws MemberTypeCantEditLevelException
492
	 * @throws Exception
493
	 */
494
	public function levelMember($circleUniqueId, $name, $type, $level, bool $force = false) {
495
496
		$level = (int)$level;
497
		if ($force === false) {
498
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
499
		} else {
500
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
501
		}
502
503
		if ($circle->getType() === Circle::CIRCLES_PERSONAL) {
504
			throw new CircleTypeNotValidException(
505
				$this->l10n->t('You cannot edit level in a personal circle')
506
			);
507
		}
508
509
		$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type);
510
		$member->levelHasToBeEditable();
511
		$this->updateMemberLevel($circle, $member, $level, $force);
512
513 View Code Duplication
		if ($force === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
514
			return $this->membersRequest->getMembers(
515
				$circle->getUniqueId(), $circle->getHigherViewer()
516
			);
517
		} else {
518
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
519
		}
520
521
	}
522
523
524
	/**
525
	 * @param Circle $circle
526
	 * @param Member $member
527
	 * @param $level
528
	 * @param bool $force
529
	 *
530
	 * @throws Exception
531
	 */
532
	private function updateMemberLevel(Circle $circle, Member $member, $level, bool $force = false) {
533
		if ($member->getLevel() === $level) {
534
			return;
535
		}
536
537
		if ($level === Member::LEVEL_OWNER) {
538
			$this->switchOwner($circle, $member, $force);
539
		} else {
540
			$this->editMemberLevel($circle, $member, $level, $force);
541
		}
542
543
		$this->eventsService->onMemberLevel($circle, $member);
544
	}
545
546
547
	/**
548
	 * @param Circle $circle
549
	 * @param Member $member
550
	 * @param $level
551
	 * @param bool $force
552
	 *
553
	 * @throws Exception
554
	 */
555
	private function editMemberLevel(Circle $circle, Member &$member, $level, bool $force = false) {
556
		if ($force === false) {
557
			$isMod = $circle->getHigherViewer();
558
			$isMod->hasToBeModerator();
559
			$isMod->hasToBeHigherLevel($level);
560
561
			$member->hasToBeMember();
562
			$isMod->hasToBeHigherLevel($member->getLevel());
563
		}
564
565
		$member->cantBeOwner();
566
567
		$member->setLevel($level);
568
		$this->membersRequest->updateMember($member);
569
	}
570
571
	/**
572
	 * @param Circle $circle
573
	 * @param Member $member
574
	 * @param bool $force
575
	 *
576
	 * @throws Exception
577
	 */
578
	private function switchOwner(Circle $circle, Member &$member, bool $force = false) {
579
		if ($force === false) {
580
			$isMod = $circle->getHigherViewer();
581
582
			// should already be possible from an NCAdmin, but not enabled in the frontend.
583
			$this->circlesService->hasToBeOwner($isMod);
584
		} else {
585
			$isMod = $circle->getOwner();
586
		}
587
588
		$member->hasToBeMember();
589
		$member->cantBeOwner();
590
591
		$member->setLevel(Member::LEVEL_OWNER);
592
		$this->membersRequest->updateMember($member);
593
594
		$isMod->setLevel(Member::LEVEL_ADMIN);
595
		$this->membersRequest->updateMember($isMod);
596
	}
597
598
599
	/**
600
	 * @param string $circleUniqueId
601
	 * @param string $name
602
	 * @param $type
603
	 * @param bool $force
604
	 *
605
	 * @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...
606
	 * @throws CircleDoesNotExistException
607
	 * @throws ConfigNoCircleAvailableException
608
	 * @throws MemberDoesNotExistException
609
	 * @throws MemberIsNotModeratorException
610
	 * @throws MemberIsOwnerException
611
	 * @throws ModeratorIsNotHighEnoughException
612
	 */
613
	public function removeMember($circleUniqueId, $name, $type, bool $force = false) {
614
615 View Code Duplication
		if ($force === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
616
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
617
			$circle->getHigherViewer()
618
				   ->hasToBeModerator();
619
		} else {
620
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
621
		}
622
623
		$member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type);
624
		$member->hasToBeMemberOrAlmost();
625
		$member->cantBeOwner();
626
627
		if ($force === false) {
628
			$circle->getHigherViewer()
629
				   ->hasToBeHigherLevel($member->getLevel());
630
		}
631
632
		$this->eventsService->onMemberLeaving($circle, $member);
633
634
		$this->membersRequest->removeMember($member);
635
		$this->sharesRequest->removeSharesFromMember($member);
636
		$this->tokensRequest->removeTokensFromMember($member);
637
638 View Code Duplication
		if ($force === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
639
			return $this->membersRequest->getMembers(
640
				$circle->getUniqueId(), $circle->getHigherViewer()
641
			);
642
		} else {
643
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
644
		}
645
	}
646
647
648
	/**
649
	 * When a user is removed, remove him from all Circles
650
	 *
651
	 * @param $userId
652
	 */
653
	public function onUserRemoved($userId) {
654
		$this->membersRequest->removeAllMembershipsFromUser($userId);
655
	}
656
657
658
}
659
660