Completed
Push — master ( d9f0cd...271a28 )
by Maxence
03:18 queued 01:12
created

DavService::manageDeprecatedMembers()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2017
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\Service;
31
32
33
use Exception;
34
use OCA\Circles\Circles\FileSharingBroadcaster;
35
use OCA\Circles\Db\CirclesRequest;
36
use OCA\Circles\Db\MembersRequest;
37
use OCA\Circles\Exceptions\CircleAlreadyExistsException;
38
use OCA\Circles\Exceptions\CircleDoesNotExistException;
39
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
40
use OCA\Circles\Exceptions\MemberDoesNotExistException;
41
use OCA\Circles\Exceptions\NotLocalMemberException;
42
use OCA\Circles\Model\Circle;
43
use OCA\Circles\Model\DavCard;
44
use OCA\Circles\Model\Member;
45
use OCA\DAV\CardDAV\CardDavBackend;
46
use OCP\App\ManagerEvent;
47
use OCP\Federation\ICloudIdManager;
48
use OCP\IUserManager;
49
use Symfony\Component\EventDispatcher\GenericEvent;
50
51
52
/**
53
 * Class DavService
54
 *
55
 * @package OCA\Circles\Service
56
 */
57
class DavService {
58
59
60
	/** @var string */
61
	private $userId;
62
63
	/** @var IUserManager */
64
	private $userManager;
65
66
	/** @var CardDavBackend */
67
	private $cardDavBackend;
68
69
	/** @var ICloudIdManager */
70
	private $cloudManager;
71
72
	/** @var FileSharingBroadcaster */
73
	private $fileSharingBroadcaster;
74
75
	/** @var CirclesRequest */
76
	private $circlesRequest;
77
78
	/** @var MembersRequest */
79
	private $membersRequest;
80
81
	/** @var ConfigService */
82
	private $configService;
83
84
	/** @var MiscService */
85
	private $miscService;
86
87
88
	/** @var array */
89
	private $migratedBooks = [];
90
91
92
	/**
93
	 * TimezoneService constructor.
94
	 *
95
	 * @param string $userId
96
	 * @param IUserManager $userManager
97
	 * @param CardDavBackend $cardDavBackend
98
	 * @param ICloudIdManager $cloudManager
99
	 * @param CirclesRequest $circlesRequest
100
	 * @param FileSharingBroadcaster $fileSharingBroadcaster
101
	 * @param MembersRequest $membersRequest
102
	 * @param ConfigService $configService
103
	 * @param MiscService $miscService
104
	 */
105 View Code Duplication
	public function __construct(
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...
106
		$userId, IUserManager $userManager, CardDavBackend $cardDavBackend, ICloudIdManager $cloudManager,
107
		FileSharingBroadcaster $fileSharingBroadcaster, CirclesRequest $circlesRequest,
108
		MembersRequest $membersRequest, ConfigService $configService, MiscService $miscService
109
	) {
110
		$this->userId = $userId;
111
		$this->userManager = $userManager;
112
		$this->cardDavBackend = $cardDavBackend;
113
		$this->cloudManager = $cloudManager;
114
		$this->fileSharingBroadcaster = $fileSharingBroadcaster;
115
		$this->circlesRequest = $circlesRequest;
116
		$this->membersRequest = $membersRequest;
117
		$this->configService = $configService;
118
		$this->miscService = $miscService;
119
	}
120
121
122
	/**
123
	 * @param ManagerEvent $event
124
	 */
125
	public function onAppEnabled(ManagerEvent $event) {
126
		if ($event->getAppID() !== 'circles') {
127
			return;
128
		}
129
130
		try {
131
			$this->migration();
132
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
133
		}
134
	}
135
136
137
	/**
138
	 * @param GenericEvent $event
139
	 */
140
	public function onCreateCard(GenericEvent $event) {
141
		$davCard = $this->generateDavCard($event);
142
		$this->manageDavCard($davCard);
143
	}
144
145
146
	/**
147
	 * @param GenericEvent $event
148
	 */
149
	public function onUpdateCard(GenericEvent $event) {
150
		$davCard = $this->generateDavCard($event);
151
		$this->manageDavCard($davCard);
152
	}
153
154
155
	/**
156
	 * @param GenericEvent $event
157
	 */
158
	public function onDeleteCard(GenericEvent $event) {
159
		$davCard = $this->generateDavCard($event, true);
160
161
		$this->miscService->log('Deleting Card: ' . json_encode($davCard), 1);
162
		$this->membersRequest->removeMembersByContactId($davCard->getUniqueId(), Member::TYPE_USER);
163
		$this->manageDeprecatedCircles($davCard->getAddressBookId());
164
		$this->manageDeprecatedMembers($davCard);
165
	}
166
167
168
	/**
169
	 * @param GenericEvent $event
170
	 * @param bool $tiny
171
	 *
172
	 * @return DavCard
173
	 */
174
	private function generateDavCard(GenericEvent $event, bool $tiny = false): DavCard {
175
		$addressBookId = $event->getArgument('addressBookId');
176
		$cardUri = $event->getArgument('cardUri');
177
178
		$davCard = new DavCard();
179
		$davCard->setAddressBookId($addressBookId);
180
		$davCard->setCardUri($cardUri);
181
182
		if ($tiny) {
183
			return $davCard;
184
		}
185
186
		$cardData = $event->getArgument('cardData');
187
		$davCard->setOwner($this->getOwnerFromAddressBook($addressBookId));
188
		$davCard->importFromDav($cardData);
189
190
		return $davCard;
191
	}
192
193
194
	/**
195
	 * @param int $bookId
196
	 * @param array $card
197
	 *
198
	 * @return DavCard
199
	 */
200
	private function generateDavCardFromCard(int $bookId, array $card): DavCard {
201
		$davCard = new DavCard();
202
		$davCard->setAddressBookId($bookId);
203
		$davCard->setCardUri($card['uri']);
204
205
		$davCard->setOwner($this->getOwnerFromAddressBook($bookId));
206
		$davCard->importFromDav($card['carddata']);
207
208
		return $davCard;
209
	}
210
211
212
	/**
213
	 * @param DavCard $davCard
214
	 *
215
	 */
216
	private function manageDavCard(DavCard $davCard) {
217
		$this->miscService->log('Updating Card: ' . json_encode($davCard), 1);
218
		$this->manageCircles($davCard);
219
		$this->manageContact($davCard);
220
	}
221
222
223
	/**
224
	 * @param DavCard $davCard
225
	 */
226
	private function manageContact(DavCard $davCard) {
227
		$this->manageDeprecatedMembers($davCard);
228
229
		switch ($this->getMemberType($davCard)) {
230
			case DavCard::TYPE_CONTACT:
231
				$this->manageRemoteContact($davCard);
232
				break;
233
234
			case DavCard::TYPE_LOCAL:
235
				$this->manageLocalContact($davCard);
236
				break;
237
238
//			case DavCard::TYPE_FEDERATED:
239
//				$this->manageFederatedContact($davCard);
240
//				break;
241
		}
242
	}
243
244
245
	/**
246
	 * @param DavCard $davCard
247
	 */
248
	private function manageDeprecatedMembers(DavCard $davCard) {
249
		$circles = array_map(
250
			function(Circle $circle) {
251
				return $circle->getUniqueId();
252
			}, $davCard->getCircles()
253
		);
254
255
		$members = $this->membersRequest->getMembersByContactId($davCard->getUniqueId());
256
		foreach ($members as $member) {
257
			if (!in_array($member->getCircleId(), $circles)) {
258
				$this->membersRequest->removeMember($member);
259
			}
260
		}
261
	}
262
263
264
	/**
265
	 * @param DavCard $davCard
266
	 */
267
	private function manageLocalContact(DavCard $davCard) {
268
		foreach ($davCard->getCircles() as $circle) {
269
			$this->manageMember($circle, $davCard, Member::TYPE_USER);
270
		}
271
	}
272
273
274
	/**
275
	 * @param DavCard $davCard
276
	 */
277
	private function manageRemoteContact(DavCard $davCard) {
278
		foreach ($davCard->getCircles() as $circle) {
279
			$this->manageMember($circle, $davCard, Member::TYPE_CONTACT);
280
		}
281
	}
282
283
284
	/**
285
	 * @param Circle $circle
286
	 * @param DavCard $davCard
287
	 * @param int $type
288
	 */
289
	private function manageMember(Circle $circle, DavCard $davCard, int $type) {
290
		try {
291
			$member =
292
				$this->membersRequest->getContactMember($circle->getUniqueId(), $davCard->getUniqueId());
293
294
			if ($member->getType() !== $type) {
295
				$this->membersRequest->removeMember($member);
296
				throw new MemberDoesNotExistException();
297
			}
298
		} catch (MemberDoesNotExistException $e) {
299
			$member = new Member();
300
			$member->setLevel(Member::LEVEL_MEMBER);
301
			$member->setStatus(Member::STATUS_MEMBER);
302
			$member->setContactId($davCard->getUniqueId());
303
			$member->setType($type);
304
			$member->setCircleId($circle->getUniqueId());
305
			$member->setUserId($davCard->getUserId());
306
307
			try {
308
				$this->membersRequest->createMember($member);
309
310
//				if ($type === Member::TYPE_CONTACT) {
311
//					$this->fileSharingBroadcaster->sendMailAboutExistingShares($circle, $member);
312
//				}
313
			} catch (MemberAlreadyExistsException $e) {
314
				$this->membersRequest->checkMember($member, false);
315
			}
316
		}
317
	}
318
319
320
	/**
321
	 * @param DavCard $davCard
322
	 *
323
	 * @return int
324
	 */
325
	private function getMemberType(DavCard $davCard): int {
326
		foreach ($davCard->getEmails() as $email) {
327
			try {
328
				$davCard->setUserId($this->isLocalMember($email));
329
330
				return DavCard::TYPE_LOCAL;
331
			} catch (NotLocalMemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
332
			}
333
		}
334
335
		$davCard->setUserId($davCard->getOwner() . ':' . $davCard->getContactId());
336
337
		return DavCard::TYPE_CONTACT;
338
	}
339
340
341
	/**
342
	 * @param string $email
343
	 *
344
	 * @return string
345
	 * @throws NotLocalMemberException
346
	 */
347
	private function isLocalMember(string $email): string {
348
		if (strpos($email, '@') === false) {
349
			throw new NotLocalMemberException();
350
		}
351
352
		list ($username, $domain) = explode('@', $email);
353
		if (in_array($domain, $this->configService->getAvailableHosts())) {
354
			$user = $this->userManager->get($username);
355
			if ($user !== null) {
356
				return $user->getUID();
357
			}
358
		}
359
360
		throw new NotLocalMemberException();
361
	}
362
363
364
	/**
365
	 * @param DavCard $davCard
366
	 */
367
	private function manageCircles(DavCard $davCard) {
368
		$fromCard = $davCard->getGroups();
369
		$current = array_map(
370
			function(Circle $circle) {
371
				return $circle->getContactGroupName();
372
			}, $this->getCirclesFromBook($davCard->getAddressBookId())
373
		);
374
375
		$this->manageNewCircles($davCard, $fromCard, $current);
376
		$this->manageDeprecatedCircles($davCard->getAddressBookId());
377
378
		$this->assignCirclesToCard($davCard);
379
	}
380
381
382
	/**
383
	 * @param DavCard $davCard
384
	 * @param array $fromCard
385
	 * @param array $current
386
	 */
387
	private function manageNewCircles(DavCard $davCard, array $fromCard, array $current) {
388
		foreach ($fromCard as $group) {
389
			if (in_array($group, $current)) {
390
				continue;
391
			}
392
393
			$user = $this->userManager->get($davCard->getOwner());
394
			$circle = new Circle($this->configService->contactsBackendType(), $group . ' - ' . $user->getDisplayName());
395
			$circle->setContactAddressBook($davCard->getAddressBookId());
396
			$circle->setContactGroupName($group);
397
398
			try {
399
				$this->circlesRequest->createCircle($circle, $davCard->getOwner());
400
				$member = new Member($davCard->getOwner(), Member::TYPE_USER, $circle->getUniqueId());
401
				$member->setLevel(Member::LEVEL_OWNER);
402
				$member->setStatus(Member::STATUS_MEMBER);
403
404
				try {
405
					$this->membersRequest->createMember($member);
406
				} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
407
				}
408
			} catch (CircleAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
409
			}
410
411
		}
412
	}
