Completed
Pull Request — master (#341)
by Julius
01:46
created

CirclesService   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 575
Duplicated Lines 2.78 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 13
dl 16
loc 575
rs 3.6
c 0
b 0
f 0

21 Methods

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