Completed
Pull Request — master (#347)
by Maxence
01:39
created

DavService::manageDeprecatedCircles()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.5226
c 0
b 0
f 0
cc 7
nc 6
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
			}
314
		}
315
	}
316
317
318
	/**
319
	 * @param DavCard $davCard
320
	 *
321
	 * @return int
322
	 */
323
	private function getMemberType(DavCard $davCard): int {
324
		foreach ($davCard->getEmails() as $email) {
325
			try {
326
				$davCard->setUserId($this->isLocalMember($email));
327
328
				return DavCard::TYPE_LOCAL;
329
			} catch (NotLocalMemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
330
			}
331
		}
332
333
		$davCard->setUserId($davCard->getOwner() . ':' . $davCard->getContactId());
334
335
		return DavCard::TYPE_CONTACT;
336
	}
337
338
339
	/**
340
	 * @param string $email
341
	 *
342
	 * @return string
343
	 * @throws NotLocalMemberException
344
	 */
345
	private function isLocalMember(string $email): string {
346
		if (strpos($email, '@') === false) {
347
			throw new NotLocalMemberException();
348
		}
349
350
		list ($username, $domain) = explode('@', $email);
351
		if (in_array($domain, $this->configService->getAvailableHosts())) {
352
			$user = $this->userManager->get($username);
353
			if ($user !== null) {
354
				return $user->getUID();
355
			}
356
		}
357
358
		throw new NotLocalMemberException();
359
	}
360
361
362
	/**
363
	 * @param DavCard $davCard
364
	 */
365
	private function manageCircles(DavCard $davCard) {
366
		$fromCard = $davCard->getGroups();
367
		$current = array_map(
368
			function(Circle $circle) {
369
				return $circle->getContactGroupName();
370
			}, $this->getCirclesFromBook($davCard->getAddressBookId())
371
		);
372
373
		$this->manageNewCircles($davCard, $fromCard, $current);
374
		$this->manageDeprecatedCircles($davCard->getAddressBookId());
375
376
		$this->assignCirclesToCard($davCard);
377
	}
378
379
380
	/**
381
	 * @param DavCard $davCard
382
	 * @param array $fromCard
383
	 * @param array $current
384
	 */
385
	private function manageNewCircles(DavCard $davCard, array $fromCard, array $current) {
386
		foreach ($fromCard as $group) {
387
			if (in_array($group, $current)) {
388
				continue;
389
			}
390
391
			$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...
392
			$circle->setContactAddressBook($davCard->getAddressBookId());
393
			$circle->setContactGroupName($group);
394
395
			try {
396
				$this->circlesRequest->createCircle($circle, $davCard->getOwner());
397
				$member = new Member($davCard->getOwner(), Member::TYPE_USER, $circle->getUniqueId());
398
				$member->setLevel(Member::LEVEL_OWNER);
399
				$member->setStatus(Member::STATUS_MEMBER);
400
401
				try {
402
					$this->membersRequest->createMember($member);
403
				} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
404
				}
405
			} catch (CircleAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
406
			}
407
408
		}
409
	}
410
411
412
	/**
413
	 * @param DavCard $davCard
414
	 */
415
	private function assignCirclesToCard(DavCard $davCard) {
416
		foreach ($davCard->getGroups() as $group) {
417
			try {
418
				$davCard->addCircle(
419
					$this->circlesRequest->getFromContactGroup($davCard->getAddressBookId(), $group)
420
				);
421
			} catch (CircleDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
422
			}
423
		}
424
	}
425
426
427
	/**
428
	 * @param int $addressBookId
429
	 *
430
	 * @return Circle[]
431
	 */
432
	private function getCirclesFromBook(int $addressBookId): array {
433
		return $this->circlesRequest->getFromBook($addressBookId);
434
	}
435
436
437
	/**
438
	 * @param int $length
439
	 *
440
	 * @return string
441
	 * @deprecated
442
	 */
443
	protected function uuid(int $length = 0): string {
444
		$uuid = sprintf(
445
			'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff),
446
			mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
447
			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
448
		);
449
450
		if ($length > 0) {
451
			if ($length <= 16) {
452
				$uuid = str_replace('-', '', $uuid);
453
			}
454
455
			$uuid = substr($uuid, 0, $length);
456
		}
457
458
		return $uuid;
459
	}
460
461
462
	/**
463
	 * @param int $bookId
464
	 *
465
	 * @return string
466
	 */
467
	private function getOwnerFromAddressBook(int $bookId): string {
468
		$data = $this->cardDavBackend->getAddressBookById($bookId);
469
470
		// let's assume the format is principals/users/OWNER
471
		$owner = substr($data['principaluri'], 17);
472
473
		return $owner;
474
	}
475
476
477
	/**
478
	 *
479
	 * @throws Exception
480
	 */
481
	public function migration() {
482
		if ($this->configService->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '1') {
483
			throw new Exception('Circles needs to be set as Contacts App Backend first');
484
		}
485
486
		$this->manageDeprecatedContacts();
487
		$this->manageDeprecatedCircles();
488
		$users = $this->userManager->search('');
489
		foreach ($users as $user) {
490
			$books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID());
491
			foreach ($books as $book) {
492
				$this->migrateBook($book['id']);
493
			}
494
		}
495
	}
496
497
498
	/**
499
	 * @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...
500
	 */
501
	private function manageDeprecatedContacts() {
502
		$contacts = $this->membersRequest->getMembersByContactId();
503
504
		foreach ($contacts as $contact) {
505
			try {
506
				$this->findContactInBook($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 array
576
	 * @throws MemberDoesNotExistException
577
	 */
578
	private function findContactInBook(Member $contact): array {
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 $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