Completed
Push — master ( 138f00...e3741d )
by Maxence
16s queued 10s
created

CirclesService::checkThatCircleIsNotFull()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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