413
414
415
	/**
416
	 * @param DavCard $davCard
417
	 */
418
	private function assignCirclesToCard(DavCard $davCard) {
419
		foreach ($davCard->getGroups() as $group) {
420
			try {
421
				$davCard->addCircle(
422
					$this->circlesRequest->getFromContactGroup($davCard->getAddressBookId(), $group)
423
				);
424
			} catch (CircleDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
425
			}
426
		}
427
	}
428
429
430
	/**
431
	 * @param int $addressBookId
432
	 *
433
	 * @return Circle[]
434
	 */
435
	private function getCirclesFromBook(int $addressBookId): array {
436
		return $this->circlesRequest->getFromBook($addressBookId);
437
	}
438
439
440
	/**
441
	 * @param int $length
442
	 *
443
	 * @return string
444
	 * @deprecated
445
	 */
446
	protected function uuid(int $length = 0): string {
447
		$uuid = sprintf(
448
			'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff),
449
			mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
450
			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
451
		);
452
453
		if ($length > 0) {
454
			if ($length <= 16) {
455
				$uuid = str_replace('-', '', $uuid);
456
			}
457
458
			$uuid = substr($uuid, 0, $length);
459
		}
460
461
		return $uuid;
462
	}
463
464
465
	/**
466
	 * @param int $bookId
467
	 *
468
	 * @return string
469
	 */
