Completed
Pull Request — master (#362)
by Maxence
01:49
created

MembersService::addMassiveMembers()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 3
nop 3
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
		if (!$this->addMassiveMembers($circle, $ident, $type)) {
164
			$this->addSingleMember($circle, $ident, $type, $instance, $force);
165
		}
166
167
		return $this->membersRequest->getMembers($circle->getUniqueId(), $circle->getHigherViewer(), $force);
168
	}
169
170
171
	/**
172
	 * add a single member to a circle.
173
	 *
174
	 * @param Circle $circle
175
	 * @param string $ident
176
	 * @param int $type
177
	 *
178
	 * @param string $instance
179
	 * @param bool $force
180
	 *
181
	 * @throws EmailAccountInvalidFormatException
182
	 * @throws NoUserException
183
	 */
184
	private function addSingleMember(Circle $circle, $ident, $type, $instance = '', bool $force = false) {
185
186
		$this->verifyIdentBasedOnItsType($ident, $type, $instance);
187
188
		$member = $this->membersRequest->getFreshNewMember($circle->getUniqueId(), $ident, $type, $instance);
189
190
		$event = new GSEvent(GSEvent::MEMBER_ADD, false, $force);
191
		$event->setSeverity(GSEvent::SEVERITY_HIGH);
192
193
		$event->setCircle($circle);
194
		$event->setMember($member);
195
		$this->gsUpstreamService->newEvent($event);
196
	}
197
198
199
	/**
200
	 * add a bunch of users to a circle based on the type of the 'bunch'
201
	 *
202
	 * @param Circle $circle
203
	 * @param string $ident
204
	 * @param int $type
205
	 *
206
	 * @return bool
207
	 * @throws Exception
208
	 */
209
	private function addMassiveMembers(Circle $circle, $ident, $type) {
210
		if ($type === Member::TYPE_GROUP) {
211
			return $this->addGroupMembers($circle, $ident);
212
		}
213
214
		if ($type === Member::TYPE_USER) {
215
			return $this->addMassiveMails($circle, $ident);
216
		}
217
218
		return false;
219
	}
220
221
222
	/**
223
	 * add a new member based on its type.
224
	 *
225
	 * @param Circle $circle
226
	 * @param Member $member
227
	 *
228
	 * @throws CircleTypeNotValidException
229
	 * @throws MemberCantJoinCircleException
230
	 */
231
	public function addMemberBasedOnItsType(Circle $circle, Member &$member) {
232
		$this->addLocalMember($circle, $member);
233
		$this->addEmailAddress($member);
234
		$this->addContact($member);
235
	}
236
237
238
	/**
239
	 * @param Circle $circle
240
	 * @param Member $member
241
	 *
242
	 * @throws CircleTypeNotValidException
243
	 * @throws MemberCantJoinCircleException
244
	 */
245
	private function addLocalMember(Circle $circle, Member $member) {
246
247
		if ($member->getType() !== Member::TYPE_USER) {
248
			return;
249
		}
250
251
		$member->inviteToCircle($circle->getType());
252
253
		if ($circle->getType() === Circle::CIRCLES_CLOSED && $this->configService->isInvitationSkipped()) {
254
			$member->joinCircle($circle->getType());
255
		}
256
	}
257
258
259
	/**
260
	 * add mail address as contact.
261
	 *
262
	 * @param Member $member
263
	 */
264
	private function addEmailAddress(Member $member) {
265
266
		if ($member->getType() !== Member::TYPE_MAIL) {
267
			return;
268
		}
269
270
		$member->addMemberToCircle();
271
	}
272
273
274
	/**
275
	 * // TODO - check this on GS setup
276
	 * Add contact as member.
277
	 *
278
	 * @param Member $member
279
	 */
280
	private function addContact(Member $member) {
281
282
		if ($member->getType() !== Member::TYPE_CONTACT) {
283
			return;
284
		}
285
286
		$member->addMemberToCircle();
287
	}
288
289
290
	/**
291
	 * // TODO - check this on GS setup
292
	 * Verify the availability of an ident, based on its type.
293
	 *
294
	 * @param string $ident
295
	 * @param int $type
296
	 * @param string $instance
297
	 *
298
	 * @throws EmailAccountInvalidFormatException
299
	 * @throws NoUserException
300
	 */
301
	public function verifyIdentBasedOnItsType(&$ident, $type, string $instance = '') {
302
		if ($instance === $this->configService->getLocalCloudId()) {
303
			$instance = '';
304
		}
305
306
		$this->verifyIdentLocalMember($ident, $type, $instance);
307
		$this->verifyIdentEmailAddress($ident, $type);
308
		$this->verifyIdentContact($ident, $type);
309
	}
310
311
312
	/**
313
	 * Verify if a local account is valid.
314
	 *
315
	 * @param $ident
316
	 * @param $type
317
	 *
318
	 * @param string $instance
319
	 *
320
	 * @throws NoUserException
321
	 */
