Completed
Pull Request — master (#394)
by Tortue
01:54
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
			if (!$this->viewerIsAdmin()) {
322
				$settings['members_limit'] = $circle->getSetting('members_limit');
323
			}
324
325
			$ak = array_keys($settings);
326
			foreach ($ak AS $k) {
327
				$circle->setSetting($k, $settings[$k]);
328
			}
329
330
			$this->circlesRequest->updateCircle($circle, $this->userId);
331
332
			$this->eventsService->onSettingsChange($circle);
333
		} catch (\Exception $e) {
334
			throw $e;
335
		}
336
337
		return $circle;
338
	}
339
340
341
	/**
342
	 * Join a circle.
343
	 *
344
	 * @param string $circleUniqueId
345
	 *
346
	 * @return null|Member
347
	 * @throws \Exception
348
	 */
349
	public function joinCircle($circleUniqueId) {
350
351
		try {
352
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
353
354
			$member = $this->membersRequest->getFreshNewMember(
355
				$circleUniqueId, $this->userId, Member::TYPE_USER
356
			);
357
			$member->hasToBeAbleToJoinTheCircle();
358
			$this->checkThatCircleIsNotFull($circle);
359
360
			$member->joinCircle($circle->getType());
361
			$this->membersRequest->updateMember($member);
362
363
			$this->eventsService->onMemberNew($circle, $member);
364
		} catch (\Exception $e) {
365
			throw $e;
366
		}
367
368
		return $member;
369
	}
370
371
372
	/**
373
	 * Leave a circle.
374
	 *
375
	 * @param string $circleUniqueId
376
	 *
377
	 * @return null|Member
378
	 * @throws \Exception
379
	 */
380
	public function leaveCircle($circleUniqueId) {
381
382
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
383
		$member = $circle->getViewer();
384
385
		$member->hasToBeMemberOrAlmost();
386
		$member->cantBeOwner();
387
388
		$this->eventsService->onMemberLeaving($circle, $member);
389
390
		$this->membersRequest->removeMember($member);
391
		$this->sharesRequest->removeSharesFromMember($member);
392
393
		return $member;
394
	}
395
396
397
	/**
398
	 * destroy a circle.
399
	 *
400
	 * @param string $circleUniqueId
401
	 *
402
	 * @param bool $force
403
	 *
404
	 * @throws CircleDoesNotExistException
405
	 * @throws MemberIsNotOwnerException
406
	 * @throws \OCA\Circles\Exceptions\ConfigNoCircleAvailableException
407
	 */
408
	public function removeCircle($circleUniqueId, bool $force = false) {
409
410 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...
411
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
412
		} else {
413
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
414
			$this->hasToBeOwner($circle->getHigherViewer());
415
		}
416
417
418
		$this->eventsService->onCircleDestruction($circle);
419
420
		$this->membersRequest->removeAllFromCircle($circleUniqueId);
421
		$this->circlesRequest->destroyCircle($circleUniqueId);
422
	}
423
424
425
	/**
426
	 * @param $circleName
427
	 *
428
	 * @return Circle|null
429
	 * @throws CircleDoesNotExistException
430
	 */
431
	public function infoCircleByName($circleName) {
432
		return $this->circlesRequest->forceGetCircleByName($circleName);
433
	}
434
435
436
	/**
437
	 * When a user is removed.
438
	 * Before deleting a user from the cloud, we assign a new owner to his Circles.
439
	 * Remove the Circle if it has no admin.
440
	 *
441
	 * @param string $userId
442
	 */
443
	public function onUserRemoved($userId) {
444
		$circles = $this->circlesRequest->getCircles($userId, 0, '', Member::LEVEL_OWNER);
445
446
		foreach ($circles as $circle) {
447
448
			$members =
449
				$this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_ADMIN);
450
451
			if (sizeof($members) === 1) {
452
				$this->circlesRequest->destroyCircle($circle->getUniqueId());
453
				continue;
454
			}
455
456
			$this->switchOlderAdminToOwner($circle, $members);
457
		}
458
	}
459
460
461
	/**
462
	 * switchOlderAdminToOwner();
463
	 *
464
	 * @param Circle $circle
465
	 * @param Member[] $members
466
	 */
467
	private function switchOlderAdminToOwner(Circle $circle, $members) {
468
469
		foreach ($members as $member) {
470
			if ($member->getLevel() === Member::LEVEL_ADMIN) {
471
				$member->setLevel(Member::LEVEL_OWNER);
472
				$this->membersRequest->updateMember($member);
473
				$this->eventsService->onMemberOwner($circle, $member);
474
475
				return;
476
			}
477
		}
478
479
	}
480
481
482
	/**
483
	 * Convert a Type in String to its Bit Value
484
	 *
485
	 * @param string $type
486
	 *
487
	 * @return int|mixed
488
	 */