470
	private function getOwnerFromAddressBook(int $bookId): string {
471
		$data = $this->cardDavBackend->getAddressBookById($bookId);
472
473
		// let's assume the format is principals/users/OWNER
474
		$owner = substr($data['principaluri'], 17);
475
476
		return $owner;
477
	}
478
479
480
	/**
481
	 *
482
	 * @throws Exception
483
	 */
484
	public function migration() {
485
		if (!$this->configService->isContactsBackend()) {
486
			throw new Exception('Circles needs to be set as Contacts App Backend first');
487
		}
488
489
		$this->manageDeprecatedContacts();
490
		$this->manageDeprecatedCircles();
491
		$users = $this->userManager->search('');
492
		foreach ($users as $user) {
493
			$books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID());
494
			foreach ($books as $book) {
495
				$this->migrateBook($book['id']);
496
			}
497
		}
498
	}
499
500
501
	/**
502
	 */
503
	private function manageDeprecatedContacts() {
504
		$contacts = $this->membersRequest->getMembersByContactId();
505
506
		foreach ($contacts as $contact) {
507
			try {
508
				$this->getDavCardFromMember($contact);
509
			} catch (MemberDoesNotExistException $e) {
510
				$this->membersRequest->removeMember($contact);
511
			}
512
		}
513
	}
