Completed
Pull Request — master (#617)
by Maxence
03:02
created

CircleService::updateSettings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Service;
33
34
35
use daita\MySmallPhpTools\Model\SimpleDataStore;
36
use daita\MySmallPhpTools\Traits\TArrayTools;
37
use daita\MySmallPhpTools\Traits\TStringTools;
38
use OCA\Circles\Db\CircleRequest;
39
use OCA\Circles\Db\MemberRequest;
40
use OCA\Circles\Exceptions\CircleNotFoundException;
41
use OCA\Circles\Exceptions\FederatedEventException;
42
use OCA\Circles\Exceptions\FederatedItemException;
43
use OCA\Circles\Exceptions\InitiatorNotConfirmedException;
44
use OCA\Circles\Exceptions\InitiatorNotFoundException;
45
use OCA\Circles\Exceptions\MembersLimitException;
46
use OCA\Circles\Exceptions\OwnerNotFoundException;
47
use OCA\Circles\Exceptions\RemoteInstanceException;
48
use OCA\Circles\Exceptions\RemoteNotFoundException;
49
use OCA\Circles\Exceptions\RemoteResourceNotFoundException;
50
use OCA\Circles\Exceptions\RequestBuilderException;
51
use OCA\Circles\Exceptions\UnknownRemoteException;
52
use OCA\Circles\FederatedItems\CircleConfig;
53
use OCA\Circles\FederatedItems\CircleCreate;
54
use OCA\Circles\FederatedItems\CircleDestroy;
55
use OCA\Circles\FederatedItems\CircleEdit;
56
use OCA\Circles\FederatedItems\CircleJoin;
57
use OCA\Circles\FederatedItems\CircleLeave;
58
use OCA\Circles\FederatedItems\CircleSettings;
59
use OCA\Circles\Model\Circle;
60
use OCA\Circles\Model\Federated\FederatedEvent;
61
use OCA\Circles\Model\FederatedUser;
62
use OCA\Circles\Model\ManagedModel;
63
use OCA\Circles\Model\Member;
64
use OCA\Circles\StatusCode;
65
66
67
/**
68
 * Class CircleService
69
 *
70
 * @package OCA\Circles\Service
71
 */
72
class CircleService {
73
74
75
	use TArrayTools;
76
	use TStringTools;
77
78
79
	/** @var CircleRequest */
80
	private $circleRequest;
81
82
	/** @var MemberRequest */
83
	private $memberRequest;
84
85
	/** @var RemoteStreamService */
86
	private $remoteStreamService;
87
88
	/** @var FederatedUserService */
89
	private $federatedUserService;
90
91
	/** @var FederatedEventService */
92
	private $federatedEventService;
93
94
	/** @var MemberService */
95
	private $memberService;
96
97
	/** @var ConfigService */
98
	private $configService;
99
100
101
	/**
102
	 * CircleService constructor.
103
	 *
104
	 * @param CircleRequest $circleRequest
105
	 * @param MemberRequest $memberRequest
106
	 * @param RemoteStreamService $remoteStreamService
107
	 * @param FederatedUserService $federatedUserService
108
	 * @param FederatedEventService $federatedEventService
109
	 * @param MemberService $memberService
110
	 * @param ConfigService $configService
111
	 */
112
	public function __construct(
113
		CircleRequest $circleRequest, MemberRequest $memberRequest, RemoteStreamService $remoteStreamService,
114
		FederatedUserService $federatedUserService, FederatedEventService $federatedEventService,
115
		MemberService $memberService, ConfigService $configService
116
	) {
117
		$this->circleRequest = $circleRequest;
118
		$this->memberRequest = $memberRequest;
119
		$this->remoteStreamService = $remoteStreamService;
120
		$this->federatedUserService = $federatedUserService;
121
		$this->federatedEventService = $federatedEventService;
122
		$this->memberService = $memberService;
123
		$this->configService = $configService;
124
	}
125
126
127
	/**
128
	 * @param string $name
129
	 * @param FederatedUser|null $owner
130
	 * @param bool $personal
131
	 * @param bool $local
132
	 *
133
	 * @return array
134
	 * @throws FederatedEventException
135
	 * @throws FederatedItemException
136
	 * @throws InitiatorNotConfirmedException
137
	 * @throws InitiatorNotFoundException
138
	 * @throws OwnerNotFoundException
139
	 * @throws RemoteInstanceException
140
	 * @throws RemoteNotFoundException
141
	 * @throws RemoteResourceNotFoundException
142
	 * @throws UnknownRemoteException
143
	 * @throws RequestBuilderException
144
	 */
145
	public function create(
146
		string $name,
147
		?FederatedUser $owner = null,
148
		bool $personal = false,
149
		bool $local = false
150
	): array {
151
152
		$this->federatedUserService->mustHaveCurrentUser();
153
		if (is_null($owner)) {
154
			$owner = $this->federatedUserService->getCurrentUser();
155
		}
156
157
		if (is_null($owner)) {
158
			throw new OwnerNotFoundException('owner not defined');
159
		}
160
161
		$circle = new Circle();
162
		$circle->setName(trim($name))
163
			   ->setSingleId($this->token(ManagedModel::ID_LENGTH))
164
			   ->setSource(Member::TYPE_CIRCLE);
165
166
		if ($personal) {
167
			$circle->setConfig(Circle::CFG_PERSONAL);
168
		}
169
170
		if ($local) {
171
			$circle->addConfig(Circle::CFG_LOCAL);
172
		}
173
174
		$this->confirmName($circle);
175
176
		$member = new Member();
177
		$member->importFromIFederatedUser($owner);
178
		$member->setId($this->token(ManagedModel::ID_LENGTH))
179
			   ->setCircleId($circle->getSingleId())
180
			   ->setLevel(Member::LEVEL_OWNER)
181
			   ->setStatus(Member::STATUS_MEMBER);
182
183
		$this->federatedUserService->setMemberPatron($member);
184
185
		$circle->setOwner($member)
186
			   ->setInitiator($member);
187
188
		$event = new FederatedEvent(CircleCreate::class);
189
		$event->setCircle($circle);
190
		$this->federatedEventService->newEvent($event);
191
192
		return $event->getOutcome();
193
	}
194
195
196
	/**
197
	 * @param string $circleId
198
	 *
199
	 * @return array
200
	 * @throws CircleNotFoundException
201
	 * @throws FederatedEventException
202
	 * @throws FederatedItemException
203
	 * @throws InitiatorNotConfirmedException
204
	 * @throws InitiatorNotFoundException
205
	 * @throws OwnerNotFoundException
206
	 * @throws RemoteInstanceException
207
	 * @throws RemoteNotFoundException
208
	 * @throws RemoteResourceNotFoundException
209
	 * @throws RequestBuilderException
210
	 * @throws UnknownRemoteException
211
	 */
212
	public function destroy(string $circleId): array {
213
		$this->federatedUserService->mustHaveCurrentUser();
214
215
		$circle = $this->getCircle($circleId);
216
217
		$event = new FederatedEvent(CircleDestroy::class);
218
		$event->setCircle($circle);
219
		$this->federatedEventService->newEvent($event);
220
221
		return $event->getOutcome();
222
	}
223
224
225
	/**
226
	 * @param string $circleId
227
	 * @param int $config
228
	 *
229
	 * @return array
230
	 * @throws CircleNotFoundException
231
	 * @throws FederatedEventException
232
	 * @throws FederatedItemException
233
	 * @throws InitiatorNotConfirmedException
234
	 * @throws InitiatorNotFoundException
235
	 * @throws OwnerNotFoundException
236
	 * @throws RemoteInstanceException
237
	 * @throws RemoteNotFoundException
238
	 * @throws RemoteResourceNotFoundException
239
	 * @throws UnknownRemoteException
240
	 * @throws RequestBuilderException
241
	 */
242
	public function updateConfig(string $circleId, int $config): array {
243
		$circle = $this->getCircle($circleId);
244
245
		$event = new FederatedEvent(CircleConfig::class);
246
		$event->setCircle($circle);
247
		$event->setParams(new SimpleDataStore(['config' => $config]));
248
249
		$this->federatedEventService->newEvent($event);
250
251
		return $event->getOutcome();
252
	}
253
254
255
	/**
256
	 * @param string $circleId
257
	 * @param string $name
258
	 *
259
	 * @return array
260
	 * @throws CircleNotFoundException
261
	 * @throws FederatedEventException
262
	 * @throws FederatedItemException
263
	 * @throws InitiatorNotConfirmedException
264
	 * @throws InitiatorNotFoundException
265
	 * @throws OwnerNotFoundException
266
	 * @throws RemoteInstanceException
267
	 * @throws RemoteNotFoundException
268
	 * @throws RemoteResourceNotFoundException
269
	 * @throws RequestBuilderException
270
	 * @throws UnknownRemoteException
271
	 */
272
	public function updateName(string $circleId, string $name): array {
273
		$circle = $this->getCircle($circleId);
274
275
		$event = new FederatedEvent(CircleEdit::class);
276
		$event->setCircle($circle);
277
		$event->setParams(new SimpleDataStore(['name' => $name]));
278
279
		$this->federatedEventService->newEvent($event);
280
281
		return $event->getOutcome();
282
	}
283
284
	/**
285
	 * @param string $circleId
286
	 * @param string $description
287
	 *
288
	 * @return array
289
	 * @throws CircleNotFoundException
290
	 * @throws FederatedEventException
291
	 * @throws FederatedItemException
292
	 * @throws InitiatorNotConfirmedException
293
	 * @throws InitiatorNotFoundException
294
	 * @throws OwnerNotFoundException
295
	 * @throws RemoteInstanceException
296
	 * @throws RemoteNotFoundException
297
	 * @throws RemoteResourceNotFoundException
298
	 * @throws RequestBuilderException
299
	 * @throws UnknownRemoteException
300
	 */
301
	public function updateDescription(string $circleId, string $description): array {
302
		$circle = $this->getCircle($circleId);
303
304
		$event = new FederatedEvent(CircleEdit::class);
305
		$event->setCircle($circle);
306
		$event->setParams(new SimpleDataStore(['description' => $description]));
307
308
		$this->federatedEventService->newEvent($event);
309
310
		return $event->getOutcome();
311
	}
312
313
	/**
314
	 * @param string $circleId
315
	 * @param array $settings
316
	 *
317
	 * @return array
318
	 * @throws CircleNotFoundException
319
	 * @throws FederatedEventException
320
	 * @throws FederatedItemException
321
	 * @throws InitiatorNotConfirmedException
322
	 * @throws InitiatorNotFoundException
323
	 * @throws OwnerNotFoundException
324
	 * @throws RemoteInstanceException
325
	 * @throws RemoteNotFoundException
326
	 * @throws RemoteResourceNotFoundException
327
	 * @throws RequestBuilderException
328
	 * @throws UnknownRemoteException
329
	 */
330
	public function updateSettings(string $circleId, array $settings): array {
331
		$circle = $this->getCircle($circleId);
332
333
		$event = new FederatedEvent(CircleSettings::class);
334
		$event->setCircle($circle);
335
		$event->setParams(new SimpleDataStore(['settings' => $settings]));
336
337
		$this->federatedEventService->newEvent($event);
338
339
		return $event->getOutcome();
340
	}
341
342
343
	/**
344
	 * @param string $circleId
345
	 *
346
	 * @return array
347
	 * @throws CircleNotFoundException
348
	 * @throws FederatedEventException
349
	 * @throws FederatedItemException
350
	 * @throws InitiatorNotConfirmedException
351
	 * @throws InitiatorNotFoundException
352
	 * @throws OwnerNotFoundException
353
	 * @throws RemoteInstanceException
354
	 * @throws RemoteNotFoundException
355
	 * @throws RemoteResourceNotFoundException
356
	 * @throws UnknownRemoteException
357
	 * @throws RequestBuilderException
358
	 */
359
	public function circleJoin(string $circleId): array {
360
		$this->federatedUserService->mustHaveCurrentUser();
361
362
		$circle = $this->circleRequest->getCircle($circleId, $this->federatedUserService->getCurrentUser());
363
		if (!$circle->getInitiator()->hasInvitedBy()) {
364
			$this->federatedUserService->setMemberPatron($circle->getInitiator());
365
		}
366
367
		echo json_encode($circle, JSON_PRETTY_PRINT);
368
		$event = new FederatedEvent(CircleJoin::class);
369
		$event->setCircle($circle);
370
371
		$this->federatedEventService->newEvent($event);
372
373
		return $event->getOutcome();
374
	}
375
376
377
	/**
378
	 * @param string $circleId
379
	 * @param bool $force
380
	 *
381
	 * @return array
382
	 * @throws CircleNotFoundException
383
	 * @throws FederatedEventException
384
	 * @throws FederatedItemException
385
	 * @throws InitiatorNotConfirmedException
386
	 * @throws InitiatorNotFoundException
387
	 * @throws OwnerNotFoundException
388
	 * @throws RemoteInstanceException
389
	 * @throws RemoteNotFoundException
390
	 * @throws RemoteResourceNotFoundException
391
	 * @throws RequestBuilderException
392
	 * @throws UnknownRemoteException
393
	 */
394
	public function circleLeave(string $circleId, bool $force = false): array {
395
		$this->federatedUserService->mustHaveCurrentUser();
396
397
		$circle = $this->circleRequest->getCircle($circleId, $this->federatedUserService->getCurrentUser());
398
399
		$event = new FederatedEvent(CircleLeave::class);
400
		$event->setCircle($circle);
401
		$event->getParams()->sBool('force', $force);
402
403
		$this->federatedEventService->newEvent($event);
404
405
		return $event->getOutcome();
406
	}
407
408
409
	/**
410
	 * @param string $circleId
411
	 * @param int $filter
412
	 *
413
	 * @return Circle
414
	 * @throws CircleNotFoundException
415
	 * @throws InitiatorNotFoundException
416
	 * @throws RequestBuilderException
417
	 */
418
	public function getCircle(
419
		string $circleId,
420
		int $filter = Circle::CFG_BACKEND | Circle::CFG_SINGLE | Circle::CFG_HIDDEN
421
	): Circle {
422
		$this->federatedUserService->mustHaveCurrentUser();
423
424
		return $this->circleRequest->getCircle(
425
			$circleId,
426
			$this->federatedUserService->getCurrentUser(),
427
			$this->federatedUserService->getRemoteInstance(),
428
			$filter
429
		);
430
	}
431
432
433
	/**
434
	 * @param Circle|null $circleFilter
435
	 * @param Member|null $memberFilter
436
	 * @param SimpleDataStore|null $params
437
	 *
438
	 * @return Circle[]
439
	 * @throws InitiatorNotFoundException
440
	 * @throws RequestBuilderException
441
	 */
442
	public function getCircles(
443
		?Circle $circleFilter = null,
444
		?Member $memberFilter = null,
445
		?SimpleDataStore $params = null
446
	): array {
447
		$this->federatedUserService->mustHaveCurrentUser();
448
449
		if ($params === null) {
450
			$params = new SimpleDataStore();
451
		}
452
		$params->default(
453
			[
454
				'limit'                  => -1,
455
				'offset'                 => 0,
456
				'mustBeMember'           => false,
457
				'includeHiddenCircles'   => false,
458
				'includeBackendCircles'  => false,
459
				'includeSystemCircles'   => false,
460
				'includePersonalCircles' => false
461
			]
462
		);
463
464
		return $this->circleRequest->getCircles(
465
			$circleFilter,
466
			$memberFilter,
467
			$this->federatedUserService->getCurrentUser(),
468
			$this->federatedUserService->getRemoteInstance(),
469
			$params
470
		);
471
	}
472
473
474
	/**
475
	 * @param Circle $circle
476
	 *
477
	 * @throws RequestBuilderException
478
	 */
479
	public function confirmName(Circle $circle): void {
480
		if ($circle->isConfig(Circle::CFG_SYSTEM)
481
			|| $circle->isConfig(Circle::CFG_SINGLE)) {
482
			return;
483
		}
484
485
		$this->confirmDisplayName($circle);
486
		$this->confirmSanitizedName($circle);
487
	}
488
489
	/**
490
	 * @param Circle $circle
491
	 *
492
	 * @throws RequestBuilderException
493
	 */
494
	private function confirmDisplayName(Circle $circle) {
495
		$baseDisplayName = $circle->getName();
496
497
		$i = 1;
498
		while (true) {
499
			$testDisplayName = $baseDisplayName . (($i > 1) ? ' (' . $i . ')' : '');
500
			$test = new Circle();
501
			$test->setDisplayName($testDisplayName);
502
503
			try {
504
				$stored = $this->circleRequest->searchCircle($test);
505
				if ($stored->getSingleId() === $circle->getSingleId()) {
506
					throw new CircleNotFoundException();
507
				}
508
			} catch (CircleNotFoundException $e) {
509
				$circle->setDisplayName($testDisplayName);
510
511
				return;
512
			}
513
514
			$i++;
515
		}
516
	}
517
518
519
	/**
520
	 * @param Circle $circle
521
	 *
522
	 * @throws RequestBuilderException
523
	 */
524
	private function confirmSanitizedName(Circle $circle) {
525
		$baseSanitizedName = $this->sanitizeName($circle->getName());
526
		if ($baseSanitizedName === '') {
527
			$baseSanitizedName = substr($circle->getSingleId(), 0, 3);
528
		}
529
530
		$i = 1;
531
		while (true) {
532
			$testSanitizedName = $baseSanitizedName . (($i > 1) ? '-' . $i : '');
533
534
			$test = new Circle();
535
			$test->setSanitizedName($testSanitizedName);
536
537
			try {
538
				$stored = $this->circleRequest->searchCircle($test);
539
				if ($stored->getSingleId() === $circle->getSingleId()) {
540
					throw new CircleNotFoundException();
541
				}
542
			} catch (CircleNotFoundException $e) {
543
				$circle->setSanitizedName($testSanitizedName);
544
545
				return;
546
			}
547
548
			$i++;
549
		}
550
	}
551
552
	/**
553
	 * @param string $name
554
	 *
555
	 * @return string
556
	 */
557
	public function sanitizeName(string $name): string {
558
		$acceptedChars = 'qwertyuiopasdfghjklzxcvbnm1234567890 ';
559
		$sanitized = '';
560
		for ($i = 0; $i < strlen($name); $i++) {
561
			if (strpos($acceptedChars, strtolower($name[$i])) !== false) {
562
				$sanitized .= $name[$i];
563
			}
564
		}
565
566
		return str_replace(' ', '', ucwords($sanitized));
567
	}
568
569
570
	/**
571
	 * @param Circle $circle
572
	 *
573
	 * @throws MembersLimitException
574
	 */
575
	public function confirmCircleNotFull(Circle $circle): void {
576
		if ($this->isCircleFull($circle)) {
577
			throw new MembersLimitException(StatusCode::$MEMBER_ADD[121], 121);
0 ignored issues
show
Bug introduced by
The property MEMBER_ADD cannot be accessed from this context as it is declared private in class OCA\Circles\StatusCode.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
578
		}
579
	}
580
581
582
	/**
583
	 * @param Circle $circle
584
	 *
585
	 * @return bool
586
	 * @throws RequestBuilderException
587
	 */
588
	public function isCircleFull(Circle $circle): bool {
589
		$filter = new Member();
590
		$filter->setLevel(Member::LEVEL_MEMBER);
591
		$members = $this->memberRequest->getMembers($circle->getSingleId(), null, null, $filter);
592
593
		$limit = $this->getInt('members_limit', $circle->getSettings());
594
		if ($limit === -1) {
595
			return false;
596
		}
597
		if ($limit === 0) {
598
			$limit = $this->configService->getAppValue(ConfigService::MEMBERS_LIMIT);
599
		}
600
601
		return (sizeof($members) >= $limit);
602
	}
603
604
}
605
606