Completed
Pull Request — master (#479)
by Maxence
02:01
created

CirclesService::settingsCircle()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

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