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

DavService::onAppEnabled()   A

Complexity

Conditions 3
Paths 3

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 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
31
32
namespace OCA\Circles\Service;
33
34
35
require_once __DIR__ . '/../../appinfo/autoload.php';
36
37
38
use Exception;
39
use OCA\Circles\Db\CirclesRequest;
40
use OCA\Circles\Db\MembersRequest;
41
use OCA\Circles\Exceptions\CircleAlreadyExistsException;
42
use OCA\Circles\Exceptions\CircleDoesNotExistException;
43
use OCA\Circles\Exceptions\MemberAlreadyExistsException;
44
use OCA\Circles\Exceptions\MemberDoesNotExistException;
45
use OCA\Circles\Exceptions\NotLocalMemberException;
46
use OCA\Circles\Model\Circle;
47
use OCA\Circles\Model\DavCard;
48
use OCA\Circles\Model\Member;
49
use OCA\DAV\CardDAV\CardDavBackend;
50
use OCP\App\ManagerEvent;
51
use OCP\Federation\ICloudIdManager;
52
use OCP\IUserManager;
53
use Symfony\Component\EventDispatcher\GenericEvent;
54
55
56
/**
57
 * Class DavService
58
 *
59
 * @package OCA\Circles\Service
60
 */
61
class DavService {
62
63
64
	/** @var string */
65
	private $userId;
66
67
	/** @var IUserManager */
68
	private $userManager;
69
70
	/** @var CardDavBackend */
71
	private $cardDavBackend;
72
73
	/** @var ICloudIdManager */
74
	private $cloudManager;
75
76
	/** @var CirclesRequest */
77
	private $circlesRequest;
78
79
	/** @var MembersRequest */
80
	private $membersRequest;
81
82
	/** @var ConfigService */
83
	private $configService;
84
85
	/** @var MiscService */
86
	private $miscService;
87
88
89
	/** @var array */
90
	private $migratedBooks = [];
91
92
93
	/**
94
	 * TimezoneService constructor.
95
	 *
96
	 * @param string $userId
97
	 * @param IUserManager $userManager
98
	 * @param CardDavBackend $cardDavBackend
99
	 * @param ICloudIdManager $cloudManager
100
	 * @param CirclesRequest $circlesRequest
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
		CirclesRequest $circlesRequest, MembersRequest $membersRequest, ConfigService $configService,
108
		MiscService $miscService
109
	) {
110
		$this->userId = $userId;
111
		$this->userManager = $userManager;
112
		$this->cardDavBackend = $cardDavBackend;
113
		$this->cloudManager = $cloudManager;
114
		$this->circlesRequest = $circlesRequest;
115
		$this->membersRequest = $membersRequest;
116
		$this->configService = $configService;
117
		$this->miscService = $miscService;
118
	}
119
120
121
	/**
122
	 * @param ManagerEvent $event
123
	 */
124
	public function onAppEnabled(ManagerEvent $event) {
125
		if ($event->getAppID() !== 'circles') {
126
			return;
127
		}
128
129
		try {
130
			$this->migration();
131
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
132
		}
133
	}
134
135
136
	/**
137
	 * @param GenericEvent $event
138
	 */
139
	public function onCreateCard(GenericEvent $event) {
140
		$davCard = $this->generateDavCard($event);
141
		$this->manageDavCard($davCard);
142
	}
143
144
145
	/**
146
	 * @param GenericEvent $event
147
	 */
148
	public function onUpdateCard(GenericEvent $event) {
149
		$davCard = $this->generateDavCard($event);
150
		$this->manageDavCard($davCard);
151
	}
152
153
154
	/**
155
	 * @param GenericEvent $event
156
	 */
157
	public function onDeleteCard(GenericEvent $event) {
158
		$addressBookId = $event->getArgument('addressBookId');
0 ignored issues
show
Unused Code introduced by
$addressBookId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
159
		$cardUri = $event->getArgument('cardUri');
0 ignored issues
show
Unused Code introduced by
$cardUri is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
160
	}
161
162
163
	/**
164
	 * @param GenericEvent $event
165
	 *
166
	 * @return DavCard
167
	 */
168
	private function generateDavCard(GenericEvent $event): DavCard {
169
		$addressBookId = $event->getArgument('addressBookId');
170
		$cardUri = $event->getArgument('cardUri');
171
		$cardData = $event->getArgument('cardData');
172
173
		$davCard = new DavCard();
174
		$davCard->setOwner($this->getOwnerFromAddressBook($addressBookId));
175
		$davCard->importFromDav($cardData);
176
		$davCard->setAddressBookId($addressBookId);
177
		$davCard->setCardUri($cardUri);
178
179
		return $davCard;
180
	}
181
182
183
	/**
184
	 * @param DavCard $davCard
185
	 */
186
	private function manageDavCard(DavCard $davCard) {
187
		$this->miscService->log('Updating Card: ' . json_encode($davCard));
188
		$this->manageCircles($davCard);
189
		$this->manageContact($davCard);
190
	}
191
192
193
	/**
194
	 * @param DavCard $davCard
195
	 */
196
	private function manageContact(DavCard $davCard) {
197
		$this->manageDeprecatedMembers($davCard);
198
199
		switch ($this->getMemberType($davCard)) {
200
			case DavCard::TYPE_CONTACT:
201
				$this->manageRemoteContact($davCard);
202
				break;
203
204
			case DavCard::TYPE_LOCAL:
205
				$this->manageLocalContact($davCard);
206
				break;
207
208
//			case DavCard::TYPE_FEDERATED:
209
//				$this->manageFederatedContact($davCard);
210
//				break;
211
		}
212
	}
213
214
215
	/**
216
	 * @param DavCard $davCard
217
	 */
218
	private function manageDeprecatedMembers(DavCard $davCard) {
219
		$circles = array_map(
220
			function(Circle $circle) {
221
				return $circle->getUniqueId();
222
			}, $davCard->getCircles()
223
		);
224
225
		$members = $this->membersRequest->getMembersByContactId($davCard->getContactId());
226
		foreach ($members as $member) {
227
			if (!in_array($member->getCircleId(), $circles)) {
228
				$this->membersRequest->removeMember($member);
229
			}
230
		}
231
	}
232
233
234
	/**
235
	 * @param DavCard $davCard
236
	 */
237
	private function manageLocalContact(DavCard $davCard) {
238
		foreach ($davCard->getCircles() as $circle) {
239
			$this->manageMember($circle, $davCard, Member::TYPE_USER);
240
		}
241
	}
242
243
244
	/**
245
	 * @param DavCard $davCard
246
	 */
247
	private function manageRemoteContact(DavCard $davCard) {
248
		foreach ($davCard->getCircles() as $circle) {
249
			$this->manageMember($circle, $davCard, Member::TYPE_CONTACT);
250
		}
251
	}
252
253
254
	/**
255
	 * @param Circle $circle
256
	 * @param DavCard $davCard
257
	 * @param int $type
258
	 */
259
	private function manageMember(Circle $circle, DavCard $davCard, int $type) {
260
		try {
261
			$member =
262
				$this->membersRequest->getContactMember($circle->getUniqueId(), $davCard->getContactId());
263
264
			if ($member->getType() !== $type) {
265
				$this->membersRequest->removeMember($member);
266
				throw new MemberDoesNotExistException();
267
			}
268
		} catch (MemberDoesNotExistException $e) {
269
			$member = new Member();
270
			$member->setLevel(Member::LEVEL_MEMBER);
271
			$member->setStatus(Member::STATUS_MEMBER);
272
			$member->setContactId($davCard->getContactId());
273
			$member->setType($type);
274
			$member->setCircleId($circle->getUniqueId());
275
			$member->setUserId($davCard->getUserId());
276
277
			try {
278
				$this->membersRequest->createMember($member);
279
			} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
280
			}
281
		}
282
	}
