Completed
Pull Request — master (#362)
by Maxence
02:10 queued 11s
created

CirclesService::leaveCircle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
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
 * @author Vinicius Cubas Brand <[email protected]>
10
 * @author Daniel Tygel <[email protected]>
11
 *
12
 * @copyright 2017
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
namespace OCA\Circles\Service;
31
32
33
use Exception;
34
use OC;
35
use OCA\Circles\AppInfo\Application;
36
use OCA\Circles\Db\CircleProviderRequest;
37
use OCA\Circles\Db\CirclesRequest;
38
use OCA\Circles\Db\FederatedLinksRequest;
39
use OCA\Circles\Db\MembersRequest;
40
use OCA\Circles\Db\SharesRequest;
41
use OCA\Circles\Exceptions\CircleAlreadyExistsException;
42
use OCA\Circles\Exceptions\CircleDoesNotExistException;
43
use OCA\Circles\Exceptions\CircleTypeDisabledException;
44
use OCA\Circles\Exceptions\FederatedCircleNotAllowedException;
45
use OCA\Circles\Exceptions\MemberDoesNotExistException;
46
use OCA\Circles\Exceptions\MemberIsNotModeratorException;
47
use OCA\Circles\Exceptions\MemberIsNotOwnerException;
48
use OCA\Circles\Exceptions\MembersLimitException;
49
use OCA\Circles\Model\Circle;
50
use OCA\Circles\Model\GlobalScale\GSEvent;
51
use OCA\Circles\Model\Member;
52
use OCP\IGroupManager;
53
use OCP\IL10N;
54
55
class CirclesService {
56
57
	/** @var string */
58
	private $userId;
59
60
	/** @var IL10N */
61
	private $l10n;
62
63
	/** @var IGroupManager */
64
	private $groupManager;
65
66
	/** @var ConfigService */
67
	private $configService;
68
69
	/** @var CirclesRequest */
70
	private $circlesRequest;
71
72
	/** @var MembersRequest */
73
	private $membersRequest;
74
75
	/** @var SharesRequest */
76
	private $sharesRequest;
77
78
	/** @var FederatedLinksRequest */
79
	private $federatedLinksRequest;
80
81
	/** @var GSUpstreamService */
82
	private $gsUpstreamService;
83
84
	/** @var EventsService */
85
	private $eventsService;
86
87
	/** @var CircleProviderRequest */
88
	private $circleProviderRequest;
89
90
	/** @var MiscService */
91
	private $miscService;
92
93
94
	/**
95
	 * CirclesService constructor.
96
	 *
97
	 * @param string $userId
98
	 * @param IL10N $l10n
99
	 * @param IGroupManager $groupManager
100
	 * @param ConfigService $configService
101
	 * @param CirclesRequest $circlesRequest
102
	 * @param MembersRequest $membersRequest
103
	 * @param SharesRequest $sharesRequest
104
	 * @param FederatedLinksRequest $federatedLinksRequest
105
	 * @param GSUpstreamService $gsUpstreamService
106
	 * @param EventsService $eventsService
107
	 * @param CircleProviderRequest $circleProviderRequest
108
	 * @param MiscService $miscService
109
	 */
110 View Code Duplication
	public function __construct(
111
		$userId,
112
		IL10N $l10n,
113
		IGroupManager $groupManager,
114
		ConfigService $configService,
115
		CirclesRequest $circlesRequest,
116
		MembersRequest $membersRequest,
117
		SharesRequest $sharesRequest,
118
		FederatedLinksRequest $federatedLinksRequest,
119
		GSUpstreamService $gsUpstreamService,
120
		EventsService $eventsService,
121
		CircleProviderRequest $circleProviderRequest,
122
		MiscService $miscService
123
	) {
124
		$this->userId = $userId;
125
		$this->l10n = $l10n;
126
		$this->groupManager = $groupManager;
127
		$this->configService = $configService;
128
		$this->circlesRequest = $circlesRequest;
129
		$this->membersRequest = $membersRequest;
130
		$this->sharesRequest = $sharesRequest;
131
		$this->federatedLinksRequest = $federatedLinksRequest;
132
		$this->gsUpstreamService = $gsUpstreamService;
133
		$this->eventsService = $eventsService;
134
		$this->circleProviderRequest = $circleProviderRequest;
135
		$this->miscService = $miscService;
136
	}
137
138
139
	/**
140
	 * Create circle using this->userId as owner
141
	 *
142
	 * @param int|string $type
143
	 * @param string $name
144
	 *
145
	 * @return Circle
146
	 * @throws CircleTypeDisabledException
147
	 * @throws Exception
148
	 */
149
	public function createCircle($type, $name) {
150
		$type = $this->convertTypeStringToBitValue($type);
151
		$type = (int)$type;
152
153
		if ($type === '') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $type (integer) and '' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
154
			throw new CircleTypeDisabledException(
155
				$this->l10n->t('You need a specify a type of circle')
156
			);
157
		}
158
159
		if (!$this->configService->isCircleAllowed($type)) {
160
			throw new CircleTypeDisabledException(
161
				$this->l10n->t('You cannot create this type of circle')
162
			);
163
		}
164
165
		$circle = new Circle($type, $name);
166
167
//		try {
168
		$circle->generateUniqueId();
169
		// TODO: check uniqueness !!
170
		// TODO: check uniqueness !!
171
		// TODO: check uniqueness !!
172
		// TODO: check uniqueness !!
173
//		$this->circlesRequest->createCircle($circle, $this->userId);
174
175
		$owner = new Member($this->userId, Member::TYPE_USER);
176
		$owner->setCircleId($circle->getUniqueId())
177
			  ->setLevel(Member::LEVEL_OWNER)
178
			  ->setStatus(Member::STATUS_MEMBER);
179
		$circle->setOwner($owner)
180
			   ->setViewer($owner);
181
182
//			$this->membersRequest->createMember($circle->getOwner());
183
184
		$event = new GSevent(GSEvent::CIRCLE_CREATE, true);
185
		$event->setCircle($circle);
186
		$this->gsUpstreamService->newEvent($event);
187
188
//		} catch (CircleAlreadyExistsException $e) {
189
//			throw $e;
190
//		}
191
192
//		$this->eventsService->onCircleCreation($circle);
193
194
		return $circle;
195
	}
196
197
198
	/**
199
	 * list Circles depends on type (or all) and name (parts) and minimum level.
200
	 *
201
	 * @param string $userId
202
	 * @param mixed $type
203
	 * @param string $name
204
	 * @param int $level
205
	 *
206
	 * @param bool $forceAll
207
	 *
208
	 * @return Circle[]
209
	 * @throws CircleTypeDisabledException
210
	 * @throws Exception
211
	 */
212
	public function listCircles($userId, $type, $name = '', $level = 0, $forceAll = false) {
213
		$type = $this->convertTypeStringToBitValue($type);
214
215
		if ($userId === '') {
216
			throw new Exception('UserID cannot be null');
217
		}
218
219
		if (!$this->configService->isCircleAllowed((int)$type)) {
220
			throw new CircleTypeDisabledException(
221
				$this->l10n->t('You cannot display this type of circle')
222
			);
223
		}
224
225
		$data = [];
226
		$result = $this->circlesRequest->getCircles($userId, $type, $name, $level, $forceAll);
227
		foreach ($result as $item) {
228
			$data[] = $item;
229
		}
230
231
		return $data;
232
	}
233
234
235
	/**
236
	 * returns details on circle and its members if this->userId is a member itself.
237
	 *
238
	 * @param string $circleUniqueId
239
	 * @param bool $forceAll
240
	 *
241
	 * @return Circle
242
	 * @throws Exception
243
	 */
244
	public function detailsCircle($circleUniqueId, $forceAll = false) {
245
246
		try {
247
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId, '', $forceAll);
248
			if ($this->viewerIsAdmin()
249
				|| $circle->getHigherViewer()
250
						  ->isLevel(Member::LEVEL_MEMBER)
251
				|| $forceAll === true
252
			) {
253
				$this->detailsCircleMembers($circle);
254
				$this->detailsCircleLinkedGroups($circle);
255
				$this->detailsCircleFederatedCircles($circle);
256
			}
257
		} catch (Exception $e) {
258
			throw $e;
259
		}
260
261
		return $circle;
262
	}
