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

DavService::generateDavCardFromCard()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 2
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\Db\CirclesRequest;
35
use OCA\Circles\Db\MembersRequest;
36
use OCA\Circles\Exceptions\CircleAlreadyExistsException;
37
use OCA\Circles\Exceptions\CircleDoesNotExistException;
38
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
39
use OCA\Circles\Exceptions\MemberDoesNotExistException;
40
use OCA\Circles\Exceptions\NotLocalMemberException;
41
use OCA\Circles\Model\Circle;
42
use OCA\Circles\Model\DavCard;
43
use OCA\Circles\Model\Member;
44
use OCA\DAV\CardDAV\CardDavBackend;
45
use OCP\App\ManagerEvent;
46
use OCP\Federation\ICloudIdManager;
47
use OCP\IUserManager;
48
use Symfony\Component\EventDispatcher\GenericEvent;
49
50
51
/**
52
 * Class DavService
53
 *
54
 * @package OCA\Circles\Service
55
 */
56
class DavService {
57
58
59
	/** @var string */
60
	private $userId;
61
62
	/** @var IUserManager */
63
	private $userManager;
64
65
	/** @var CardDavBackend */
66
	private $cardDavBackend;
67
68
	/** @var ICloudIdManager */
69
	private $cloudManager;
70
71
	/** @var CirclesRequest */
72
	private $circlesRequest;
73
74
	/** @var MembersRequest */
75
	private $membersRequest;
76
77
	/** @var ConfigService */
78
	private $configService;
79
80
	/** @var MiscService */
81
	private $miscService;
82
83
84
	/** @var array */
85
	private $migratedBooks = [];
86
87
88
	/**
89
	 * TimezoneService constructor.
90
	 *
91
	 * @param string $userId
92
	 * @param IUserManager $userManager
93
	 * @param CardDavBackend $cardDavBackend
94
	 * @param ICloudIdManager $cloudManager
95
	 * @param CirclesRequest $circlesRequest
96
	 * @param MembersRequest $membersRequest
97
	 * @param ConfigService $configService
98
	 * @param MiscService $miscService
99
	 */
100 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...
101
		$userId, IUserManager $userManager, CardDavBackend $cardDavBackend, ICloudIdManager $cloudManager,
102
		CirclesRequest $circlesRequest, MembersRequest $membersRequest, ConfigService $configService,
103
		MiscService $miscService
104
	) {
105
		$this->userId = $userId;
106
		$this->userManager = $userManager;
107
		$this->cardDavBackend = $cardDavBackend;
108
		$this->cloudManager = $cloudManager;
109
		$this->circlesRequest = $circlesRequest;
110
		$this->membersRequest = $membersRequest;
111
		$this->configService = $configService;
112
		$this->miscService = $miscService;
113
	}
114
115
116
	/**
117
	 * @param ManagerEvent $event
118
	 */
119
	public function onAppEnabled(ManagerEvent $event) {
120
		if ($event->getAppID() !== 'circles') {
121
			return;
122
		}
123
124
		try {
125
			$this->migration();
126
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
127
		}
128
	}
129
130
131
	/**
132
	 * @param GenericEvent $event
133
	 */
134
	public function onCreateCard(GenericEvent $event) {
135
		$davCard = $this->generateDavCard($event);
136
		$this->manageDavCard($davCard);
137
	}
138
139
140
	/**
141
	 * @param GenericEvent $event
142
	 */
143
	public function onUpdateCard(GenericEvent $event) {
144
		$davCard = $this->generateDavCard($event);
145
		$this->manageDavCard($davCard);
146
	}
147
148
149
	/**
150
	 * @param GenericEvent $event
151
	 */
152
	public function onDeleteCard(GenericEvent $event) {
153
		$davCard = $this->generateDavCard($event, true);
154
155
		$this->miscService->log('Deleting Card: ' . json_encode($davCard), 1);
156
		$this->membersRequest->removeMembersByContactId($davCard->getUniqueId(), Member::TYPE_USER);
157
	}
158
159
160
	/**
161
	 * @param GenericEvent $event
162
	 * @param bool $tiny
163
	 *
164
	 * @return DavCard
165
	 */