283
284
285
	/**
286
	 * @param DavCard $davCard
287
	 *
288
	 * @return int
289
	 */
290
	private function getMemberType(DavCard $davCard): int {
291
		foreach ($davCard->getEmails() as $email) {
292
			try {
293
				$davCard->setUserId($this->isLocalMember($email));
294
295
				return DavCard::TYPE_LOCAL;
296
			} catch (NotLocalMemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
297
			}
298
		}
299
300
		$davCard->setUserId($davCard->getOwner() . ':' . $davCard->getContactId());
301
302
		return DavCard::TYPE_CONTACT;
303
	}
304
305
306
	/**
307
	 * @param string $email
308
	 *
309
	 * @return string
310
	 * @throws NotLocalMemberException
311
	 */
312
	private function isLocalMember(string $email): string {
313
		if (strpos($email, '@') === false) {
314
			throw new NotLocalMemberException();
315
		}
316
317
		list ($username, $domain) = explode('@', $email);
318
		if (in_array($domain, $this->configService->getAvailableHosts())) {
319
			$user = $this->userManager->get($username);
320
			if ($user !== null) {
321
				return $user->getUID();
322
			}
323
		}
324
325
		throw new NotLocalMemberException();
326
	}
327
328
329
	/**
330
	 * @param DavCard $davCard
331
	 */
332
	private function manageCircles(DavCard $davCard) {
333
		$fromCard = $davCard->getGroups();
334
		$current = array_map(
335
			function(Circle $circle) {
336
				return $circle->getContactGroupName();
337
			}, $this->getCirclesFromBook($davCard->getAddressBookId())
338
		);
339
340
		$this->manageNewCircles($davCard, $fromCard, $current);
341
		$this->manageDeprecatedCircles($fromCard, $current);
0 ignored issues
show
Unused Code introduced by
The call to the method OCA\Circles\Service\DavS...nageDeprecatedCircles() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
342
343
		$this->assignCirclesToCard($davCard);
344
	}
