Completed
Pull Request — master (#362)
by Maxence
02:19
created

CirclesService::settingsCircle()   A

Complexity

Conditions 2
Paths 2

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