166
	private function generateDavCard(GenericEvent $event, bool $tiny = false): DavCard {
167
		$addressBookId = $event->getArgument('addressBookId');
168
		$cardUri = $event->getArgument('cardUri');
169
170
		$davCard = new DavCard();
171
		$davCard->setAddressBookId($addressBookId);
172
		$davCard->setCardUri($cardUri);
173
174
		if ($tiny) {
175
			return $davCard;
176
		}
177
178
		$cardData = $event->getArgument('cardData');
179
		$davCard->setOwner($this->getOwnerFromAddressBook($addressBookId));
180
		$davCard->importFromDav($cardData);
181
182
		return $davCard;
183
	}
184
185
186
	/**
187
	 * @param int $bookId
188
	 * @param array $card
189
	 *
190
	 * @return DavCard
191
	 */
192
	private function generateDavCardFromCard(int $bookId, array $card): DavCard {
193
		$davCard = new DavCard();
194
		$davCard->setAddressBookId($bookId);
195
		$davCard->setCardUri($card['uri']);
196
197
		$davCard->setOwner($this->getOwnerFromAddressBook($bookId));
198
		$davCard->importFromDav($card['carddata']);
199
200
		return $davCard;
201
	}
202
203
204
	/**
205
	 * @param DavCard $davCard
206
	 *
207
	 */
208
	private function manageDavCard(DavCard $davCard) {
209
		$this->miscService->log('Updating Card: ' . json_encode($davCard), 1);
210
		$this->manageCircles($davCard);
211
		$this->manageContact($davCard);
212
	}
213
214
215
	/**
216
	 * @param DavCard $davCard
217
	 */
218
	private function manageContact(DavCard $davCard) {
219
		$this->manageDeprecatedMembers($davCard);
220
221
		switch ($this->getMemberType($davCard)) {
222
			case DavCard::TYPE_CONTACT:
223
				$this->manageRemoteContact($davCard);
224
				break;
225
226
			case DavCard::TYPE_LOCAL:
227
				$this->manageLocalContact($davCard);
228
				break;
229
230
//			case DavCard::TYPE_FEDERATED:
231
//				$this->manageFederatedContact($davCard);
232
//				break;
233
		}
234
	}
235
236
237
	/**
238
	 * @param DavCard $davCard
239
	 */
240
	private function manageDeprecatedMembers(DavCard $davCard) {
241
		// TODO: Check this.
242
		$circles = array_map(
243
			function(Circle $circle) {
244
				return $circle->getUniqueId();
245
			}, $davCard->getCircles()
246
		);
247
248
		$members = $this->membersRequest->getMembersByContactId($davCard->getUniqueId());
249
		foreach ($members as $member) {
250
			if (!in_array($member->getCircleId(), $circles)) {
251
				$this->membersRequest->removeMember($member);
252
			}
253
		}
254
	}
255
256
257
	/**
258
	 * @param DavCard $davCard
259
	 */
260
	private function manageLocalContact(DavCard $davCard) {
261
		foreach ($davCard->getCircles() as $circle) {
262
			$this->manageMember($circle, $davCard, Member::TYPE_USER);
263
		}
264
	}
265
266
267
	/**
268
	 * @param DavCard $davCard
269
	 */
270
	private function manageRemoteContact(DavCard $davCard) {
271
		foreach ($davCard->getCircles() as $circle) {
272
			$this->manageMember($circle, $davCard, Member::TYPE_CONTACT);
273
		}
274
	}
275
276
277
	/**
278
	 * @param Circle $circle
279
	 * @param DavCard $davCard
280
	 * @param int $type
281
	 */
282
	private function manageMember(Circle $circle, DavCard $davCard, int $type) {
283
		try {
284
			$member =
285
				$this->membersRequest->getContactMember($circle->getUniqueId(), $davCard->getUniqueId());
286
287
			if ($member->getType() !== $type) {
288
				$this->membersRequest->removeMember($member);
289
				throw new MemberDoesNotExistException();
290
			}
291
		} catch (MemberDoesNotExistException $e) {
292
			$member = new Member();
293
			$member->setLevel(Member::LEVEL_MEMBER);
294
			$member->setStatus(Member::STATUS_MEMBER);
295
			$member->setContactId($davCard->getUniqueId());
296
			$member->setType($type);
297
			$member->setCircleId($circle->getUniqueId());
298
			$member->setUserId($davCard->getUserId());
299
300
			try {
301
				$this->membersRequest->createMember($member);
302
			} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
303
			}
304
		}
305
	}
