Completed
Push — master ( 4da19b...370775 )
by Maxence
18s queued 12s
created

CirclesService::listCircles()   A

Complexity

Conditions 4
Paths 4

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 4
nop 5
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
		if ($userId === null) {
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
			if (!$forceAll) {
277
				$circle = $this->circlesRequest->getCircle(
278
					$circleUniqueId, $this->userId, Member::TYPE_USER, '', $forceAll
279
				);
280
			} else {
281
				$circle = $this->circlesRequest->getCircleFromUniqueId($circleUniqueId);
282
			}
283
			if ($this->viewerIsAdmin()
284
				|| $circle->getHigherViewer()
285
						  ->isLevel(Member::LEVEL_MEMBER)
286
				|| $forceAll === true
287
			) {
288
				$this->detailsCircleMembers($circle);
289
				$this->detailsCircleLinkedGroups($circle);
290
				$this->detailsCircleFederatedCircles($circle);
291
			}
292
		} catch (Exception $e) {
293
			throw $e;
294
		}
295
296
		return $circle;
297
	}
298
299
300
	/**
301
	 * get the Members list and add the result to the Circle.
302
	 *
303
	 * @param Circle $circle
304
	 *
305
	 * @throws Exception
306
	 */
307
	private function detailsCircleMembers(Circle $circle) {
308
		if ($this->viewerIsAdmin()) {
309
			$members = $this->membersRequest->forceGetMembers($circle->getUniqueId(), 0);
310
		} else {
311
			$members = $this->membersRequest->getMembers(
312
				$circle->getUniqueId(), $circle->getHigherViewer()
313
			);
314
		}
315
316
		$circle->setMembers($members);
317
	}
318
319
320
	/**
321
	 * // TODO - check this on GS setup
322
	 * get the Linked Group list and add the result to the Circle.
323
	 *
324
	 * @param Circle $circle
325
	 *
326
	 * @throws GSStatusException
327
	 */
328
	private function detailsCircleLinkedGroups(Circle $circle) {
329
		$groups = [];
330
		if ($this->configService->isLinkedGroupsAllowed()) {
331
			$groups =
332
				$this->membersRequest->getGroupsFromCircle(
333
					$circle->getUniqueId(), $circle->getHigherViewer()
334
				);
335
		}
336
337
		$circle->setGroups($groups);
338
	}
339
340
341
	/**
342
	 * get the Federated Circles list and add the result to the Circle.
343
	 *
344
	 * @param Circle $circle
345
	 */
346
	private function detailsCircleFederatedCircles(Circle $circle) {
347
		$links = [];
348
349
		try {
350
			if ($this->configService->isFederatedCirclesAllowed()) {
351
				$circle->hasToBeFederated();
352
				$links = $this->federatedLinksRequest->getLinksFromCircle($circle->getUniqueId());
353
			}
354
		} catch (FederatedCircleNotAllowedException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
355
		}
356
357
		$circle->setLinks($links);
358
	}
359
360
361
	/**
362
	 * save new settings if current user is admin.
363
	 *
364
	 * @param string $circleUniqueId
365
	 * @param array $settings
366
	 *
367
	 * @return Circle
368
	 * @throws Exception
369
	 */
370
	public function settingsCircle(string $circleUniqueId, array $settings) {
371
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
372
		$this->hasToBeOwner($circle->getHigherViewer());
373
374
		if (!$this->viewerIsAdmin()) {
375
			$settings['members_limit'] = $circle->getSetting('members_limit');
376
		}
377
378
		if ($this->get('password_single', $settings) === ''
379
			&& $circle->getSetting('password_single_enabled') === 'false') {
380
			$settings['password_single_enabled'] = false;
381
		}
382
383
		// can only be run from the instance of the circle's owner.
384
		$event = new GSEvent(GSEvent::CIRCLE_UPDATE);
385
		$event->setCircle($circle);
386
		$event->getData()
387
			  ->sBool('local_admin', $this->viewerIsAdmin())
388
			  ->sArray('settings', $settings);
389
390
		if ($this->getBool('password_enforcement', $settings) === true
391
			&& $this->getBool('password_single_enabled', $settings) === true
392
			&& $this->get('password_single', $settings) !== ''
393
		) {
394
			$event->getData()
395
				  ->sBool('password_changed', true);
396
		}
397
398
		$this->gsUpstreamService->newEvent($event);
399
400
		$circle->setSettings($settings);
401
402
		return $circle;
403
	}
