Completed
Pull Request — master (#347)
by Maxence
02:20
created

DavService::onDeleteCard()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
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
	}
164
165
166
	/**
167
	 * @param GenericEvent $event
168
	 * @param bool $tiny
169
	 *
170
	 * @return DavCard
171
	 */
172
	private function generateDavCard(GenericEvent $event, bool $tiny = false): DavCard {
173
		$addressBookId = $event->getArgument('addressBookId');
174
		$cardUri = $event->getArgument('cardUri');
175
176
		$davCard = new DavCard();
177
		$davCard->setAddressBookId($addressBookId);
178
		$davCard->setCardUri($cardUri);
179
180
		if ($tiny) {
181
			return $davCard;
182
		}
183
184
		$cardData = $event->getArgument('cardData');
185
		$davCard->setOwner($this->getOwnerFromAddressBook($addressBookId));
186
		$davCard->importFromDav($cardData);
187
188
		return $davCard;
189
	}
190
191
192
	/**
193
	 * @param int $bookId
194
	 * @param array $card
195
	 *
196
	 * @return DavCard
197
	 */
198
	private function generateDavCardFromCard(int $bookId, array $card): DavCard {
199
		$davCard = new DavCard();
200
		$davCard->setAddressBookId($bookId);
201
		$davCard->setCardUri($card['uri']);
202
203
		$davCard->setOwner($this->getOwnerFromAddressBook($bookId));
204
		$davCard->importFromDav($card['carddata']);
205
206
		return $davCard;
207
	}
208
209
210
	/**
211
	 * @param DavCard $davCard
212
	 *
213
	 */
214
	private function manageDavCard(DavCard $davCard) {
215
		$this->miscService->log('Updating Card: ' . json_encode($davCard), 1);
216
		$this->manageCircles($davCard);
217
		$this->manageContact($davCard);
218
	}
219
220
221
	/**
222
	 * @param DavCard $davCard
223
	 */
224
	private function manageContact(DavCard $davCard) {
225
		$this->manageDeprecatedMembers($davCard);
226
227
		switch ($this->getMemberType($davCard)) {
228
			case DavCard::TYPE_CONTACT:
229
				$this->manageRemoteContact($davCard);
230
				break;
231
232
			case DavCard::TYPE_LOCAL:
233
				$this->manageLocalContact($davCard);
234
				break;
235
236
//			case DavCard::TYPE_FEDERATED:
237
//				$this->manageFederatedContact($davCard);
238
//				break;
239
		}
240
	}
241
242
243
	/**
244
	 * @param DavCard $davCard
245
	 */
246
	private function manageDeprecatedMembers(DavCard $davCard) {
247
		// TODO: Check this.
248
		$circles = array_map(
249
			function(Circle $circle) {
250
				return $circle->getUniqueId();
251
			}, $davCard->getCircles()
252
		);
253
254
		$members = $this->membersRequest->getMembersByContactId($davCard->getUniqueId());
255
		foreach ($members as $member) {
256
			if (!in_array($member->getCircleId(), $circles)) {
257
				$this->membersRequest->removeMember($member);
258
			}
259
		}
260
	}
261
262
263
	/**
264
	 * @param DavCard $davCard
265
	 */
266
	private function manageLocalContact(DavCard $davCard) {
267
		foreach ($davCard->getCircles() as $circle) {
268
			$this->manageMember($circle, $davCard, Member::TYPE_USER);
269
		}
270
	}
271
272
273
	/**
274
	 * @param DavCard $davCard
275
	 */
276
	private function manageRemoteContact(DavCard $davCard) {
277
		foreach ($davCard->getCircles() as $circle) {
278
			$this->manageMember($circle, $davCard, Member::TYPE_CONTACT);
279
		}
280
	}
281
282
283
	/**
284
	 * @param Circle $circle
285
	 * @param DavCard $davCard
286
	 * @param int $type
287
	 */
288
	private function manageMember(Circle $circle, DavCard $davCard, int $type) {
289
		try {
290
			$member =
291
				$this->membersRequest->getContactMember($circle->getUniqueId(), $davCard->getUniqueId());
292
293
			if ($member->getType() !== $type) {
294
				$this->membersRequest->removeMember($member);
295
				throw new MemberDoesNotExistException();
296
			}
297
		} catch (MemberDoesNotExistException $e) {
298
			$member = new Member();
299
			$member->setLevel(Member::LEVEL_MEMBER);
300
			$member->setStatus(Member::STATUS_MEMBER);
301
			$member->setContactId($davCard->getUniqueId());
302
			$member->setType($type);
303
			$member->setCircleId($circle->getUniqueId());
304
			$member->setUserId($davCard->getUserId());
305
306
			try {
307
				$this->membersRequest->createMember($member);
308
309
//				if ($type === Member::TYPE_CONTACT) {
310
//					$this->fileSharingBroadcaster->sendMailAboutExistingShares($circle, $member);
311
//				}
312
			} catch (MemberAlreadyExistsException $e) {
313
				$this->membersRequest->checkMember($member, false);
314
			}
315
		}
316
	}
