Completed
Pull Request — master (#362)
by Maxence
01:55 queued 11s
created

CirclesService   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 546
Duplicated Lines 8.97 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 15
dl 49
loc 546
rs 7.44
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A convertTypeStringToBitValue() 0 15 2
B getCircleIcon() 0 31 6
A getFilesForCircles() 0 11 2
A infoCircleByName() 0 3 1
A __construct() 27 27 1
B createCircle() 0 43 5
A listCircles() 0 21 4
A detailsCircle() 0 19 5
A detailsCircleMembers() 0 11 2
A detailsCircleLinkedGroups() 0 11 2
A detailsCircleFederatedCircles() 0 13 3
A settingsCircle() 0 19 2
A joinCircle() 0 17 2
A leaveCircle() 0 11 1
A removeCircle() 6 15 2
A checkThatCircleIsNotFull() 0 19 4
A viewerIsAdmin() 0 7 2
A hasToBeOwner() 8 8 3
A hasToBeAdmin() 8 8 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CirclesService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CirclesService, and based on these observations, apply Extract Interface, too.

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
	 * Convert a Type in String to its Bit Value
447
	 *
448
	 * @param string $type
449
	 *
450
	 * @return int|mixed
451
	 */
452
	public function convertTypeStringToBitValue($type) {
453
		$strings = [
454
			'personal' => Circle::CIRCLES_PERSONAL,
455
			'secret'   => Circle::CIRCLES_SECRET,
456
			'closed'   => Circle::CIRCLES_CLOSED,
457
			'public'   => Circle::CIRCLES_PUBLIC,
458
			'all'      => Circle::CIRCLES_ALL
459
		];
460
461
		if (!key_exists(strtolower($type), $strings)) {
462
			return $type;
463
		}
464
465
		return $strings[strtolower($type)];
466
	}
467
468
469
	/**
470
	 * getCircleIcon()
471
	 *
472
	 * Return the right imagePath for a type of circle.
473
	 *
474
	 * @param string $type
475
	 * @param bool $png
476
	 *
477
	 * @return string
478
	 */
479
	public static function getCircleIcon($type, $png = false) {
480
481
		$ext = '.svg';
482
		if ($png === true) {
483
			$ext = '.png';
484
		}
485
486
		$urlGen = OC::$server->getURLGenerator();
487
		switch ($type) {
488
			case Circle::CIRCLES_PERSONAL:
489
				return $urlGen->getAbsoluteURL(
490
					$urlGen->imagePath(Application::APP_NAME, 'personal' . $ext)
491
				);
492
			case Circle::CIRCLES_CLOSED:
493
				return $urlGen->getAbsoluteURL(
494
					$urlGen->imagePath(Application::APP_NAME, 'closed' . $ext)
495
				);
496
			case Circle::CIRCLES_SECRET:
497
				return $urlGen->getAbsoluteURL(
498
					$urlGen->imagePath(Application::APP_NAME, 'secret' . $ext)
499
				);
500
			case Circle::CIRCLES_PUBLIC:
501
				return $urlGen->getAbsoluteURL(
502
					$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
503
				);
504
		}
505
506
		return $urlGen->getAbsoluteURL(
507
			$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
508
		);
509
	}
510
511
512
	/**
513
	 * @param string $circleUniqueIds
514
	 * @param int $limit
515
	 * @param int $offset
516
	 *
517
	 * @return array
518
	 */
519
	public function getFilesForCircles($circleUniqueIds, $limit = -1, $offset = 0) {
520
		if (!is_array($circleUniqueIds)) {
521
			$circleUniqueIds = [$circleUniqueIds];
522
		}
523
524
		$objectIds = $this->circleProviderRequest->getFilesForCircles(
525
			$this->userId, $circleUniqueIds, $limit, $offset
526
		);
527
528
		return $objectIds;
529
	}
530
531
532
	/**
533
	 * @param Circle $circle
534
	 *
535
	 * @throws MembersLimitException
536
	 */
537
	public function checkThatCircleIsNotFull(Circle $circle) {
538
		$members =
539
			$this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_MEMBER, 0, true);
540
541
		$limit = (int)$circle->getSetting('members_limit');
542
		if ($limit === -1) {
543
			return;
544
		}
545
		if ($limit === 0) {
546
			$limit = $this->configService->getAppValue(ConfigService::CIRCLES_MEMBERS_LIMIT);
547
		}
548
549
		if (sizeof($members) >= $limit) {
550
			throw new MembersLimitException(
551
				'This circle already reach its limit on the number of members'
552
			);
553
		}
554
555
	}
556
557
	/**
558
	 * @return bool
559
	 */
560
	public function viewerIsAdmin(): bool {
561
		if ($this->userId === '') {
562
			return false;
563
		}
564
565
		return ($this->groupManager->isAdmin($this->userId));
566
	}
567
568
569
	/**
570
	 * should be moved.
571
	 *
572
	 * @param Member $member
573
	 *
574
	 * @throws MemberIsNotOwnerException
575
	 */
576 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...
577
		if (!$this->groupManager->isAdmin($this->userId)
578
			&& $member->getLevel() < Member::LEVEL_OWNER) {
579
			throw new MemberIsNotOwnerException(
580
				$this->l10n->t('This member is not the owner of the circle')
581
			);
582
		}
583
	}
584
585
586
	/**
587
	 * should be moved.
588
	 *
589
	 * @param Member $member
590
	 *
591
	 * @throws MemberIsNotOwnerException
592
	 */
593 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...
594
		if (!$this->groupManager->isAdmin($member->getUserId())
595
			&& $member->getLevel() < Member::LEVEL_ADMIN) {
596
			throw new MemberIsNotOwnerException(
597
				$this->l10n->t('This member is not an admin of the circle')
598
			);
599
		}
600
	}
601
}
602