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

MembersService   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 558
Duplicated Lines 8.78 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 51
lcom 1
cbo 12
dl 49
loc 558
rs 7.92
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A addMemberBasedOnItsType() 0 5 1
A __construct() 21 21 1
A addMember() 7 15 3
A addSingleMember() 0 23 1
A addMassiveMembers() 0 11 3
A addLocalMember() 0 12 3
A addEmailAddress() 0 8 2
A addContact() 0 8 2
A verifyIdentBasedOnItsType() 0 5 1
A verifyIdentLocalMember() 0 13 4
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 getMemberById() 0 3 1
B levelMember() 7 40 5
A removeMember() 14 37 3
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\MemberCantJoinCircleException;
45
use OCA\Circles\Exceptions\MemberDoesNotExistException;
46
use OCA\Circles\Exceptions\MemberIsNotModeratorException;
47
use OCA\Circles\Exceptions\MemberTypeCantEditLevelException;
48
use OCA\Circles\Model\Circle;
49
use OCA\Circles\Model\GlobalScale\GSEvent;
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 GSUpstreamService */
93
	private $gsUpstreamService;
94
95
	/** @var FileSharingBroadcaster */
96
	private $fileSharingBroadcaster;
97
98
	/** @var MiscService */
99
	private $miscService;
100
101
	/**
102
	 * MembersService constructor.
103
	 *
104
	 * @param string $userId
105
	 * @param IL10N $l10n
106
	 * @param IUserManager $userManager
107
	 * @param ConfigService $configService
108
	 * @param CirclesRequest $circlesRequest
109
	 * @param MembersRequest $membersRequest
110
	 * @param SharesRequest $sharesRequest
111
	 * @param TokensRequest $tokensRequest
112
	 * @param CirclesService $circlesService
113
	 * @param EventsService $eventsService
114
	 * @param FileSharingBroadcaster $fileSharingBroadcaster
115
	 * @param MiscService $miscService
116
	 */
117 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...
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
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...
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
		if (!$this->addMassiveMembers($circle, $ident, $type)) {
165
			$this->addSingleMember($circle, $ident, $type, $instance);
166
		}
167
168
		return $this->membersRequest->getMembers($circle->getUniqueId(), $circle->getHigherViewer(), $force);
169
	}
170
171
172
	/**
173
	 * add a single member to a circle.
174
	 *
175
	 * @param Circle $circle
176
	 * @param string $ident
177
	 * @param int $type
178
	 *
179
	 * @param string $instance
180
	 *
181
	 * @throws Exception
182
	 */
183
	private function addSingleMember(Circle $circle, $ident, $type, $instance = '') {
184
185
		$this->verifyIdentBasedOnItsType($ident, $type, $instance);
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_ADD);
196
		$event->setSeverity(GSEvent::SEVERITY_HIGH);
197
		$event->setCircle($circle);
198
		$event->setMember($member);
199
		$this->gsUpstreamService->newEvent($event);
200
201
//		$this->membersRequest->updateMember($member);
202
//		$this->fileSharingBroadcaster->sendMailAboutExistingShares($circle, $member);
203
//		$this->eventsService->onMemberNew($circle, $member);
204
205
	}
206
207
208
	/**
209
	 * add a bunch of users to a circle based on the type of the 'bunch'
210
	 *
211
	 * @param Circle $circle
212
	 * @param string $ident
213
	 * @param int $type
214
	 *
215
	 * @return bool
216
	 * @throws Exception
217
	 */
218
	private function addMassiveMembers(Circle $circle, $ident, $type) {
219
		if ($type === Member::TYPE_GROUP) {
220
			return $this->addGroupMembers($circle, $ident);
221
		}
222
223
		if ($type === Member::TYPE_USER) {
224
			return $this->addMassiveMails($circle, $ident);
225
		}
226
227
		return false;
228
	}
229
230
231
	/**
232
	 * add a new member based on its type.
233
	 *
234
	 * @param Circle $circle
235
	 * @param Member $member
236
	 *
237
	 * @throws CircleTypeNotValidException
238
	 * @throws MemberCantJoinCircleException
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 CircleTypeNotValidException
252
	 * @throws MemberCantJoinCircleException
253
	 */
254
	private function addLocalMember(Circle $circle, Member $member) {
255
256
		if ($member->getType() !== Member::TYPE_USER) {
257
			return;
258
		}
259
260
		$member->inviteToCircle($circle->getType());
261
262
		if ($this->configService->isInvitationSkipped()) {
263
			$member->joinCircle($circle->getType());
264
		}
265
	}
266
267
268
	/**
269
	 * add mail address as contact.
270
	 *
271
	 * @param Member $member
272
	 */
273
	private function addEmailAddress(Member $member) {
274
275
		if ($member->getType() !== Member::TYPE_MAIL) {
276
			return;
277
		}
278
279
		$member->addMemberToCircle();
280
	}
281
282
283
	/**
284
	 * Add contact as member.
285
	 *
286
	 * @param Member $member
287
	 */
