Completed
Pull Request — master (#494)
by Maxence
05:14 queued 03:02
created

MembersService::getMember()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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