317
318
319
	/**
320
	 * @param DavCard $davCard
321
	 *
322
	 * @return int
323
	 */
324
	private function getMemberType(DavCard $davCard): int {
325
		foreach ($davCard->getEmails() as $email) {
326
			try {
327
				$davCard->setUserId($this->isLocalMember($email));
328
329
				return DavCard::TYPE_LOCAL;
330
			} catch (NotLocalMemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
331
			}
332
		}
333
334
		$davCard->setUserId($davCard->getOwner() . ':' . $davCard->getContactId());
335
336
		return DavCard::TYPE_CONTACT;
337
	}
338
339
340
	/**
341
	 * @param string $email
342
	 *
343
	 * @return string
344
	 * @throws NotLocalMemberException
345
	 */
346
	private function isLocalMember(string $email): string {
347
		if (strpos($email, '@') === false) {
348
			throw new NotLocalMemberException();
349
		}
350
351
		list ($username, $domain) = explode('@', $email);
352
		if (in_array($domain, $this->configService->getAvailableHosts())) {
353
			$user = $this->userManager->get($username);
354
			if ($user !== null) {
355
				return $user->getUID();
356
			}
357
		}
358
359
		throw new NotLocalMemberException();
360
	}
361
362
363
	/**
364
	 * @param DavCard $davCard
365
	 */
366
	private function manageCircles(DavCard $davCard) {
367
		$fromCard = $davCard->getGroups();
368
		$current = array_map(
369
			function(Circle $circle) {
370
				return $circle->getContactGroupName();
371
			}, $this->getCirclesFromBook($davCard->getAddressBookId())
372
		);
373
374
		$this->manageNewCircles($davCard, $fromCard, $current);
375
		$this->manageDeprecatedCircles($davCard->getAddressBookId());
376
377
		$this->assignCirclesToCard($davCard);
378
	}
379
380
381
	/**
382
	 * @param DavCard $davCard
383
	 * @param array $fromCard
384
	 * @param array $current
385
	 */
386
	private function manageNewCircles(DavCard $davCard, array $fromCard, array $current) {
387
		foreach ($fromCard as $group) {
388
			if (in_array($group, $current)) {
389
				continue;
390
			}
391
392
			$circle = new Circle(Circle::CIRCLES_PUBLIC, $group . ' - ' . $this->uuid(5));
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Service\DavService::uuid() has been deprecated.

This method has been deprecated.

Loading history...
393
			$circle->setContactAddressBook($davCard->getAddressBookId());
394
			$circle->setContactGroupName($group);
395
396
			try {
397
				$this->circlesRequest->createCircle($circle, $davCard->getOwner());
398
				$member = new Member($davCard->getOwner(), Member::TYPE_USER, $circle->getUniqueId());
399
				$member->setLevel(Member::LEVEL_OWNER);
400
				$member->setStatus(Member::STATUS_MEMBER);
401
402
				try {
403
					$this->membersRequest->createMember($member);
404
				} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
405
				}
406
			} catch (CircleAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
407
			}
408
409
		}
410
	}
411
412
413
	/**
414
	 * @param DavCard $davCard
415
	 */
416
	private function assignCirclesToCard(DavCard $davCard) {
417
		foreach ($davCard->getGroups() as $group) {
418
			try {
419
				$davCard->addCircle(
420
					$this->circlesRequest->getFromContactGroup($davCard->getAddressBookId(), $group)
421
				);
422
			} catch (CircleDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
423
			}
424
		}
425
	}
426
427
428
	/**
429
	 * @param int $addressBookId
430
	 *
431
	 * @return Circle[]
432
	 */
433
	private function getCirclesFromBook(int $addressBookId): array {
434
		return $this->circlesRequest->getFromBook($addressBookId);
435
	}
436
437
438
	/**
439
	 * @param int $length
440
	 *
441
	 * @return string
442
	 * @deprecated
443
	 */
444
	protected function uuid(int $length = 0): string {
445
		$uuid = sprintf(
446
			'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff),
447
			mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
448
			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
449
		);
450
451
		if ($length > 0) {
452
			if ($length <= 16) {
453
				$uuid = str_replace('-', '', $uuid);
454
			}
455
456
			$uuid = substr($uuid, 0, $length);
457
		}
458
459
		return $uuid;
460
	}