345
346
347
	/**
348
	 * @param DavCard $davCard
349
	 * @param array $fromCard
350
	 * @param array $current
351
	 */
352
	private function manageNewCircles(DavCard $davCard, array $fromCard, array $current) {
353
		foreach ($fromCard as $group) {
354
			if (in_array($group, $current)) {
355
				continue;
356
			}
357
358
			$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...
359
			$circle->setContactAddressBook($davCard->getAddressBookId());
360
			$circle->setContactGroupName($group);
361
362
			try {
363
				$this->circlesRequest->createCircle($circle, $davCard->getOwner());
364
				$member = new Member($davCard->getOwner(), Member::TYPE_USER, $circle->getUniqueId());
365
				$member->setLevel(Member::LEVEL_OWNER);
366
				$member->setStatus(Member::STATUS_MEMBER);
367
368
				try {
369
					$this->membersRequest->createMember($member);
370
				} catch (MemberAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
371
				}
372
			} catch (CircleAlreadyExistsException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
373
			}
374
375
		}
376
	}
377
378
379
	/**
380
	 * // TODO: Get all group from an addressbook
381
	 * // TODO: remove deprecated circles
382
	 *
383
	 * @param array $fromCard
384
	 * @param array $current
385
	 */
386
	private function manageDeprecatedCircles(array $fromCard, array $current) {
0 ignored issues
show
Unused Code introduced by
The parameter $fromCard is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $current is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
387
	}
388
389
390
	/**
391
	 * @param DavCard $davCard
392
	 */
393
	private function assignCirclesToCard(DavCard $davCard) {
394
		foreach ($davCard->getGroups() as $group) {
395
			try {
396
				$davCard->addCircle(
397
					$this->circlesRequest->getFromContactGroup($davCard->getAddressBookId(), $group)
398
				);
399
			} catch (CircleDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
400
			}
401
		}
402
	}
403
404
405
	/**
406
	 * @param int $addressBookId
407
	 *
408
	 * @return Circle[]
409
	 */
410
	private function getCirclesFromBook(int $addressBookId): array {
411
		return $this->circlesRequest->getFromBook($addressBookId);
412
	}
413
414
415
	/**
416
	 * @param int $length
417
	 *
418
	 * @return string
419
	 * @deprecated
420
	 */
421
	protected function uuid(int $length = 0): string {
422
		$uuid = sprintf(
423
			'%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff),
424
			mt_rand(0, 0xffff), mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
425
			mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
426
		);
427
428
		if ($length > 0) {
429
			if ($length <= 16) {
430
				$uuid = str_replace('-', '', $uuid);
431
			}
432
433
			$uuid = substr($uuid, 0, $length);
434
		}
435
436
		return $uuid;
437
	}
438
439
440
	/**
441
	 * @param int $bookId
442
	 *
443
	 * @return string
444
	 */
445
	private function getOwnerFromAddressBook(int $bookId): string {
446
		$data = $this->cardDavBackend->getAddressBookById($bookId);
447
448
		// let's assume the format is principals/users/OWNER
449
		$owner = substr($data['principaluri'], 17);
450
451
		return $owner;
452
	}
453
454
455
	/**
456
	 *
457
	 * @throws Exception
458
	 */
459
	public function migration() {
460
		if ($this->configService->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '1') {
461
			throw new Exception('Circles needs to be set as Contacts App Backend first');
462
		}
463
464
465
		$users = $this->userManager->search('');
466
		foreach ($users as $user) {
467
			$books = $this->cardDavBackend->getAddressBooksForUser('principals/users/' . $user->getUID());
468
			foreach ($books as $book) {
469
				$this->migrateBook($book['id']);
470
			}
471
		}
472
	}
473
474
475
	/**
476
	 * @param int $bookId
477
	 */
478
	private function migrateBook(int $bookId) {
479
		if (in_array($bookId, $this->migratedBooks)) {
480
			return;
481
		}
482
483
		$owner = $this->getOwnerFromAddressBook($bookId);
484
485
		foreach ($this->cardDavBackend->getCards($bookId) as $card) {
486
			$davCard = new DavCard();
487
			$davCard->setOwner($owner);
488
			$davCard->importFromDav($card['carddata']);
489
			$davCard->setAddressBookId($bookId);
490
			$davCard->setCardUri($card['uri']);
491
492
			$this->manageDavCard($davCard);
493
		}
494
495
		$this->migratedBooks[] = $bookId;
496
	}
497
498
499
}
500
501
502