Completed
Push — master ( 4fa0dd...87dfa7 )
by Maxence
02:01
created

MembersService::addEmailAddress()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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