Completed
Push — master ( bca2e7...1f8246 )
by Maxence
02:35 queued 11s
created

CircleService::confirmName()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 3
nc 2
nop 1
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->setData(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 $displayName
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 updateDisplayName(string $circleId, string $displayName): array {
273
		$circle = $this->getCircle($circleId);
274
275
		$event = new FederatedEvent(CircleEdit::class);
276
		$event->setCircle($circle);
277
		$event->setData(new SimpleDataStore(['displayName' => $displayName]));
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->setData(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->setData(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
364
		$event = new FederatedEvent(CircleJoin::class);
365
		$event->setCircle($circle);
366
367
		$this->federatedEventService->newEvent($event);
368
369
		return $event->getOutcome();
370
	}
371
372
373
	/**
374
	 * @param string $circleId
375
	 *
376
	 * @return array
377
	 * @throws CircleNotFoundException
378
	 * @throws FederatedEventException
379
	 * @throws FederatedItemException
380
	 * @throws InitiatorNotConfirmedException
381
	 * @throws InitiatorNotFoundException
382
	 * @throws OwnerNotFoundException
383
	 * @throws RemoteInstanceException
384
	 * @throws RemoteNotFoundException
385
	 * @throws RemoteResourceNotFoundException
386
	 * @throws UnknownRemoteException
387
	 */
388
	public function circleLeave(string $circleId): array {
389
		$this->federatedUserService->mustHaveCurrentUser();
390
391
		$circle = $this->circleRequest->getCircle($circleId, $this->federatedUserService->getCurrentUser());
392
393
		$event = new FederatedEvent(CircleLeave::class);
394
		$event->setCircle($circle);
395
396
		$this->federatedEventService->newEvent($event);
397
398
		return $event->getOutcome();
399
	}
400
401
402
	/**
403
	 * @param string $circleId
404
	 * @param int $filter
405
	 *
406
	 * @return Circle
407
	 * @throws CircleNotFoundException
408
	 * @throws InitiatorNotFoundException
409
	 * @throws RequestBuilderException
410
	 */
411
	public function getCircle(
412
		string $circleId,
413
		int $filter = Circle::CFG_BACKEND | Circle::CFG_SINGLE | Circle::CFG_HIDDEN
414
	): Circle {
415
		$this->federatedUserService->mustHaveCurrentUser();
416
417
		return $this->circleRequest->getCircle(
418
			$circleId,
419
			$this->federatedUserService->getCurrentUser(),
420
			$this->federatedUserService->getRemoteInstance(),
421
			$filter
422
		);
423
	}
424
425
426
	/**
427
	 * @param Circle|null $circleFilter
428
	 * @param Member|null $memberFilter
429
	 * @param SimpleDataStore|null $params
430
	 *
431
	 * @return Circle[]
432
	 * @throws InitiatorNotFoundException
433
	 * @throws RequestBuilderException
434
	 */
435
	public function getCircles(
436
		?Circle $circleFilter = null,
437
		?Member $memberFilter = null,
438
		?SimpleDataStore $params = null
439
	): array {
440
		$this->federatedUserService->mustHaveCurrentUser();
441
442
		if ($params === null) {
443
			$params = new SimpleDataStore();
444
		}
445
		$params->default(
446
			[
447
				'limit'                  => -1,
448
				'offset'                 => 0,
449
				'mustBeMember'           => false,
450
				'includeHiddenCircles'   => false,
451
				'includeBackendCircles'  => false,
452
				'includeSystemCircles'   => false,
453
				'includePersonalCircles' => false
454
			]
455
		);
456
457
		return $this->circleRequest->getCircles(
458
			$circleFilter,
459
			$memberFilter,
460
			$this->federatedUserService->getCurrentUser(),
461
			$this->federatedUserService->getRemoteInstance(),
462
			$params
463
		);
464
	}
465
466
467
	/**
468
	 * @param Circle $circle
469
	 *
470
	 * @throws RequestBuilderException
471
	 */
472
	public function confirmName(Circle $circle): void {
473
		if ($circle->isConfig(Circle::CFG_SYSTEM)
474
			|| $circle->isConfig(Circle::CFG_SINGLE)) {
475
			return;
476
		}
477
478
		$this->confirmDisplayName($circle);
479
		$this->confirmSanitizedName($circle);
480
	}
481
482
	/**
483
	 * @param Circle $circle
484
	 *
485
	 * @throws RequestBuilderException
486
	 */
487
	private function confirmDisplayName(Circle $circle) {
488
		$baseDisplayName = $circle->getName();
489
490
		$i = 1;
491
		while (true) {
492
			$testDisplayName = $baseDisplayName . (($i > 1) ? ' (' . $i . ')' : '');
493
			$test = new Circle();
494
			$test->setDisplayName($testDisplayName);
495
496
			try {
497
				$stored = $this->circleRequest->searchCircle($test);
498
				if ($stored->getSingleId() === $circle->getSingleId()) {
499
					throw new CircleNotFoundException();
500
				}
501
			} catch (CircleNotFoundException $e) {
502
				$circle->setDisplayName($testDisplayName);
503
504
				return;
505
			}
506
507
			$i++;
508
		}
509
	}
510
511
512
	/**
513
	 * @param Circle $circle
514
	 *
515
	 * @throws RequestBuilderException
516
	 */
517
	private function confirmSanitizedName(Circle $circle) {
518
		$baseSanitizedName = $this->sanitizeName($circle->getName());
519
		if ($baseSanitizedName === '') {
520
			$baseSanitizedName = substr($circle->getSingleId(), 0, 3);
521
		}
522
523
		$i = 1;
524
		while (true) {
525
			$testSanitizedName = $baseSanitizedName . (($i > 1) ? '-' . $i : '');
526
527
			$test = new Circle();
528
			$test->setSanitizedName($testSanitizedName);
529
530
			try {
531
				$stored = $this->circleRequest->searchCircle($test);
532
				if ($stored->getSingleId() === $circle->getSingleId()) {
533
					throw new CircleNotFoundException();
534
				}
535
			} catch (CircleNotFoundException $e) {
536
				$circle->setSanitizedName($testSanitizedName);
537
538
				return;
539
			}
540
541
			$i++;
542
		}
543
	}
544
545
	/**
546
	 * @param string $name
547
	 *
548
	 * @return string
549
	 */
550
	public function sanitizeName(string $name): string {
551
		$acceptedChars = 'qwertyuiopasdfghjklzxcvbnm ';
552
		$sanitized = '';
553
		for ($i = 0; $i < strlen($name); $i++) {
554
			if (strpos($acceptedChars, strtolower($name[$i])) !== false) {
555
				$sanitized .= $name[$i];
556
			}
557
		}
558
559
		return str_replace(' ', '', ucwords($sanitized));
560
	}
561
562
563
	/**
564
	 * @param Circle $circle
565
	 *
566
	 * @throws MembersLimitException
567
	 */
568
	public function confirmCircleNotFull(Circle $circle): void {
569
		if ($this->isCircleFull($circle)) {
570
			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...
571
		}
572
	}
573
574
575
	/**
576
	 * @param Circle $circle
577
	 *
578
	 * @return bool
579
	 */
580
	public function isCircleFull(Circle $circle): bool {
581
		$filter = new Member();
582
		$filter->setLevel(Member::LEVEL_MEMBER);
583
		$members = $this->memberRequest->getMembers($circle->getSingleId(), null, null, $filter);
584
585
		$limit = $this->getInt('members_limit', $circle->getSettings());
586
		if ($limit === -1) {
587
			return false;
588
		}
589
		if ($limit === 0) {
590
			$limit = $this->configService->getAppValue(ConfigService::MEMBERS_LIMIT);
591
		}
592
593
		return (sizeof($members) >= $limit);
594
	}
595
596
}
597
598