404
405
406
	/**
407
	 * @param Circle $circle
408
	 */
409
	public function updatePasswordOnShares(Circle $circle) {
410
		$this->tokensRequest->updateSinglePassword($circle->getUniqueId(), $circle->getPasswordSingle());
411
	}
412
413
414
	/**
415
	 * Join a circle.
416
	 *
417
	 * @param string $circleUniqueId
418
	 *
419
	 * @return null|Member
420
	 * @throws Exception
421
	 */
422
	public function joinCircle($circleUniqueId): Member {
423
		try {
424
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
425
			$member = $this->membersRequest->getFreshNewMember(
426
				$circleUniqueId, $this->userId, Member::TYPE_USER, ''
427
			);
428
429
			$this->membersService->updateCachedName($member);
430
431
			$event = new GSEvent(GSEvent::MEMBER_JOIN);
432
			$event->setCircle($circle);
433
			$event->setMember($member);
434
			$this->gsUpstreamService->newEvent($event);
435
		} catch (Exception $e) {
436
			throw $e;
437
		}
438
439
		return $member;
440
	}
441
442
443
	/**
444
	 * Leave a circle.
445
	 *
446
	 * @param string $circleUniqueId
447
	 *
448
	 * @return null|Member
449
	 * @throws Exception
450
	 */
451
	public function leaveCircle($circleUniqueId) {
452
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
453
		$member = $circle->getViewer();
454
455
		$event = new GSEvent(GSEvent::MEMBER_LEAVE);
456
		$event->setCircle($circle);
457
		$event->setMember($member);
458
		$this->gsUpstreamService->newEvent($event);
459
460
		return $member;
461
	}
462
463
464
	/**
465
	 * destroy a circle.
466
	 *
467
	 * @param string $circleUniqueId
468
	 *
469
	 * @param bool $force
470
	 *
471
	 * @throws CircleDoesNotExistException
472
	 * @throws MemberIsNotOwnerException
473
	 * @throws ConfigNoCircleAvailableException
474
	 * @throws Exception
475
	 */
476
	public function removeCircle($circleUniqueId, bool $force = false) {
477
		if ($force) {
478
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
479
		} else {
480
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
481
			$this->hasToBeOwner($circle->getHigherViewer());
482
		}
483
484
		// removing a Circle is done only by owner, so can already be done by local user, or admin, or occ
485
		// at this point, we already know that all condition are filled. we can force it.
486
		$event = new GSEvent(GSEvent::CIRCLE_DESTROY, false, true);
487
		$event->setCircle($circle);
488
489
		$this->gsUpstreamService->newEvent($event);
490
	}
491
492
493
494
495
	/**
496
	 * @return Circle[]
497
	 */
498
	public function getCirclesToSync(): array {
499
		$circles = $this->circlesRequest->forceGetCircles();
500
501
		$sync = [];
502
		foreach ($circles as $circle) {
503
			if ($circle->getOwner()
504
					   ->getInstance() !== ''
505
//				|| $circle->getType() === Circle::CIRCLES_PERSONAL
506
			) {
507
				continue;
508
			}
509
510
			$members = $this->membersRequest->forceGetMembers($circle->getUniqueId());
511
			$circle->setMembers($members);
512
513
			$sync[] = $circle;
514
		}
515
516
		return $sync;
517
	}
518
519
520
	/**
521
	 * @param $circleName
522
	 *
523
	 * @return Circle|null
524
	 * @throws CircleDoesNotExistException
525
	 */
526
	public function infoCircleByName($circleName) {
527
		return $this->circlesRequest->forceGetCircleByName($circleName);
528
	}
529
530
531
	/**
532
	 * Convert a Type in String to its Bit Value
533
	 *
534
	 * @param string $type
535
	 *
536
	 * @return int|mixed
537
	 */
538
	public function convertTypeStringToBitValue($type) {
539
		$strings = [
540
			'personal' => Circle::CIRCLES_PERSONAL,
541
			'secret'   => Circle::CIRCLES_SECRET,
542
			'closed'   => Circle::CIRCLES_CLOSED,
543
			'public'   => Circle::CIRCLES_PUBLIC,
544
			'all'      => Circle::CIRCLES_ALL
545
		];
546
547
		if (!key_exists(strtolower($type), $strings)) {
548
			return $type;
549
		}
550
551
		return $strings[strtolower($type)];
552
	}
