Completed
Push — master ( b4b3e8...fb5007 )
by Maxence
02:30 queued 42s
created

MembersService::updateMember()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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