263
264
265
	/**
266
	 * get the Members list and add the result to the Circle.
267
	 *
268
	 * @param Circle $circle
269
	 *
270
	 * @throws Exception
271
	 */
272
	private function detailsCircleMembers(Circle &$circle) {
273
		if ($this->viewerIsAdmin()) {
274
			$members = $this->membersRequest->forceGetMembers($circle->getUniqueId(), 0);
275
		} else {
276
			$members = $this->membersRequest->getMembers(
277
				$circle->getUniqueId(), $circle->getHigherViewer()
278
			);
279
		}
280
281
		$circle->setMembers($members);
282
	}
283
284
285
	/**
286
	 * get the Linked Group list and add the result to the Circle.
287
	 *
288
	 * @param Circle $circle
289
	 *
290
	 * @throws MemberDoesNotExistException
291
	 */
292
	private function detailsCircleLinkedGroups(Circle &$circle) {
293
		$groups = [];
294
		if ($this->configService->isLinkedGroupsAllowed()) {
295
			$groups =
296
				$this->membersRequest->getGroupsFromCircle(
297
					$circle->getUniqueId(), $circle->getHigherViewer()
298
				);
299
		}
300
301
		$circle->setGroups($groups);
302
	}
303
304
305
	/**
306
	 * get the Federated Circles list and add the result to the Circle.
307
	 *
308
	 * @param Circle $circle
309
	 */
