Completed
Pull Request — master (#551)
by Maxence
01:55
created

Circle   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 566
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 57
lcom 1
cbo 8
dl 0
loc 566
rs 5.04
c 0
b 0
f 0

36 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A setId() 0 5 1
A getId() 0 3 1
A setConfig() 0 13 1
A getConfig() 0 3 1
A isConfig() 0 3 1
A setName() 0 5 1
A getName() 0 3 1
A setAltName() 0 5 1
A getAltName() 0 3 1
A setOwner() 0 5 1
A getOwner() 0 3 1
A hasOwner() 0 3 1
A setMembers() 0 5 1
A getMembers() 0 7 2
A setInitiator() 0 5 1
A getInitiator() 0 3 1
A hasInitiator() 0 3 1
A setInstance() 0 7 2
A getInstance() 0 11 3
A setSettings() 0 5 1
A getSettings() 0 3 1
A setDescription() 0 5 1
A getDescription() 0 3 1
A setContactAddressBook() 0 5 1
A getContactAddressBook() 0 3 1
A setContactGroupName() 0 5 1
A getContactGroupName() 0 3 1
A setMemberOf() 0 5 1
A memberOf() 0 7 2
A setCreation() 0 5 1
A getCreation() 0 3 1
A import() 0 32 4
A jsonSerialize() 0 26 4
A importFromDatabase() 0 23 2
B compareWith() 0 21 10

How to fix   Complexity   

Complex Class

Complex classes like Circle 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 Circle, and based on these observations, apply Extract Interface, too.

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\Model;
33
34
use daita\MySmallPhpTools\Db\Nextcloud\nc21\INC21QueryRow;
35
use daita\MySmallPhpTools\Exceptions\InvalidItemException;
36
use daita\MySmallPhpTools\Model\Nextcloud\nc21\INC21Convert;
37
use daita\MySmallPhpTools\Traits\Nextcloud\nc21\TNC21Convert;
38
use daita\MySmallPhpTools\Traits\TArrayTools;
39
use DateTime;
40
use JsonSerializable;
41
use OCA\Circles\Exceptions\CircleNotFoundException;
42
use OCA\Circles\Exceptions\OwnerNotFoundException;
43
44
45
/**
46
 * Class Circle
47
 *
48
 * ** examples of use of bitwise flags for members management:
49
 *      CFG_OPEN, CFG_REQUEST, CFG_INVITE, CFG_FRIEND
50
 *
51
 * - CFG_OPEN                             => everyone can enter. moderator can add members.
52
 * - CFG_OPEN | CFG_REQUEST               => anyone can initiate a request to join the circle, moderator can
53
 *                                           add members
54
 * - CFG_OPEN | CFG_INVITE                => every one can enter, moderator must send invitation.
55
 * - CFG_OPEN | CFG_INVITE | CFG_REQUEST  => every one send a request, moderator must send invitation.
56
 * - CFG_OPEN | CFG_FRIEND                => useless
57
 * - CFG_OPEN | CFG_FRIEND | *            => useless
58
 *
59
 * - CFG_CIRCLE                           => no one can enter, moderator can add members.
60
 *                                           default config, this is only for code readability.
61
 * - CFG_INVITE                           => no one can enter, moderator must send invitation.
62
 * - CFG_FRIEND                           => no one can enter, but all members can add new member.
63
 * - CFG_REQUEST                          => useless (use CFG_OPEN | CFG_REQUEST)
64
 * - CFG_FRIEND | CFG_REQUEST             => no one can join the circle, but all members can request a
65
 *                                           moderator to accept new member
66
 * - CFG_FRIEND | CFG_INVITE              => no one can join the circle, but all members can add new member.
67
 *                                           An invitation will be generated
68
 * - CFG_FRIEND | CFG_INVITE | CFG_REQUEST  => no one can join the circle, but all members can request a
69
 *                                             moderator to accept new member. An invitation will be generated
70
 *
71
 * @package OCA\Circles\Model
72
 */
73
class Circle extends ManagedModel implements INC21Convert, INC21QueryRow, JsonSerializable {
74
75
76
	use TArrayTools;
77
	use TNC21Convert;
78
79
80
	// specific value
81
	const CFG_CIRCLE = 0;        // only for code readability. Circle is locked by default.
82
	const CFG_SINGLE = 1;        // Circle with only one single member.
83
	const CFG_PERSONAL = 2;      // Personal circle, only the owner can see it.
84
85
	// bitwise
86
	const CFG_VISIBLE = 8;        // Visible to everyone, if not visible, people have to know its name to be able to find it
87
	const CFG_OPEN = 16;          // Circle is open, people can join
88
	const CFG_INVITE = 32;        // Adding a member generate an invitation that needs to be accepted
89
	const CFG_REQUEST = 64;       // Request to join Circles needs to be confirmed by a moderator
90
	const CFG_FRIEND = 128;       // Members of the circle can invite their friends
91
	const CFG_PROTECTED = 256;    // Password protected to join/request
92
	const CFG_NO_OWNER = 512;     // no owner, only members
93
	const CFG_HIDDEN = 1024;      // hidden from listing, but available as a share entity
94
	const CFG_BACKEND = 2048;     // Fully hidden, only backend Circles
95
	const CFG_ROOT = 4096;        // Circle cannot be inside another Circle
96
	const CFG_FEDERATED = 8192;   // Federated
97
98
	static $DEF = [
99
		1 => 'S|Single',
100
101
		2    => 'P|Personal',
102
		8    => 'V|Visible',
103
		16   => 'O|Open',
104
		32   => 'I|Invite',
105
		64   => 'JR|Join Request',
106
		128  => 'F|Friends',
107
		256  => 'PP|Password Protected',
108
		512  => 'NO|No Owner',
109
		1024 => 'H|Hidden',
110
		2048 => 'T|Root',
111
		4096 => 'F|Federated'
112
	];
113
114
115
	/** @var string */
116
	private $id = '';
117
118
	/** @var int */
119
	private $config = 0;
120
121
	/** @var int */
122
	private $type = 0;
123
124
	/** @var string */
125
	private $name = '';
126
127
	/** @var string */
128
	private $altName = '';
129
130
	/** @var Member */
131
	private $owner;
132
133
	/** @var array */
134
	private $members = [];
135
136
	/** @var Member */
137
	private $initiator;
138
139
	/** @var array */
140
	private $settings = [];
141
142
	/** @var string */
143
	private $description = '';
144
145
	/** @var int */
146
	private $contactAddressBook = 0;
147
148
	/** @var string */
149
	private $contactGroupName = '';
150
151
	/** @var string */
152
	private $instance = '';
153
154
//	/** @var bool */
155
//	private $hidden = false;
156
157
	/** @var int */
158
	private $creation = 0;
159
160
161
	/** @var Circle[] */
162
	private $memberOf = null;
163
164
	private $completeJson = false;
165
166
167
	/**
168
	 * Circle constructor.
169
	 */
170
	public function __construct() {
171
	}
172
173
	/**
174
	 * @param string $id
175
	 *
176
	 * @return self
177
	 */
178
	public function setId(string $id): self {
179
		$this->id = $id;
180
181
		return $this;
182
	}
183
184
	/**
185
	 * @return string
186
	 */
187
	public function getId(): string {
188
		return $this->id;
189
	}
190
191
192
	/**
193
	 * @param int $config
194
	 *
195
	 * @return self
196
	 */
197
	public function setConfig(int $config): self {
198
		$this->config = $config;
199
200
//		$this->hidden = false;
201
//		foreach (array_keys(self::$DEF) as $def) {
202
//			if ($this->isType($def) && substr(self::$DEF[$def], 0, 1) === '*') {
203
//				$this->setHidden(true);
204
//				break;
205
//			}
206
//		}
207
208
		return $this;
209
	}
210
211
	/**
212
	 * @return int
213
	 */
214
	public function getConfig(): int {
215
		return $this->config;
216
	}
217
218
	/**
219
	 * @param int $flag
220
	 *
221
	 * @return bool
222
	 */
223
	public function isConfig(int $flag): bool {
224
		return (($this->getConfig() & $flag) !== 0);
225
	}
226
227
228
	/**
229
	 * @param string $name
230
	 *
231
	 * @return self
232
	 */
233
	public function setName(string $name): self {
234
		$this->name = $name;
235
236
		return $this;
237
	}
238
239
	/**
240
	 * @return string
241
	 */
242
	public function getName(): string {
243
		return $this->name;
244
	}
245
246
247
	/**
248
	 * @param string $altName
249
	 *
250
	 * @return self
251
	 */
252
	public function setAltName(string $altName): self {
253
		$this->altName = $altName;
254
255
		return $this;
256
	}
257
258
	/**
259
	 * @return string
260
	 */
261
	public function getAltName(): string {
262
		return $this->altName;
263
	}
264
265
266
	/**
267
	 * @param Member $owner
268
	 *
269
	 * @return self
270
	 */
271
	public function setOwner(Member $owner): self {
272
		$this->owner = $owner;
273
274
		return $this;
275
	}
276
277
	/**
278
	 * @return Member
279
	 */
280
	public function getOwner(): Member {
281
		return $this->owner;
282
	}
283
284
	/**
285
	 * @return bool
286
	 */
287
	public function hasOwner(): bool {
288
		return ($this->owner !== null);
289
	}
290
291
292
	/**
293
	 * @param array $members
294
	 *
295
	 * @return self
296
	 */
297
	public function setMembers(array $members): self {
298
		$this->members = $members;
299
300
		return $this;
301
	}
302
303
	/**
304
	 * @return array
305
	 */
306
	public function getMembers(): array {
307
		if (empty($this->members)) {
308
			$this->getManager()->getMembers($this);
309
		}
310
311
		return $this->members;
312
	}
313
314
315
	/**
316
	 * @param Member $initiator
317
	 *
318
	 * @return Circle
319
	 */
320
	public function setInitiator(Member $initiator): self {
321
		$this->initiator = $initiator;
322
323
		return $this;
324
	}
325
326
	/**
327
	 * @return Member
328
	 */
329
	public function getInitiator(): Member {
330
		return $this->initiator;
331
	}
332
333
	/**
334
	 * @return bool
335
	 */
336
	public function hasInitiator(): bool {
337
		return ($this->initiator !== null);
338
	}
339
340
	/**
341
	 * @param string $instance
342
	 *
343
	 * @return Circle
344
	 */
345
	public function setInstance(string $instance): self {
346
		if ($this->isConfig(self::CFG_NO_OWNER)) {
347
			$this->instance = $instance;
348
		}
349
350
		return $this;
351
	}
352
353
	/**
354
	 * @return string
355
	 * @throws OwnerNotFoundException
356
	 */
357
	public function getInstance(): string {
358
		if ($this->isConfig(self::CFG_NO_OWNER)) {
359
			return $this->instance;
360
		}
361
362
		if (!$this->hasOwner()) {
363
			throw new OwnerNotFoundException('circle has no owner, or not set to have no owner');
364
		}
365
366
		return $this->getOwner()->getInstance();
367
	}
368
369
370
	/**
371
	 * @param array $settings
372
	 *
373
	 * @return self
374
	 */
375
	public function setSettings(array $settings): self {
376
		$this->settings = $settings;
377
378
		return $this;
379
	}
380
381
	/**
382
	 * @return array
383
	 */
384
	public function getSettings(): array {
385
		return $this->settings;
386
	}
387
388
389
	/**
390
	 * @param string $description
391
	 *
392
	 * @return self
393
	 */
394
	public function setDescription(string $description): self {
395
		$this->description = $description;
396
397
		return $this;
398
	}
399
400
	/**
401
	 * @return string
402
	 */
403
	public function getDescription(): string {
404
		return $this->description;
405
	}
406
407
408
	/**
409
	 * @param int $contactAddressBook
410
	 *
411
	 * @return self
412
	 */
413
	public function setContactAddressBook(int $contactAddressBook): self {
414
		$this->contactAddressBook = $contactAddressBook;
415
416
		return $this;
417
	}
418
419
	/**
420
	 * @return int
421
	 */
422
	public function getContactAddressBook(): int {
423
		return $this->contactAddressBook;
424
	}
425
426
427
	/**
428
	 * @param string $contactGroupName
429
	 *
430
	 * @return self
431
	 */
432
	public function setContactGroupName(string $contactGroupName): self {
433
		$this->contactGroupName = $contactGroupName;
434
435
		return $this;
436
	}
437
438
	/**
439
	 * @return string
440
	 */
441
	public function getContactGroupName(): string {
442
		return $this->contactGroupName;
443
	}
444
445
446
//	/**
447
//	 * @param bool $hidden
448
//	 *
449
//	 * @return Circle
450
//	 */
451
//	public function setHidden(bool $hidden): self {
452
//		$this->hidden = $hidden;
453
//
454
//		return $this;
455
//	}
456
//
457
//	/**
458
//	 * @return bool
459
//	 */
460
//	public function isHidden(): bool {
461
//		return $this->hidden;
462
//	}
463
464
465
	/**
466
	 * @param array $memberOf
467
	 *
468
	 * @return $this
469
	 */
470
	public function setMemberOf(array $memberOf): self {
471
		$this->memberOf = $memberOf;
472
473
		return $this;
474
	}
475
476
	/**
477
	 * @return Circle[]
478
	 */
479
	public function memberOf(): array {
480
		if ($this->memberOf === null) {
481
			$this->getManager()->memberOf($this);
482
		}
483
484
		return $this->memberOf;
485
	}
486
487
488
	/**
489
	 * @param int $creation
490
	 *
491
	 * @return self
492
	 */
493
	public function setCreation(int $creation): self {
494
		$this->creation = $creation;
495
496
		return $this;
497
	}
498
499
	/**
500
	 * @return int
501
	 */
502
	public function getCreation(): int {
503
		return $this->creation;
504
	}
505
506
507
	/**
508
	 * @param array $data
509
	 *
510
	 * @return $this
511
	 * @throws InvalidItemException
512
	 */
513
	public function import(array $data): INC21Convert {
514
		if ($this->get('id', $data) === '') {
515
			throw new InvalidItemException();
516
		}
517
518
		$this->setId($this->get('id', $data))
519
			 ->setName($this->get('name', $data))
520
			 ->setAltName($this->get('alt_name', $data))
521
			 ->setConfig($this->getInt('config', $data))
522
			 ->setSettings($this->getArray('settings', $data))
523
//			 ->setContactAddressBook($this->get('contact_addressbook', $data))
524
//			 ->setContactGroupName($this->get('contact_groupname', $data))
525
			 ->setDescription($this->get('description', $data))
526
			 ->setCreation($this->getInt('creation', $data));
527
528
529
		try {
530
			/** @var Member $owner */
531
			$owner = $this->convert($this->getArray('owner', $data), Member::class);
532
			$this->setOwner($owner);
533
		} catch (InvalidItemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
534
		}
535
536
		try {
537
			/** @var Member $owner */
538
			$initiator = $this->convert($this->getArray('initiator', $data), Member::class);
539
			$this->setInitiator($initiator);
0 ignored issues
show
Compatibility introduced by
$initiator of type object<daita\MySmallPhpT...loud\nc21\INC21Convert> is not a sub-type of object<OCA\Circles\Model\Member>. It seems like you assume a concrete implementation of the interface daita\MySmallPhpTools\Mo...cloud\nc21\INC21Convert to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
540
		} catch (InvalidItemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
541
		}
542
543
		return $this;
544
	}
545
546
547
	/**
548
	 * @return array
549
	 */
550
	public function jsonSerialize(): array {
551
		$arr = [
552
			'id'          => $this->getId(),
553
			'name'        => $this->getName(),
554
			'alt_name'    => $this->getAltName(),
555
			'config'      => $this->getConfig(),
556
			'description' => $this->getDescription(),
557
			'settings'    => $this->getSettings(),
558
			//			'hidden'      => $this->isHidden(),
559
			'creation'    => $this->getCreation()
560
		];
561
562
		if ($this->hasOwner()) {
563
			$arr['owner'] = $this->getOwner();
564
		}
565
566
		if ($this->hasInitiator()) {
567
			$arr['initiator'] = $this->getInitiator();
568
		}
569
570
		if ($this->getManager()->isFullDetails()) {
571
			$arr['memberOf'] = $this->memberOf();
572
		}
573
574
		return array_filter($arr);
575
	}
576
577
578
	/**
579
	 * @param array $data
580
	 * @param string $prefix
581
	 *
582
	 * @return INC21QueryRow
583
	 * @throws CircleNotFoundException
584
	 */
585
	public function importFromDatabase(array $data, string $prefix = ''): INC21QueryRow {
586
		if (!array_key_exists($prefix . 'unique_id', $data)) {
587
			throw new CircleNotFoundException();
588
		}
589
590
		$this->setId($this->get($prefix . 'unique_id', $data))
591
			 ->setName($this->get($prefix . 'name', $data))
592
			 ->setAltName($this->get($prefix . 'alt_name', $data))
593
			 ->setConfig($this->getInt($prefix . 'config', $data))
594
			 ->setInstance($this->get($prefix . 'instance', $data))
595
			 ->setSettings($this->getArray($prefix . 'settings', $data))
596
			 ->setContactAddressBook($this->getInt($prefix . 'contact_addressbook', $data))
597
			 ->setContactGroupName($this->get($prefix . 'contact_groupname', $data))
598
			 ->setDescription($this->get($prefix . 'description', $data));
599
600
		$creation = $this->get($prefix . 'creation', $data);
601
		$this->setCreation(DateTime::createFromFormat('Y-m-d H:i:s', $creation)->getTimestamp());
602
603
		$this->getManager()->importOwnerFromDatabase($this, $data);
604
		$this->getManager()->importInitiatorFromDatabase($this, $data);
605
606
		return $this;
607
	}
608
609
610
	/**
611
	 * @param Circle $circle
612
	 *
613
	 * @return bool
614
	 * @throws OwnerNotFoundException
615
	 */
616
	public function compareWith(Circle $circle): bool {
617
		if ($this->getId() !== $circle->getId()
618
			|| $this->getInstance() !== $circle->getInstance()
619
			|| $this->getConfig() !== $circle->getConfig()) {
620
			return false;
621
		}
622
623
		if ($this->hasOwner()
624
			&& (!$circle->hasOwner()
625
				|| !$this->getOwner()->compareWith($circle->getOwner()))) {
626
			return false;
627
		}
628
629
		if ($this->hasInitiator()
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !($this->hasIniti...cle->getInitiator())));.
Loading history...
630
			&& (!$circle->hasInitiator()
631
				|| !$this->getInitiator()->compareWith($circle->getInitiator()))) {
632
			return false;
633
		}
634
635
		return true;
636
	}
637
638
}
639
640