288
	private function addContact(Member $member) {
289
290
		if ($member->getType() !== Member::TYPE_CONTACT) {
291
			return;
292
		}
293
294
		$member->addMemberToCircle();
295
	}
296
297
298
	/**
299
	 * Verify the availability of an ident, based on its type.
300
	 *
301
	 * @param string $ident
302
	 * @param int $type
303
	 * @param string $instance
304
	 *
305
	 * @throws EmailAccountInvalidFormatException
306
	 * @throws NoUserException
307
	 */
308
	public function verifyIdentBasedOnItsType(&$ident, $type, string $instance = '') {
309
		$this->verifyIdentLocalMember($ident, $type, $instance);
310
		$this->verifyIdentEmailAddress($ident, $type);
311
		$this->verifyIdentContact($ident, $type);
312
	}
313
314
315
	/**
316
	 * Verify if a local account is valid.
317
	 *
318
	 * @param $ident
319
	 * @param $type
320
	 *
321
	 * @param string $instance
322
	 *
323
	 * @throws NoUserException
324
	 */
325
	private function verifyIdentLocalMember(&$ident, $type, string $instance = '') {
326
		if ($type !== Member::TYPE_USER) {
327
			return;
328
		}
329
330
		if ($instance === '') {
331
			try {
332
				$ident = $this->miscService->getRealUserId($ident);
333
			} 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...
334
				throw new NoUserException($this->l10n->t("This user does not exist"));
335
			}
336
		}
337
	}
338
339
340
	/**
341
	 * Verify if a mail have a valid format.
342
	 *
343
	 * @param $ident
344
	 * @param $type
345
	 *
346
	 * @throws EmailAccountInvalidFormatException
347
	 */
348
	private function verifyIdentEmailAddress(&$ident, $type) {
349
350
		if ($type !== Member::TYPE_MAIL) {
351
			return;
352
		}
353
354
		if ($this->configService->isAccountOnly()) {
355
			throw new EmailAccountInvalidFormatException(
356
				$this->l10n->t('You cannot add a mail address as member of your Circle')
357
			);
358
		}
359
360
		if (!filter_var($ident, FILTER_VALIDATE_EMAIL)) {
361
			throw new EmailAccountInvalidFormatException(
362
				$this->l10n->t('Email format is not valid')
363
			);
364
		}
365
	}
366
367
368
	/**
369
	 * Verify if a contact exist in current user address books.
370
	 *
371
	 * @param $ident
372
	 * @param $type
373
	 *
374
	 * @throws NoUserException
375
	 * @throws EmailAccountInvalidFormatException
376
	 */
377
	private function verifyIdentContact(&$ident, $type) {
378
		if ($type !== Member::TYPE_CONTACT) {
379
			return;
380
		}
381
382
		if ($this->configService->isAccountOnly()) {
383
			throw new EmailAccountInvalidFormatException(
384
				$this->l10n->t('You cannot add a contact as member of your Circle')
385
			);
386
		}
387
388
		$tmpContact = $this->userId . ':' . $ident;
389
		$result = MiscService::getContactData($tmpContact);
390
		if (empty($result)) {
391
			throw new NoUserException($this->l10n->t("This contact is not available"));
392
		}
393
394
		$ident = $tmpContact;
395
	}
396
397
398
	/**
399
	 * @param Circle $circle
400
	 * @param string $groupId
401
	 *
402
	 * @return bool
403
	 * @throws Exception
404
	 */
405
	private function addGroupMembers(Circle $circle, $groupId) {
406
407
		$group = OC::$server->getGroupManager()
408
							->get($groupId);
409
		if ($group === null) {
410
			throw new GroupDoesNotExistException($this->l10n->t('This group does not exist'));
411
		}
412
413
		foreach ($group->getUsers() as $user) {
414
			try {
415
				$this->addSingleMember($circle, $user->getUID(), Member::TYPE_USER);
416
			} catch (MemberAlreadyExistsException $e) {
417
			} catch (Exception $e) {
418
				throw $e;
419
			}
420
		}
421
422
		return true;
423
	}
424
425
426
	/**
427
	 * @param Circle $circle
428
	 * @param string $mails
429
	 *
430
	 * @return bool
431
	 */
432
	private function addMassiveMails(Circle $circle, $mails) {
433
434
		$mails = trim($mails);
435
		if (substr($mails, 0, 6) !== 'mails:') {
436
			return false;
437
		}
438
439
		$mails = substr($mails, 6);
440
		foreach (explode(' ', $mails) as $mail) {
441
			if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
442
				continue;
443
			}
444
445
			try {
446
				$this->addMember($circle->getUniqueId(), $mail, Member::TYPE_MAIL);
0 ignored issues
show
Bug introduced by
The call to addMember() misses a required argument $instance.

This check looks for function calls that miss required arguments.

Loading history...
447
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
448
			}
449
		}
450
451
		return true;
452
	}