310
	private function detailsCircleFederatedCircles(Circle &$circle) {
311
		$links = [];
312
313
		try {
314
			if ($this->configService->isFederatedCirclesAllowed()) {
315
				$circle->hasToBeFederated();
316
				$links = $this->federatedLinksRequest->getLinksFromCircle($circle->getUniqueId());
317
			}
318
		} catch (FederatedCircleNotAllowedException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
319
		}
320
321
		$circle->setLinks($links);
322
	}
323
324
325
	/**
326
	 * save new settings if current user is admin.
327
	 *
328
	 * @param string $circleUniqueId
329
	 * @param array $settings
330
	 *
331
	 * @return Circle
332
	 * @throws Exception
333
	 */
334
	public function settingsCircle($circleUniqueId, $settings) {
335
336
		try {
337
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
338
			$this->hasToBeOwner($circle->getHigherViewer());
339
340
			if (!$this->viewerIsAdmin()) {
341
				$settings['members_limit'] = $circle->getSetting('members_limit');
342
			}
343
344
			$ak = array_keys($settings);
345
			foreach ($ak AS $k) {
346
				$circle->setSetting($k, $settings[$k]);
347
			}
348
349
			$this->circlesRequest->updateCircle($circle, $this->userId);
350
351
			$this->eventsService->onSettingsChange($circle);
352
		} catch (Exception $e) {
353
			throw $e;
354
		}
355
356
		return $circle;
357
	}
358
359
360
	/**
361
	 * Join a circle.
362
	 *
363
	 * @param string $circleUniqueId
364
	 *
365
	 * @return null|Member
366
	 * @throws Exception
367
	 */
368
	public function joinCircle($circleUniqueId) {
369
		try {
370
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
371
			$member = $this->membersRequest->getFreshNewMember(
372
				$circleUniqueId, $this->userId, Member::TYPE_USER, ''
373
			);
374
375
			$event = new GSevent(GSEvent::MEMBER_JOIN);
376
			$event->setCircle($circle);
377
			$event->setMember($member);
378
			$this->gsUpstreamService->newEvent($event);
379
		} catch (Exception $e) {
380
			throw $e;
381
		}
382
383
		return $member;
384
	}
385
386
387
	/**
388
	 * Leave a circle.
389
	 *
390
	 * @param string $circleUniqueId
391
	 *
392
	 * @return null|Member
393
	 * @throws Exception
394
	 */
