Completed
Pull Request — master (#362)
by Maxence
01:50
created

CirclesService::createCircle()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 8.9208
c 0
b 0
f 0
cc 5
nc 6
nop 3
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
	 * get the Linked Group list and add the result to the Circle.
288
	 *
289
	 * @param Circle $circle
290
	 *
291
	 * @throws MemberDoesNotExistException
292
	 */
293
	private function detailsCircleLinkedGroups(Circle &$circle) {
294
		$groups = [];
295
		if ($this->configService->isLinkedGroupsAllowed()) {
296
			$groups =
297
				$this->membersRequest->getGroupsFromCircle(
298
					$circle->getUniqueId(), $circle->getHigherViewer()
299
				);
300
		}
301
302
		$circle->setGroups($groups);
303
	}
304
305
306
	/**
307
	 * get the Federated Circles list and add the result to the Circle.
308
	 *
309
	 * @param Circle $circle
310
	 */
311
	private function detailsCircleFederatedCircles(Circle &$circle) {
312
		$links = [];
313
314
		try {
315
			if ($this->configService->isFederatedCirclesAllowed()) {
316
				$circle->hasToBeFederated();
317
				$links = $this->federatedLinksRequest->getLinksFromCircle($circle->getUniqueId());
318
			}
319
		} catch (FederatedCircleNotAllowedException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
320
		}
321
322
		$circle->setLinks($links);
323
	}
324
325
326
	/**
327
	 * save new settings if current user is admin.
328
	 *
329
	 * @param string $circleUniqueId
330
	 * @param array $settings
331
	 *
332
	 * @return Circle
333
	 * @throws Exception
334
	 */
335
	public function settingsCircle(string $circleUniqueId, array $settings) {
336
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
337
		$this->hasToBeOwner($circle->getHigherViewer());
338
339
		if (!$this->viewerIsAdmin()) {
340
			$settings['members_limit'] = $circle->getSetting('members_limit');
341
		}
342
343
		// can only be run from the instance of the circle's owner.
344
		$event = new GSEvent(GSEvent::CIRCLE_UPDATE);
345
		$event->setCircle($circle);
346
		$event->getData()
347
			  ->sBool('local_admin', $this->viewerIsAdmin())
348
			  ->sArray('settings', $settings);
349
350
		$this->gsUpstreamService->newEvent($event);
351
352
		return $circle;
353
	}
354
355
356
	/**
357
	 * Join a circle.
358
	 *
359
	 * @param string $circleUniqueId
360
	 *
361
	 * @return null|Member
362
	 * @throws Exception
363
	 */
364
	public function joinCircle($circleUniqueId) {
365
		try {
366
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
367
			$member = $this->membersRequest->getFreshNewMember(
368
				$circleUniqueId, $this->userId, Member::TYPE_USER, ''
369
			);
370
371
			$event = new GSEvent(GSEvent::MEMBER_JOIN);
372
			$event->setCircle($circle);
373
			$event->setMember($member);
374
			$this->gsUpstreamService->newEvent($event);
375
		} catch (Exception $e) {
376
			throw $e;
377
		}
378
379
		return $member;
380
	}
381
382
383
	/**
384
	 * Leave a circle.
385
	 *
386
	 * @param string $circleUniqueId
387
	 *
388
	 * @return null|Member
389
	 * @throws Exception
390
	 */
391
	public function leaveCircle($circleUniqueId) {
392
		$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
393
		$member = $circle->getViewer();
394
395
		$event = new GSEvent(GSEvent::MEMBER_LEAVE);
396
		$event->setCircle($circle);
397
		$event->setMember($member);
398
		$this->gsUpstreamService->newEvent($event);
399
400
		return $member;
401
	}
402
403
404
	/**
405
	 * destroy a circle.
406
	 *
407
	 * @param string $circleUniqueId
408
	 *
409
	 * @param bool $force
410
	 *
411
	 * @throws CircleDoesNotExistException
412
	 * @throws MemberIsNotOwnerException
413
	 * @throws ConfigNoCircleAvailableException
414
	 * @throws Exception
415
	 */
416
	public function removeCircle($circleUniqueId, bool $force = false) {
417 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...
418
			$circle = $this->circlesRequest->forceGetCircle($circleUniqueId);
419
		} else {
420
			$circle = $this->circlesRequest->getCircle($circleUniqueId, $this->userId);
421
			$this->hasToBeOwner($circle->getHigherViewer());
422
		}
423
424
		// removing a Circle is done only by owner, so can already be done by local user, or admin, or occ
425
		// at this point, we already know that all condition are filled. we can force it.
426
		$event = new GSEvent(GSEvent::CIRCLE_DESTROY, false, true);
427
		$event->setCircle($circle);
428
429
		$this->gsUpstreamService->newEvent($event);
430
	}
431
432
433
	/**
434
	 * @param $circleName
435
	 *
436
	 * @return Circle|null
437
	 * @throws CircleDoesNotExistException
438
	 */
439
	public function infoCircleByName($circleName) {
440
		return $this->circlesRequest->forceGetCircleByName($circleName);
441
	}
442
443
444
	/**
445
	 * When a user is removed.
446
	 * Before deleting a user from the cloud, we assign a new owner to his Circles.
447
	 * Remove the Circle if it has no admin.
448
	 *
449
	 * @param string $userId
450
	 */
451
	public function onUserRemoved($userId) {
452
		$circles = $this->circlesRequest->getCircles($userId, 0, '', Member::LEVEL_OWNER);
453
454
		foreach ($circles as $circle) {
455
456
			$members =
457
				$this->membersRequest->forceGetMembers($circle->getUniqueId(), Member::LEVEL_ADMIN);
458
459
			if (sizeof($members) === 1) {
460
				$this->circlesRequest->destroyCircle($circle->getUniqueId());
461
				continue;
462
			}
463
464
			$this->switchOlderAdminToOwner($circle, $members);
465
		}
466
	}