453
454
455
	/**
456
	 * getMember();
457
	 *
458
	 * Will return any data of a user related to a circle (as a Member). User can be a 'non-member'
459
	 * Viewer needs to be at least Member of the Circle
460
	 *
461
	 * @param $circleId
462
	 * @param $userId
463
	 * @param $type
464
	 * @param bool $forceAll
465
	 *
466
	 * @return Member
467
	 * @throws CircleDoesNotExistException
468
	 * @throws ConfigNoCircleAvailableException
469
	 * @throws MemberDoesNotExistException
470
	 */
471
	public function getMember($circleId, $userId, $type, $forceAll = false) {
472
		if (!$forceAll) {
473
			$this->circlesRequest->getCircle($circleId, $this->userId)
474
								 ->getHigherViewer()
475
								 ->hasToBeMember();
476
		}
477
478
		$member = $this->membersRequest->forceGetMember($circleId, $userId, $type);
479
		$member->setNote('');
480
481
		return $member;
482
	}
483
484
485
	/**
486
	 * @param string $memberId
487
	 *
488
	 * @return Member
489
	 * @throws MemberDoesNotExistException
490
	 */
491
	public function getMemberById(string $memberId): Member {
492
		return $this->membersRequest->forceGetMemberById($memberId);
493
	}
494
495
496
	/**
497
	 * @param string $circleUniqueId
498
	 * @param string $name
499
	 * @param int $type
500
	 * @param int $level
501
	 * @param bool $force
502
	 *
503
	 * @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...
504
	 * @throws CircleDoesNotExistException
505
	 * @throws CircleTypeNotValidException
506
	 * @throws ConfigNoCircleAvailableException
507
	 * @throws MemberDoesNotExistException
508
	 * @throws MemberTypeCantEditLevelException
509
	 * @throws Exception
510
	 */
511
	public function levelMember($circleUniqueId, $name, $type, $instance, $level, bool $force = false) {
512
513
		$level = (int)$level;
514
		if ($force === false) {
515
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
516
		} else {
517
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
518
		}
519
520
		if ($circle->getType() === Circle::CIRCLES_PERSONAL) {
521
			throw new CircleTypeNotValidException(
522
				$this->l10n->t('You cannot edit level in a personal circle')
523
			);
524
		}
525
526
//<<<<<<< master
527
//		$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type);
528
//		$member->levelHasToBeEditable();
529
//		$this->updateMemberLevel($circle, $member, $level, $force);
530
//=======
531
			$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type, $instance);
532
			if ($member->getLevel() !== $level) {
533
				$event = new GSevent(GSEvent::MEMBER_LEVEL);
534
				$event->setCircle($circle);
535
536
				$event->getData()
537
					  ->sInt('level', $level);
538
				$event->setMember($member);
539
				$this->gsUpstreamService->newEvent($event);
540
			}
541
542 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...
543
			return $this->membersRequest->getMembers(
544
				$circle->getUniqueId(), $circle->getHigherViewer()
545
			);
546
		} else {
547
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
548
		}
549
550
	}
551
552
553
	/**
554
	 * @param string $circleUniqueId
555
	 * @param string $name
556
	 * @param int $type
557
	 * @param string $instance
558
	 * @param bool $force
559
	 *
560
	 * @return Member[]
561
	 * @throws CircleDoesNotExistException
562
	 * @throws ConfigNoCircleAvailableException
563
	 * @throws MemberDoesNotExistException
564
	 * @throws MemberIsNotModeratorException
565
	 * @throws Exception
566
	 */
567
	public function removeMember(string $circleUniqueId, string $name, int $type, string $instance, bool $force = false): array {
568
569 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...
570
					$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
571
					$circle->getHigherViewer()
572
						   ->hasToBeModerator();
573
				} else {
574
					$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
575
				}
576
577
			$member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type, $instance);
578
579
			$event = new GSevent(GSEvent::MEMBER_REMOVE);
580
			$event->setCircle($circle);
581
			$event->setMember($member);
582
			$this->gsUpstreamService->newEvent($event);
583
584
//			$member->hasToBeMemberOrAlmost();
585
//			$member->cantBeOwner();
586
//
587
//			$circle->getHigherViewer()
588
//				   ->hasToBeHigherLevel($member->getLevel());
589
590
//		$this->eventsService->onMemberLeaving($circle, $member);
591
//
592
//		$this->membersRequest->removeMember($member);
593
//		$this->sharesRequest->removeSharesFromMember($member);
594
//		$this->tokensRequest->removeTokensFromMember($member);
595
596 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...
597
			return $this->membersRequest->getMembers(
598
				$circle->getUniqueId(), $circle->getHigherViewer()
599
			);
600
		} else {
601
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
602
		}
603
	}
604
605
606
	/**
607
	 * When a user is removed, remove him from all Circles
608
	 *
609
	 * @param $userId
610
	 */
611
	public function onUserRemoved($userId) {
612
		// TODO: broadcast the event to all instances
613
		$this->membersRequest->removeAllMembershipsFromUser($userId);
614
	}
615
616
617
}
618
619