Completed
Push — master ( f63403...0a370b )
by Maxence
03:01 queued 01:09
created

CirclesService::viewerIsAdmin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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