Completed
Pull Request — master (#384)
by Tortue
02:31
created

CirclesService::checkThatCircleIsNotFull()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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