322
	private function verifyIdentLocalMember(&$ident, $type, string $instance = '') {
323
		if ($type !== Member::TYPE_USER) {
324
			return;
325
		}
326
327
		if ($instance === '') {
328
			try {
329
				$ident = $this->miscService->getRealUserId($ident);
330
			} 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...
331
				throw new NoUserException($this->l10n->t("This user does not exist"));
332
			}
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
	 * // TODO - check this on GS setup
425
	 *
426
	 * @param Circle $circle
427
	 * @param string $mails
428
	 *
429
	 * @return bool
430
	 */
431
	private function addMassiveMails(Circle $circle, $mails) {
432
433
		$mails = trim($mails);
434
		if (substr($mails, 0, 6) !== 'mails:') {
435
			return false;
436
		}
437
438
		$mails = substr($mails, 6);
439
		foreach (explode(' ', $mails) as $mail) {
440
			if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
441
				continue;
442
			}
443
444
			try {
445
				$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...
446
			} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
447
			}
448
		}
449
450
		return true;
451
	}
452
453
454
	/**
455
	 * getMember();
456
	 *
457
	 * Will return any data of a user related to a circle (as a Member). User can be a 'non-member'
458
	 * Viewer needs to be at least Member of the Circle
459
	 *
460
	 * @param $circleId
461
	 * @param $userId
462
	 * @param $type
463
	 * @param bool $forceAll
464
	 *
465
	 * @return Member
466
	 * @throws CircleDoesNotExistException
467
	 * @throws ConfigNoCircleAvailableException
468
	 * @throws MemberDoesNotExistException
469
	 */
470
	public function getMember($circleId, $userId, $type, $forceAll = false) {
471
		if (!$forceAll) {
472
			$this->circlesRequest->getCircle($circleId, $this->userId)
473
								 ->getHigherViewer()
474
								 ->hasToBeMember();
475
		}
476
477
		$member = $this->membersRequest->forceGetMember($circleId, $userId, $type);
478
		$member->setNote('');
479
480
		return $member;
481
	}
482
483
484
	/**
485
	 * @param string $memberId
486
	 *
487
	 * @return Member
488
	 * @throws MemberDoesNotExistException
489
	 */
490
	public function getMemberById(string $memberId): Member {
491
		return $this->membersRequest->forceGetMemberById($memberId);
492
	}
493
494
495
	/**
496
	 * @param string $circleUniqueId
497
	 * @param string $name
498
	 * @param int $type
499
	 * @param string $instance
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 Exception
509
	 */
510
	public function levelMember(
511
		string $circleUniqueId, string $name, int $type, string $instance, int $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
		$member = $this->membersRequest->forceGetMember($circle->getUniqueId(), $name, $type, $instance);
527
		if ($member->getLevel() !== $level) {
528
			$event = new GSEvent(GSEvent::MEMBER_LEVEL, false, $force);
529
			$event->setCircle($circle);
530
531
			$event->getData()
532
				  ->sInt('level', $level);
533
			$event->setMember($member);
534
			$this->gsUpstreamService->newEvent($event);
535
		}
536
537 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...
538
			return $this->membersRequest->getMembers(
539
				$circle->getUniqueId(), $circle->getHigherViewer()
540
			);
541
		} else {
542
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
543
		}
544
545
	}
546
547
548
	/**
549
	 * @param string $circleUniqueId
550
	 * @param string $name
551
	 * @param int $type
552
	 * @param string $instance
553
	 * @param bool $force
554
	 *
555
	 * @return Member[]
556
	 * @throws CircleDoesNotExistException
557
	 * @throws ConfigNoCircleAvailableException
558
	 * @throws MemberDoesNotExistException
559
	 * @throws MemberIsNotModeratorException
560
	 * @throws Exception
561
	 */
562
	public function removeMember(
563
		string $circleUniqueId, string $name, int $type, string $instance, bool $force = false
564
	): array {
565
566 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...
567
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
568
			$circle->getHigherViewer()
569
				   ->hasToBeModerator();
570
		} else {
571
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
572
		}
573
574
		$member = $this->membersRequest->forceGetMember($circleUniqueId, $name, $type, $instance);
575
576
		$event = new GSEvent(GSEvent::MEMBER_REMOVE, false, $force);
577
		$event->setCircle($circle);
578
		$event->setMember($member);
579
		$this->gsUpstreamService->newEvent($event);
580
581 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...
582
			return $this->membersRequest->getMembers(
583
				$circle->getUniqueId(), $circle->getHigherViewer()
584
			);
585
		} else {
586
			return $this->membersRequest->forceGetMembers($circle->getUniqueId());
587
		}
588
	}
589
590
591
	/**
592
	 * When a user is removed, remove him from all Circles
593
	 *
594
	 * @param string $userId
595
	 *
596
	 * @throws Exception
597
	 */
598
	public function onUserRemoved(string $userId) {
599
		$event = new GSEvent(GSEvent::USER_DELETED, true, true);
600
601
		$member = new Member($userId);
602
		$event->setMember($member);
603
		$event->getData()
604
			  ->s('userId', $userId);
605
606
		$this->gsUpstreamService->newEvent($event);
607
	}
608
609
610
}
611
612