Completed
Pull Request — master (#362)
by Maxence
02:57
created

MembersService   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 598
Duplicated Lines 3.51 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 15
dl 21
loc 598
rs 8.64
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 21 21 1
A addMember() 0 18 3
A addSingleMember() 0 23 2
A addMassiveMembers() 0 11 3
A addMemberBasedOnItsType() 0 5 1
A addLocalMember() 0 12 3
A addEmailAddress() 0 8 2
A addContact() 0 8 2
A verifyIdentBasedOnItsType() 0 5 1
A verifyIdentLocalMember() 0 11 3
A verifyIdentEmailAddress() 0 18 4
A verifyIdentContact() 0 19 4
A addGroupMembers() 0 19 5
A addMassiveMails() 0 21 5
A getMember() 0 12 2
A levelMember() 0 32 3
A removeMember() 0 33 2
A onUserRemoved() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MembersService 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 MembersService, and based on these observations, apply Extract Interface, too.

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