395
	public function leaveCircle($circleUniqueId) {
396
397
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
398
		$member = $circle->getViewer();
399
400
		$event = new GSevent(GSEvent::MEMBER_LEAVE);
401
		$event->setCircle($circle);
402
		$event->setMember($member);
403
		$this->gsUpstreamService->newEvent($event);
404
405
//		$member->hasToBeMemberOrAlmost();
406
//		$member->cantBeOwner();
407
//
408
//		$this->eventsService->onMemberLeaving($circle, $member);
409
//
410
//		$this->membersRequest->removeMember($member);
411
//		$this->sharesRequest->removeSharesFromMember($member);
412
413
		return $member;
414
	}
415
416
417
	/**
418
	 * destroy a circle.
419
	 *
420
	 * @param string $circleUniqueId
421
	 *
422
	 * @throws CircleDoesNotExistException
423
	 * @throws MemberIsNotModeratorException
424
	 * @throws MemberIsNotOwnerException
425
	 */
426
	public function removeCircle($circleUniqueId) {
427
428
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
429
430
		$this->hasToBeOwner($circle->getHigherViewer());
431
432
		$this->eventsService->onCircleDestruction($circle);
433
434
		$this->membersRequest->removeAllFromCircle($circleUniqueId);
435
		$this->circlesRequest->destroyCircle($circleUniqueId);
436
	}
437
438
439
	/**
440
	 * @param $circleName
441
	 *
442
	 * @return Circle|null
443
	 * @throws CircleDoesNotExistException
444
	 */
445
	public function infoCircleByName($circleName) {
446
		return $this->circlesRequest->forceGetCircleByName($circleName);
447
	}
448
449
450
	/**
451
	 * When a user is removed.
452
	 * Before deleting a user from the cloud, we assign a new owner to his Circles.
453
	 * Remove the Circle if it has no admin.
454
	 *
455
	 * @param string $userId
456
	 */
457
	public function onUserRemoved($userId) {
458
		$circles = $this->circlesRequest->getCircles($userId, 0, '', Member::LEVEL_OWNER);
459
460
		foreach ($circles as $circle) {
461
462
			$members =
463
				$this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_ADMIN);
464
465
			if (sizeof($members) === 1) {
466
				$this->circlesRequest->destroyCircle($circle->getUniqueId());
467
				continue;
468
			}
469
470
			$this->switchOlderAdminToOwner($circle, $members);
471
		}
472
	}
473
474
475
	/**
476
	 * switchOlderAdminToOwner();
477
	 *
478
	 * @param Circle $circle
479
	 * @param Member[] $members
480
	 */
481
	private function switchOlderAdminToOwner(Circle $circle, $members) {
482
483
		foreach ($members as $member) {
484
			if ($member->getLevel() === Member::LEVEL_ADMIN) {
485
				$member->setLevel(Member::LEVEL_OWNER);
486
				$this->membersRequest->updateMember($member);
487
				$this->eventsService->onMemberOwner($circle, $member);
488
489
				return;
490
			}
491
		}
492
493
	}
494
495
496
	/**
497
	 * Convert a Type in String to its Bit Value
498
	 *
499
	 * @param string $type
500
	 *
501
	 * @return int|mixed
502
	 */
503
	public function convertTypeStringToBitValue($type) {
504
		$strings = [
505
			'personal' => Circle::CIRCLES_PERSONAL,
506
			'secret'   => Circle::CIRCLES_SECRET,
507
			'closed'   => Circle::CIRCLES_CLOSED,
508
			'public'   => Circle::CIRCLES_PUBLIC,
509
			'all'      => Circle::CIRCLES_ALL
510
		];
511
512
		if (!key_exists(strtolower($type), $strings)) {
513
			return $type;
514
		}
515
516
		return $strings[strtolower($type)];
517
	}
518
519
520
	/**
521
	 * getCircleIcon()
522
	 *
523
	 * Return the right imagePath for a type of circle.
524
	 *
525
	 * @param string $type
526
	 * @param bool $png
527
	 *
528
	 * @return string
529
	 */