306
307
308
	/**
309
	 * @param DavCard $davCard
310
	 *
311
	 * @return int
312
	 */
313
	private function getMemberType(DavCard $davCard): int {
314
		foreach ($davCard->getEmails() as $email) {
315
			try {
316
				$davCard->setUserId($this->isLocalMember($email));
317
318
				return DavCard::TYPE_LOCAL;
319
			} catch (NotLocalMemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
320
			}
321
		}
322
323
		$davCard->setUserId($davCard->getOwner() . ':' . $davCard->getContactId());
324
325
		return DavCard::TYPE_CONTACT;
326
	}
327
328
329
	/**
330
	 * @param string $email
331
	 *
332
	 * @return string
333
	 * @throws NotLocalMemberException
334
	 */
335
	private function isLocalMember(string $email): string {
336
		if (strpos($email, '@') === false) {
337
			throw new NotLocalMemberException();
338
		}
339
340
		list ($username, $domain) = explode('@', $email);
341
		if (in_array($domain, $this->configService->getAvailableHosts())) {
342
			$user = $this->userManager->get($username);
343
			if ($user !== null) {
344
				return $user->getUID();
345
			}
346
		}
347
348
		throw new NotLocalMemberException();
349
	}
350
351
352
	/**
353
	 * @param DavCard $davCard
354
	 */
355
	private function manageCircles(DavCard $davCard) {
356
		$fromCard = $davCard->getGroups();
357
		$current = array_map(
358
			function(Circle $circle) {
359
				return $circle->getContactGroupName();
360
			}, $this->getCirclesFromBook($davCard->getAddressBookId())
361
		);
362
363
		$this->manageNewCircles($davCard, $fromCard, $current);
364
		$this->manageDeprecatedCircles($davCard->getAddressBookId());
365
366
		$this->assignCirclesToCard($davCard);
367
	}
368
369
370
	/**
371
	 * @param DavCard $davCard
372
	 * @param array $fromCard
373
	 * @param array $current
374
	 */
375
	private function manageNewCircles(DavCard $davCard, array $fromCard, array $current) {
376
		foreach ($fromCard as $group) {
377
			if (in_array($group, $current)) {
378
				continue;
379
			}
380
381
			$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...
382
			$circle->setContactAddressBook($davCard->getAddressBookId());
383
			$circle->setContactGroupName($group);
384
385
			try {
386
				$this->circlesRequest->createCircle($circle, $davCard->getOwner());
387
				$member = new Member($davCard->getOwner(), Member::TYPE_USER, $circle->getUniqueId());
388
				$member->setLevel(Member::LEVEL_OWNER);
389
				$member->setStatus(Member::STATUS_MEMBER);
390
391
				try {
392
					$this->membersRequest->createMember($member);
393
				} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
394
				}
395
			} catch (CircleAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
396
			}
397
398
		}
399
	}
400
401
402
	/**
403
	 * @param DavCard $davCard
404
	 */
405
	private function assignCirclesToCard(DavCard $davCard) {
406
		foreach ($davCard->getGroups() as $group) {
407
			try {
408
				$davCard->addCircle(
409
					$this->circlesRequest->getFromContactGroup($davCard->getAddressBookId(), $group)
410
				);
411
			} catch (CircleDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
412
			}
413
		}
414
	}
415
416
417
	/**
418
	 * @param int $addressBookId
419
	 *
420
	 * @return Circle[]
421
	 */
422
	private function getCirclesFromBook(int $addressBookId): array {
423
		return $this->circlesRequest->getFromBook($addressBookId);
424
	}
425
426
427
	/**
428
	 * @param int $length
429
	 *
430
	 * @return string
431
	 * @deprecated
432
	 */
433
	protected function uuid(int $length = 0): string {
434
		$uuid = sprintf(
435
			'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff),
436
			mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
437
			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
438
		);
439
440
		if ($length > 0) {
441
			if ($length <= 16) {
442
				$uuid = str_replace('-', '', $uuid);
443
			}
444
445
			$uuid = substr($uuid, 0, $length);
446
		}
447
448
		return $uuid;
449
	}
450
451
452
	/**
453
	 * @param int $bookId
454
	 *
455
	 * @return string
456
	 */
457
	private function getOwnerFromAddressBook(int $bookId): string {
458
		$data = $this->cardDavBackend->getAddressBookById($bookId);
459
460
		// let's assume the format is principals/users/OWNER
461
		$owner = substr($data['principaluri'], 17);
462
463
		return $owner;
464
	}