489
	public function convertTypeStringToBitValue($type) {
490
		$strings = [
491
			'personal' => Circle::CIRCLES_PERSONAL,
492
			'secret'   => Circle::CIRCLES_SECRET,
493
			'closed'   => Circle::CIRCLES_CLOSED,
494
			'public'   => Circle::CIRCLES_PUBLIC,
495
			'all'      => Circle::CIRCLES_ALL
496
		];
497
498
		if (!key_exists(strtolower($type), $strings)) {
499
			return $type;
500
		}
501
502
		return $strings[strtolower($type)];
503
	}
504
505
506
	/**
507
	 * getCircleIcon()
508
	 *
509
	 * Return the right imagePath for a type of circle.
510
	 *
511
	 * @param string $type
512
	 * @param bool $png
513
	 *
514
	 * @return string
515
	 */
516
	public static function getCircleIcon($type, $png = false) {
517
518
		$ext = '.svg';
519
		if ($png === true) {
520
			$ext = '.png';
521
		}
522
523
		$urlGen = \OC::$server->getURLGenerator();
524
		switch ($type) {
525
			case Circle::CIRCLES_PERSONAL:
526
				return $urlGen->getAbsoluteURL(
527
					$urlGen->imagePath(Application::APP_NAME, 'personal' . $ext)
528
				);
529
			case Circle::CIRCLES_CLOSED:
530
				return $urlGen->getAbsoluteURL(
531
					$urlGen->imagePath(Application::APP_NAME, 'closed' . $ext)
532
				);
533
			case Circle::CIRCLES_SECRET:
534
				return $urlGen->getAbsoluteURL(
535
					$urlGen->imagePath(Application::APP_NAME, 'secret' . $ext)
536
				);
537
			case Circle::CIRCLES_PUBLIC:
538
				return $urlGen->getAbsoluteURL(
539
					$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
540
				);
541
		}
542
543
		return $urlGen->getAbsoluteURL(
544
			$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
545
		);
546
	}
547
548
549
	/**
550
	 * @param string $circleUniqueIds
551
	 * @param int $limit
552
	 * @param int $offset
553
	 *
554
	 * @return array
555
	 */
556
	public function getFilesForCircles($circleUniqueIds, $limit = -1, $offset = 0) {
557
		if (!is_array($circleUniqueIds)) {
558
			$circleUniqueIds = [$circleUniqueIds];
559
		}
560
561
		$objectIds = $this->circleProviderRequest->getFilesForCircles(
562
			$this->userId, $circleUniqueIds, $limit, $offset
563
		);
564
565
		return $objectIds;
566
	}
567
568
569
	/**
570
	 * @param Circle $circle
571
	 *
572
	 * @throws MembersLimitException
573
	 */
574
	public function checkThatCircleIsNotFull(Circle $circle) {
575
576
		$members = $this->membersRequest->forceGetMembers(
577
			$circle->getUniqueId(), Member::LEVEL_MEMBER, true
578
		);
579
580
		$limit = (int) $circle->getSetting('members_limit');
581
		if ($limit === -1) {
582
			return;
583
		}
584
		if ($limit === 0) {
585
			$limit = $this->configService->getAppValue(ConfigService::CIRCLES_MEMBERS_LIMIT);
586
		}
587
588
		if (sizeof($members) >= $limit) {
589
			throw new MembersLimitException(
590
				'This circle already reach its limit on the number of members'
591
			);
592
		}
593
594
	}
595
596
	/**
597
	 * @return bool
598
	 */
599
	public function viewerIsAdmin() {
600
		if ($this->userId === '') {
601
			return false;
602
		}
603
604
		return ($this->groupManager->isAdmin($this->userId));
605
	}
606
607
608
	/**
609
	 * should be moved.
610
	 *
611
	 * @param Member $member
612
	 *
613
	 * @throws MemberIsNotOwnerException
614
	 */
615 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...
616
		if (!$this->groupManager->isAdmin($this->userId)
617
			&& $member->getLevel() < Member::LEVEL_OWNER) {
618
			throw new MemberIsNotOwnerException(
619
				$this->l10n->t('This member is not the owner of the circle')
620
			);
621
		}
622
	}
623
624
625
	/**
626
	 * should be moved.
627
	 *
628
	 * @param Member $member
629
	 *
630
	 * @throws MemberIsNotOwnerException
631
	 */
632 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...
633
		if (!$this->groupManager->isAdmin($member->getUserId())
634
			&& $member->getLevel() < Member::LEVEL_ADMIN) {
635
			throw new MemberIsNotOwnerException(
636
				$this->l10n->t('This member is not an admin of the circle')
637
			);
638
		}
639
	}
640
}
641