530
	public static function getCircleIcon($type, $png = false) {
531
532
		$ext = '.svg';
533
		if ($png === true) {
534
			$ext = '.png';
535
		}
536
537
		$urlGen = OC::$server->getURLGenerator();
538
		switch ($type) {
539
			case Circle::CIRCLES_PERSONAL:
540
				return $urlGen->getAbsoluteURL(
541
					$urlGen->imagePath(Application::APP_NAME, 'personal' . $ext)
542
				);
543
			case Circle::CIRCLES_CLOSED:
544
				return $urlGen->getAbsoluteURL(
545
					$urlGen->imagePath(Application::APP_NAME, 'closed' . $ext)
546
				);
547
			case Circle::CIRCLES_SECRET:
548
				return $urlGen->getAbsoluteURL(
549
					$urlGen->imagePath(Application::APP_NAME, 'secret' . $ext)
550
				);
551
			case Circle::CIRCLES_PUBLIC:
552
				return $urlGen->getAbsoluteURL(
553
					$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
554
				);
555
		}
556
557
		return $urlGen->getAbsoluteURL(
558
			$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
559
		);
560
	}
561
562
563
	/**
564
	 * @param string $circleUniqueIds
565
	 * @param int $limit
566
	 * @param int $offset
567
	 *
568
	 * @return array
569
	 */
570
	public function getFilesForCircles($circleUniqueIds, $limit = -1, $offset = 0) {
571
		if (!is_array($circleUniqueIds)) {
572
			$circleUniqueIds = [$circleUniqueIds];
573
		}
574
575
		$objectIds = $this->circleProviderRequest->getFilesForCircles(
576
			$this->userId, $circleUniqueIds, $limit, $offset
577
		);
578
579
		return $objectIds;
580
	}
581
582
583
	/**
584
	 * @param Circle $circle
585
	 *
586
	 * @throws MembersLimitException
587
	 */
588
	public function checkThatCircleIsNotFull(Circle $circle) {
589
590
		$members = $this->membersRequest->forceGetMembers(
591
			$circle->getUniqueId(), Member::LEVEL_MEMBER, true
592
		);
593
594
		$limit = $circle->getSetting('members_limit');
595
		if ($limit === -1) {
596
			return;
597
		}
598
		if ($limit === 0 || $limit === '' || $limit === null) {
599
			$limit = $this->configService->getAppValue(ConfigService::CIRCLES_MEMBERS_LIMIT);
600
		}
601
602
		if (sizeof($members) >= $limit) {
603
			throw new MembersLimitException(
604
				'This circle already reach its limit on the number of members'
605
			);
606
		}
607
608
	}
609
610
	/**
611
	 * @return bool
612
	 */
613
	public function viewerIsAdmin() {
614
		if ($this->userId === '') {
615
			return false;
616
		}
617
618
		return ($this->groupManager->isAdmin($this->userId));
619
	}
620
621
622
	/**
623
	 * should be moved.
624
	 *
625
	 * @param Member $member
626
	 *
627
	 * @throws MemberIsNotOwnerException
628
	 */
629 View Code Duplication
	public function hasToBeOwner(Member $member) {
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...
630
		if (!$this->groupManager->isAdmin($this->userId)
631
			&& $member->getLevel() < Member::LEVEL_OWNER) {
632
			throw new MemberIsNotOwnerException(
633
				$this->l10n->t('This member is not the owner of the circle')
634
			);
635
		}
636
	}
637
638
639
	/**
640
	 * should be moved.
641
	 *
642
	 * @param Member $member
643
	 *
644
	 * @throws MemberIsNotOwnerException
645
	 */
646 View Code Duplication
	public function hasToBeAdmin(Member $member) {
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...
647
		if (!$this->groupManager->isAdmin($member->getUserId())
648
			&& $member->getLevel() < Member::LEVEL_ADMIN) {
649
			throw new MemberIsNotOwnerException(
650
				$this->l10n->t('This member is not an admin of the circle')
651
			);
652
		}
653
	}
654
}
655