465
466
467
	/**
468
	 *
469
	 * @throws Exception
470
	 */
471
	public function migration() {
472
		if ($this->configService->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '1') {
473
			throw new Exception('Circles needs to be set as Contacts App Backend first');
474
		}
475
476
		$this->manageDeprecatedContacts();
477
		$this->manageDeprecatedCircles();
478
		$users = $this->userManager->search('');
479
		foreach ($users as $user) {
480
			$books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID());
481
			foreach ($books as $book) {
482
				$this->migrateBook($book['id']);
483
			}
484
		}
485
	}
486
487
488
	/**
489
	 * @param DavCard $davCard
0 ignored issues
show
Bug introduced by
There is no parameter named $davCard. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
490
	 */
491
	private function manageDeprecatedContacts() {
492
		$contacts = $this->membersRequest->getMembersByContactId();
493
494
		foreach ($contacts as $contact) {
495
			try {
496
				$this->findContactInBook($contact);
497
			} catch (MemberDoesNotExistException $e) {
498
				$this->membersRequest->removeMember($contact);
499
			}
500
		}
501
	}
502
503
504
	/**
505
	 * @param int $bookId
506
	 */
507
	private function manageDeprecatedCircles(int $bookId = 0) {
508
		$knownBooks = [$bookId];
509
		if ($bookId > 0) {
510
			$knownBooks = [];
511
			$contacts = $this->membersRequest->getMembersByContactId();
512
			foreach ($contacts as $contact) {
513
				list($bookId,) = explode('/', $contact->getContactId(), 2);
514
				if (in_array($bookId, $knownBooks)) {
515
					continue;
516
				}
517
518
				$knownBooks[] = $bookId;
519
			}
520
		}
521
522
		foreach ($knownBooks as $bookId) {
523
			$circles = $this->circlesRequest->getFromContactBook($bookId);
524
			$fromBook = $this->getExistingCirclesFromBook($bookId);
525
526
			foreach ($circles as $circle) {
527
				if (in_array($circle->getContactGroupName(), $fromBook)) {
528
					continue;
529
				}
530
531
				$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
532
				$this->circlesRequest->destroyCircle($circle->getUniqueId());
533
			}
534
		}
535
	}
536
537
538
	/**
539
	 * @param int $bookId
540
	 *
541
	 * @return Circle[]
542
	 */
543
	private function getExistingCirclesFromBook(int $bookId): array {
544
		$circles = [];
545
		$cards = $this->cardDavBackend->getCards($bookId);
546
		foreach ($cards as $card) {
547
			$davCard = $this->generateDavCardFromCard($bookId, $card);
548
			$this->assignCirclesToCard($davCard);
549
			$circles = array_merge($circles, $davCard->getCircles());
550
		}
551
552
		$existing = array_map(
553
			function(Circle $circle) {
554
				return $circle->getContactGroupName();
555
			}, $circles
556
		);
557
558
		return array_unique($existing);
559
	}
560
561
562
	/**
563
	 * @param Member $contact
564
	 *
565
	 * @return array
566
	 * @throws MemberDoesNotExistException
567
	 */
568
	private function findContactInBook(Member $contact): array {
569
		list($bookId, $cardUri) = explode('/', $contact->getContactId(), 2);
570
		$cards = $this->cardDavBackend->getCards($bookId);
571
		foreach ($cards as $card) {
572
			if ($card['uri'] === $cardUri) {
573
				return $card;
574
			}
575
		}
576
577
		throw new MemberDoesNotExistException();
578
	}
579
580
581
	/**
582
	 * @param int $bookId
583
	 */
584
	private function migrateBook(int $bookId) {
585
		if (in_array($bookId, $this->migratedBooks)) {
586
			return;
587
		}
588
589
		$owner = $this->getOwnerFromAddressBook($bookId);
590
591
		foreach ($this->cardDavBackend->getCards($bookId) as $card) {
592
			$davCard = new DavCard();
593
			$davCard->setOwner($owner);
594
			$davCard->importFromDav($card['carddata']);
595
			$davCard->setAddressBookId($bookId);
596
			$davCard->setCardUri($card['uri']);
597
598
			$this->manageDavCard($davCard);
599
		}
600
601
		$this->migratedBooks[] = $bookId;
602
	}
603
604
605
}
606
607
608