553
554
555
	/**
556
	 * getCircleIcon()
557
	 *
558
	 * Return the right imagePath for a type of circle.
559
	 *
560
	 * @param string $type
561
	 * @param bool $png
562
	 *
563
	 * @return string
564
	 */
565
	public static function getCircleIcon($type, $png = false) {
566
567
		$ext = '.svg';
568
		if ($png === true) {
569
			$ext = '.png';
570
		}
571
572
		$urlGen = OC::$server->getURLGenerator();
573
		switch ($type) {
574
			case Circle::CIRCLES_PERSONAL:
575
				return $urlGen->getAbsoluteURL(
576
					$urlGen->imagePath(Application::APP_ID, 'personal' . $ext)
577
				);
578
			case Circle::CIRCLES_CLOSED:
579
				return $urlGen->getAbsoluteURL(
580
					$urlGen->imagePath(Application::APP_ID, 'closed' . $ext)
581
				);
582
			case Circle::CIRCLES_SECRET:
583
				return $urlGen->getAbsoluteURL(
584
					$urlGen->imagePath(Application::APP_ID, 'secret' . $ext)
585
				);
586
			case Circle::CIRCLES_PUBLIC:
587
				return $urlGen->getAbsoluteURL(
588
					$urlGen->imagePath(Application::APP_ID, 'black_circle' . $ext)
589
				);
590
		}
591
592
		return $urlGen->getAbsoluteURL(
593
			$urlGen->imagePath(Application::APP_ID, 'black_circle' . $ext)
594
		);
595
	}
596
597
598
	/**
599
	 * @param string $circleUniqueIds
600
	 * @param int $limit
601
	 * @param int $offset
602
	 *
603
	 * @return array
604
	 * @throws GSStatusException
605
	 */
606
	public function getFilesForCircles($circleUniqueIds, $limit = -1, $offset = 0) {
607
		if (!is_array($circleUniqueIds)) {
608
			$circleUniqueIds = [$circleUniqueIds];
609
		}
610
611
		return $this->circleProviderRequest->getFilesForCircles(
612
			$this->userId, $circleUniqueIds, $limit, $offset
613
		);
614
	}
615
616
617
	/**
618
	 * @param Circle $circle
619
	 *
620
	 * @throws MembersLimitException
621
	 */
622
	public function checkThatCircleIsNotFull(Circle $circle) {
623
		$members =
624
			$this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_MEMBER, 0, true);
625
626
		$limit = (int)$circle->getSetting('members_limit');
627
		if ($limit === -1) {
628
			return;
629
		}
630
		if ($limit === 0) {
631
			$limit = $this->configService->getAppValue(ConfigService::CIRCLES_MEMBERS_LIMIT);
632
		}
633
634
		if (sizeof($members) >= $limit) {
635
			throw new MembersLimitException(
636
				'This circle already reach its limit on the number of members'
637
			);
638
		}
639
640
	}
641
642
	/**
643
	 * @return bool
644
	 */
645
	public function viewerIsAdmin(): bool {
646
		if ($this->userId === '') {
647
			return false;
648
		}
649
650
		return ($this->groupManager->isAdmin($this->userId));
651
	}
652
653
654
	/**
655
	 * should be moved.
656
	 *
657
	 * @param Member $member
658
	 *
659
	 * @throws MemberIsNotOwnerException
660
	 */
661 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...
662
		if (!$this->groupManager->isAdmin($this->userId)
663
			&& $member->getLevel() < Member::LEVEL_OWNER) {
664
			throw new MemberIsNotOwnerException(
665
				$this->l10n->t('This member is not the owner of the circle')
666
			);
667
		}
668
	}
669
670
671
	/**
672
	 * should be moved.
673
	 *
674
	 * @param Member $member
675
	 *
676
	 * @throws MemberIsNotOwnerException
677
	 */
678 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...
679
		if (!$this->groupManager->isAdmin($member->getUserId())
680
			&& $member->getLevel() < Member::LEVEL_ADMIN) {
681
			throw new MemberIsNotOwnerException(
682
				$this->l10n->t('This member is not an admin of the circle')
683
			);
684
		}
685
	}
686
687
}
688