514
515
516
	/**
517
	 * @param int $bookId
518
	 */
519
	private function manageDeprecatedCircles(int $bookId = 0) {
520
		$knownBooks = [$bookId];
521
		if ($bookId > 0) {
522
			$knownBooks = [];
523
			$contacts = $this->membersRequest->getMembersByContactId();
524
			foreach ($contacts as $contact) {
525
				list($bookId,) = explode('/', $contact->getContactId(), 2);
526
				if (in_array($bookId, $knownBooks)) {
527
					continue;
528
				}
529
530
				$knownBooks[] = $bookId;
531
			}
532
		}
533
534
		foreach ($knownBooks as $bookId) {
535
			$circles = $this->circlesRequest->getFromContactBook($bookId);
536
			$fromBook = $this->getExistingCirclesFromBook($bookId);
537
538
			foreach ($circles as $circle) {
539
				if (in_array($circle->getContactGroupName(), $fromBook)) {
540
					continue;
541
				}
542
543
				$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
544
				$this->circlesRequest->destroyCircle($circle->getUniqueId());
545
			}
546
		}
547
	}
548
549
550
	/**
551
	 * @param int $bookId
552
	 *
553
	 * @return Circle[]
554
	 */
555
	private function getExistingCirclesFromBook(int $bookId): array {
556
		$circles = [];
557
		$cards = $this->cardDavBackend->getCards($bookId);
558
		foreach ($cards as $card) {
559
			$davCard = $this->generateDavCardFromCard($bookId, $card);
560
			$this->assignCirclesToCard($davCard);
561
			$circles = array_merge($circles, $davCard->getCircles());
562
		}
563
564
		$existing = array_map(
565
			function(Circle $circle) {
566
				return $circle->getContactGroupName();
567
			}, $circles
568
		);
569
570
		return array_unique($existing);
571
	}
572
573
574
	/**
575
	 * @param Member $contact
576
	 *
577
	 * @return DavCard
578
	 * @throws MemberDoesNotExistException
579
	 */
580
	public function getDavCardFromMember(Member $contact): DavCard {
581
		list($bookId, $cardUri) = explode('/', $contact->getContactId(), 2);
582
		$cards = $this->cardDavBackend->getCards($bookId);
583
		foreach ($cards as $card) {
584
			if ($card['uri'] === $cardUri) {
585
				return $this->generateDavCardFromCard($bookId, $card);
586
			}
587
		}
588
589
		throw new MemberDoesNotExistException();
590
	}
591
592
593
	/**
594
	 * @param int $bookId
595
	 */
596
	private function migrateBook(int $bookId) {
597
		if (in_array($bookId, $this->migratedBooks)) {
598
			return;
599
		}
600
601
		$owner = $this->getOwnerFromAddressBook($bookId);
602
603
		foreach ($this->cardDavBackend->getCards($bookId) as $card) {
604
			$davCard = new DavCard();
605
			$davCard->setOwner($owner);
606
			$davCard->importFromDav($card['carddata']);
607
			$davCard->setAddressBookId($bookId);
608
			$davCard->setCardUri($card['uri']);
609
610
			$this->manageDavCard($davCard);
611
		}
612
613
		$this->migratedBooks[] = $bookId;
614
	}
615
616
617
}
618
619
620