467
468
469
	/**
470
	 * switchOlderAdminToOwner();
471
	 *
472
	 * @param Circle $circle
473
	 * @param Member[] $members
474
	 */
475
	private function switchOlderAdminToOwner(Circle $circle, $members) {
476
477
		foreach ($members as $member) {
478
			if ($member->getLevel() === Member::LEVEL_ADMIN) {
479
				$member->setLevel(Member::LEVEL_OWNER);
480
				$this->membersRequest->updateMember($member);
481
				$this->eventsService->onMemberOwner($circle, $member);
482
483
				return;
484
			}
485
		}
486
487
	}
488
489
490
	/**
491
	 * Convert a Type in String to its Bit Value
492
	 *
493
	 * @param string $type
494
	 *
495
	 * @return int|mixed
496
	 */
497
	public function convertTypeStringToBitValue($type) {
498
		$strings = [
499
			'personal' => Circle::CIRCLES_PERSONAL,
500
			'secret'   => Circle::CIRCLES_SECRET,
501
			'closed'   => Circle::CIRCLES_CLOSED,
502
			'public'   => Circle::CIRCLES_PUBLIC,
503
			'all'      => Circle::CIRCLES_ALL
504
		];
505
506
		if (!key_exists(strtolower($type), $strings)) {
507
			return $type;
508
		}
509
510
		return $strings[strtolower($type)];
511
	}
512
513
514
	/**
515
	 * getCircleIcon()
516
	 *
517
	 * Return the right imagePath for a type of circle.
518
	 *
519
	 * @param string $type
520
	 * @param bool $png
521
	 *
522
	 * @return string
523
	 */
524
	public static function getCircleIcon($type, $png = false) {
525
526
		$ext = '.svg';
527
		if ($png === true) {
528
			$ext = '.png';
529
		}
530
531
		$urlGen = OC::$server->getURLGenerator();
532
		switch ($type) {
533
			case Circle::CIRCLES_PERSONAL:
534
				return $urlGen->getAbsoluteURL(
535
					$urlGen->imagePath(Application::APP_NAME, 'personal' . $ext)
536
				);
537
			case Circle::CIRCLES_CLOSED:
538
				return $urlGen->getAbsoluteURL(
539
					$urlGen->imagePath(Application::APP_NAME, 'closed' . $ext)
540
				);
541
			case Circle::CIRCLES_SECRET:
542
				return $urlGen->getAbsoluteURL(
543
					$urlGen->imagePath(Application::APP_NAME, 'secret' . $ext)
544
				);
545
			case Circle::CIRCLES_PUBLIC:
546
				return $urlGen->getAbsoluteURL(
547
					$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
548
				);
549
		}
550
551
		return $urlGen->getAbsoluteURL(
552
			$urlGen->imagePath(Application::APP_NAME, 'black_circle' . $ext)
553
		);
554
	}
555
556
557
	/**
558
	 * @param string $circleUniqueIds
559
	 * @param int $limit
560
	 * @param int $offset
561
	 *
562
	 * @return array
563
	 */
564
	public function getFilesForCircles($circleUniqueIds, $limit = -1, $offset = 0) {
565
		if (!is_array($circleUniqueIds)) {
566
			$circleUniqueIds = [$circleUniqueIds];
567
		}
568
569
		$objectIds = $this->circleProviderRequest->getFilesForCircles(
570
			$this->userId, $circleUniqueIds, $limit, $offset
571
		);
572
573
		return $objectIds;
574
	}
575
576
577
	/**
578
	 * @param Circle $circle
579
	 *
580
	 * @throws MembersLimitException
581
	 */
582
	public function checkThatCircleIsNotFull(Circle $circle) {
583
		$members = $this->membersRequest->forceGetMembers(
584
			$circle->getUniqueId(), Member::LEVEL_MEMBER, true
585
		);
586
587
		$limit = $circle->getSetting('members_limit');
588
		if ($limit === -1) {
589
			return;
590
		}
591
		if ($limit === 0 || $limit === '' || $limit === null) {
592
			$limit = $this->configService->getAppValue(ConfigService::CIRCLES_MEMBERS_LIMIT);
593
		}
594
595
		if (sizeof($members) >= $limit) {
596
			throw new MembersLimitException(
597
				'This circle already reach its limit on the number of members'
598
			);
599
		}
600
601
	}
602
603
	/**
604
	 * @return bool
605
	 */
606
	public function viewerIsAdmin(): bool {
607
		if ($this->userId === '') {
608
			return false;
609
		}
610
611
		return ($this->groupManager->isAdmin($this->userId));
612
	}
613
614
615
	/**
616
	 * should be moved.
617
	 *
618
	 * @param Member $member
619
	 *
620
	 * @throws MemberIsNotOwnerException
621
	 */
622 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...
623
		if (!$this->groupManager->isAdmin($this->userId)
624
			&& $member->getLevel() < Member::LEVEL_OWNER) {
625
			throw new MemberIsNotOwnerException(
626
				$this->l10n->t('This member is not the owner of the circle')
627
			);
628
		}
629
	}
630
631
632
	/**
633
	 * should be moved.
634
	 *
635
	 * @param Member $member
636
	 *
637
	 * @throws MemberIsNotOwnerException
638
	 */
639 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...
640
		if (!$this->groupManager->isAdmin($member->getUserId())
641
			&& $member->getLevel() < Member::LEVEL_ADMIN) {
642
			throw new MemberIsNotOwnerException(
643
				$this->l10n->t('This member is not an admin of the circle')
644
			);
645
		}
646
	}
647
}
648