461
462
463
	/**
464
	 * @param int $bookId
465
	 *
466
	 * @return string
467
	 */
468
	private function getOwnerFromAddressBook(int $bookId): string {
469
		$data = $this->cardDavBackend->getAddressBookById($bookId);
470
471
		// let's assume the format is principals/users/OWNER
472
		$owner = substr($data['principaluri'], 17);
473
474
		return $owner;
475
	}
476
477
478
	/**
479
	 *
480
	 * @throws Exception
481
	 */
482
	public function migration() {
483
		if ($this->configService->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '1') {
484
			throw new Exception('Circles needs to be set as Contacts App Backend first');
485
		}
486
487
		$this->manageDeprecatedContacts();
488
		$this->manageDeprecatedCircles();
489
		$users = $this->userManager->search('');
490
		foreach ($users as $user) {
491
			$books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID());
492
			foreach ($books as $book) {
493
				$this->migrateBook($book['id']);
494
			}
495
		}
496
	}
497
498
499
	/**
500
	 */
501
	private function manageDeprecatedContacts() {
502
		$contacts = $this->membersRequest->getMembersByContactId();
503
504
		foreach ($contacts as $contact) {
505
			try {
506
				$this->getDavCardFromMember($contact);
507
			} catch (MemberDoesNotExistException $e) {
508
				$this->membersRequest->removeMember($contact);
509
			}
510
		}
511
	}
512
513
514
	/**
515
	 * @param int $bookId
516
	 */
517
	private function manageDeprecatedCircles(int $bookId = 0) {
518
		$knownBooks = [$bookId];
519
		if ($bookId > 0) {
520
			$knownBooks = [];
521
			$contacts = $this->membersRequest->getMembersByContactId();
522
			foreach ($contacts as $contact) {
523
				list($bookId,) = explode('/', $contact->getContactId(), 2);
524
				if (in_array($bookId, $knownBooks)) {
525
					continue;
526
				}
527
528
				$knownBooks[] = $bookId;
529
			}
530
		}
531
532
		foreach ($knownBooks as $bookId) {
533
			$circles = $this->circlesRequest->getFromContactBook($bookId);
534
			$fromBook = $this->getExistingCirclesFromBook($bookId);
535
536
			foreach ($circles as $circle) {
537
				if (in_array($circle->getContactGroupName(), $fromBook)) {
538
					continue;
539
				}
540
541
				$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
542
				$this->circlesRequest->destroyCircle($circle->getUniqueId());
543
			}
544
		}
545
	}
546
547
548
	/**
549
	 * @param int $bookId
550
	 *
551
	 * @return Circle[]
552
	 */
553
	private function getExistingCirclesFromBook(int $bookId): array {
554
		$circles = [];
555
		$cards = $this->cardDavBackend->getCards($bookId);
556
		foreach ($cards as $card) {
557
			$davCard = $this->generateDavCardFromCard($bookId, $card);
558
			$this->assignCirclesToCard($davCard);
559
			$circles = array_merge($circles, $davCard->getCircles());
560
		}
561
562
		$existing = array_map(
563
			function(Circle $circle) {
564
				return $circle->getContactGroupName();
565
			}, $circles
566
		);
567
568
		return array_unique($existing);
569
	}
570
571
572
	/**
573
	 * @param Member $contact
574
	 *
575
	 * @return DavCard
576
	 * @throws MemberDoesNotExistException
577
	 */
578
	public function getDavCardFromMember(Member $contact): DavCard {
579
		list($bookId, $cardUri) = explode('/', $contact->getContactId(), 2);
580
		$cards = $this->cardDavBackend->getCards($bookId);
581
		foreach ($cards as $card) {
582
			if ($card['uri'] === $cardUri) {
583
				return $this->generateDavCardFromCard($bookId, $card);
584
			}
585
		}
586
587
		throw new MemberDoesNotExistException();
588
	}
589
590
591
	/**
592
	 * @param int $bookId
593
	 */
594
	private function migrateBook(int $bookId) {
595
		if (in_array($bookId, $this->migratedBooks)) {
596
			return;
597
		}
598
599
		$owner = $this->getOwnerFromAddressBook($bookId);
600
601
		foreach ($this->cardDavBackend->getCards($bookId) as $card) {
602
			$davCard = new DavCard();
603
			$davCard->setOwner($owner);
604
			$davCard->importFromDav($card['carddata']);
605
			$davCard->setAddressBookId($bookId);
606
			$davCard->setCardUri($card['uri']);
607
608
			$this->manageDavCard($davCard);
609
		}
610
611
		$this->migratedBooks[] = $bookId;
612
	}
613
614
615
}
616
617
618