Passed
Push — master ( 33c34d...f0c85a )
by Morris
09:38
created

CardDavBackend   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 1113
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 494
dl 0
loc 1113
rs 2
c 0
b 0
f 0
wmc 92

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A getAddressBooksForUserCount() 0 8 1
A updateShares() 0 2 1
A applyShareAcl() 0 2 1
A purgeProperties() 0 6 1
A updateCard() 0 25 1
A addChange() 0 12 1
A addOwnerPrincipal() 0 12 3
A convertPrincipal() 0 9 3
A deleteAddressBook() 0 22 1
A getAddressBookById() 0 26 3
A getUserDisplayName() 0 12 3
A getCardId() 0 15 2
A getContact() 0 15 2
A getCardUri() 0 15 2
A getAddressBooksByUri() 0 28 3
B updateProperties() 0 33 6
A readCard() 0 2 1
A updateAddressBook() 0 35 5
A createAddressBook() 0 43 5
A createCard() 0 40 2
A search() 0 29 3
C getAddressBooksForUser() 0 85 10
A getUID() 0 12 3
A getCard() 0 17 2
A getCards() 0 17 2
B getChangesForAddressBook() 0 60 9
A getShares() 0 2 1
A readBlob() 0 6 2
A getUsersOwnAddressBooks() 0 26 3
A getMultipleCards() 0 26 4
A collectCardProperties() 0 12 1
A deleteCard() 0 27 4

How to fix   Complexity   

Complex Class

Complex classes like CardDavBackend often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CardDavBackend, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author John Molakvoæ (skjnldsv) <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 * @author Stefan Weil <[email protected]>
15
 * @author Thomas Citharel <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OCA\DAV\CardDAV;
35
36
use OCA\DAV\Connector\Sabre\Principal;
37
use OCP\DB\QueryBuilder\IQueryBuilder;
38
use OCA\DAV\DAV\Sharing\Backend;
39
use OCA\DAV\DAV\Sharing\IShareable;
40
use OCP\IDBConnection;
41
use OCP\IGroupManager;
42
use OCP\IUser;
43
use OCP\IUserManager;
44
use PDO;
45
use Sabre\CardDAV\Backend\BackendInterface;
46
use Sabre\CardDAV\Backend\SyncSupport;
47
use Sabre\CardDAV\Plugin;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OCA\DAV\CardDAV\Plugin. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
48
use Sabre\DAV\Exception\BadRequest;
49
use Sabre\VObject\Component\VCard;
50
use Sabre\VObject\Reader;
51
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
52
use Symfony\Component\EventDispatcher\GenericEvent;
53
54
class CardDavBackend implements BackendInterface, SyncSupport {
55
56
	const PERSONAL_ADDRESSBOOK_URI = 'contacts';
57
	const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
58
59
	/** @var Principal */
60
	private $principalBackend;
61
62
	/** @var string */
63
	private $dbCardsTable = 'cards';
64
65
	/** @var string */
66
	private $dbCardsPropertiesTable = 'cards_properties';
67
68
	/** @var IDBConnection */
69
	private $db;
70
71
	/** @var Backend */
72
	private $sharingBackend;
73
74
	/** @var array properties to index */
75
	public static $indexProperties = array(
76
			'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
77
			'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD');
78
79
	/**
80
	 * @var string[] Map of uid => display name
81
	 */
82
	protected $userDisplayNames;
83
84
	/** @var IUserManager */
85
	private $userManager;
86
87
	/** @var EventDispatcherInterface */
88
	private $dispatcher;
89
90
	/**
91
	 * CardDavBackend constructor.
92
	 *
93
	 * @param IDBConnection $db
94
	 * @param Principal $principalBackend
95
	 * @param IUserManager $userManager
96
	 * @param IGroupManager $groupManager
97
	 * @param EventDispatcherInterface $dispatcher
98
	 */
99
	public function __construct(IDBConnection $db,
100
								Principal $principalBackend,
101
								IUserManager $userManager,
102
								IGroupManager $groupManager,
103
								EventDispatcherInterface $dispatcher) {
104
		$this->db = $db;
105
		$this->principalBackend = $principalBackend;
106
		$this->userManager = $userManager;
107
		$this->dispatcher = $dispatcher;
108
		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
109
	}
110
111
	/**
112
	 * Return the number of address books for a principal
113
	 *
114
	 * @param $principalUri
115
	 * @return int
116
	 */
117
	public function getAddressBooksForUserCount($principalUri) {
118
		$principalUri = $this->convertPrincipal($principalUri, true);
119
		$query = $this->db->getQueryBuilder();
120
		$query->select($query->func()->count('*'))
121
			->from('addressbooks')
122
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
123
124
		return (int)$query->execute()->fetchColumn();
125
	}
126
127
	/**
128
	 * Returns the list of address books for a specific user.
129
	 *
130
	 * Every addressbook should have the following properties:
131
	 *   id - an arbitrary unique id
132
	 *   uri - the 'basename' part of the url
133
	 *   principaluri - Same as the passed parameter
134
	 *
135
	 * Any additional clark-notation property may be passed besides this. Some
136
	 * common ones are :
137
	 *   {DAV:}displayname
138
	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
139
	 *   {http://calendarserver.org/ns/}getctag
140
	 *
141
	 * @param string $principalUri
142
	 * @return array
143
	 */
144
	function getAddressBooksForUser($principalUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
145
		$principalUriOriginal = $principalUri;
146
		$principalUri = $this->convertPrincipal($principalUri, true);
147
		$query = $this->db->getQueryBuilder();
148
		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
149
			->from('addressbooks')
150
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
151
152
		$addressBooks = [];
153
154
		$result = $query->execute();
155
		while($row = $result->fetch()) {
156
			$addressBooks[$row['id']] = [
157
				'id'  => $row['id'],
158
				'uri' => $row['uri'],
159
				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
160
				'{DAV:}displayname' => $row['displayname'],
161
				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
162
				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
163
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
164
			];
165
166
			$this->addOwnerPrincipal($addressBooks[$row['id']]);
167
		}
168
		$result->closeCursor();
169
170
		// query for shared addressbooks
171
		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
172
		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
173
174
		$principals = array_map(function($principal) {
175
			return urldecode($principal);
176
		}, $principals);
177
		$principals[]= $principalUri;
178
179
		$query = $this->db->getQueryBuilder();
180
		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
181
			->from('dav_shares', 's')
182
			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
183
			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
184
			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
185
			->setParameter('type', 'addressbook')
186
			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
187
			->execute();
188
189
		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
190
		while($row = $result->fetch()) {
191
			if ($row['principaluri'] === $principalUri) {
192
				continue;
193
			}
194
195
			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
196
			if (isset($addressBooks[$row['id']])) {
197
				if ($readOnly) {
198
					// New share can not have more permissions then the old one.
199
					continue;
200
				}
201
				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
202
					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
203
					// Old share is already read-write, no more permissions can be gained
204
					continue;
205
				}
206
			}
207
208
			list(, $name) = \Sabre\Uri\split($row['principaluri']);
209
			$uri = $row['uri'] . '_shared_by_' . $name;
210
			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
211
212
			$addressBooks[$row['id']] = [
213
				'id'  => $row['id'],
214
				'uri' => $uri,
215
				'principaluri' => $principalUriOriginal,
216
				'{DAV:}displayname' => $displayName,
217
				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
218
				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
219
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
220
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
221
				$readOnlyPropertyName => $readOnly,
222
			];
223
224
			$this->addOwnerPrincipal($addressBooks[$row['id']]);
225
		}
226
		$result->closeCursor();
227
228
		return array_values($addressBooks);
229
	}
230
231
	public function getUsersOwnAddressBooks($principalUri) {
232
		$principalUri = $this->convertPrincipal($principalUri, true);
233
		$query = $this->db->getQueryBuilder();
234
		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
235
			  ->from('addressbooks')
236
			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
237
238
		$addressBooks = [];
239
240
		$result = $query->execute();
241
		while($row = $result->fetch()) {
242
			$addressBooks[$row['id']] = [
243
				'id'  => $row['id'],
244
				'uri' => $row['uri'],
245
				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
246
				'{DAV:}displayname' => $row['displayname'],
247
				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
248
				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
249
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
250
			];
251
252
			$this->addOwnerPrincipal($addressBooks[$row['id']]);
253
		}
254
		$result->closeCursor();
255
256
		return array_values($addressBooks);
257
	}
258
259
	private function getUserDisplayName($uid) {
260
		if (!isset($this->userDisplayNames[$uid])) {
261
			$user = $this->userManager->get($uid);
262
263
			if ($user instanceof IUser) {
264
				$this->userDisplayNames[$uid] = $user->getDisplayName();
265
			} else {
266
				$this->userDisplayNames[$uid] = $uid;
267
			}
268
		}
269
270
		return $this->userDisplayNames[$uid];
271
	}
272
273
	/**
274
	 * @param int $addressBookId
275
	 */
276
	public function getAddressBookById($addressBookId) {
277
		$query = $this->db->getQueryBuilder();
278
		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
279
			->from('addressbooks')
280
			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
281
			->execute();
282
283
		$row = $result->fetch();
284
		$result->closeCursor();
285
		if ($row === false) {
286
			return null;
287
		}
288
289
		$addressBook = [
290
			'id'  => $row['id'],
291
			'uri' => $row['uri'],
292
			'principaluri' => $row['principaluri'],
293
			'{DAV:}displayname' => $row['displayname'],
294
			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
295
			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
296
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
297
		];
298
299
		$this->addOwnerPrincipal($addressBook);
300
301
		return $addressBook;
302
	}
303
304
	/**
305
	 * @param $addressBookUri
306
	 * @return array|null
307
	 */
308
	public function getAddressBooksByUri($principal, $addressBookUri) {
309
		$query = $this->db->getQueryBuilder();
310
		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
311
			->from('addressbooks')
312
			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
313
			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
314
			->setMaxResults(1)
315
			->execute();
316
317
		$row = $result->fetch();
318
		$result->closeCursor();
319
		if ($row === false) {
320
			return null;
321
		}
322
323
		$addressBook = [
324
			'id'  => $row['id'],
325
			'uri' => $row['uri'],
326
			'principaluri' => $row['principaluri'],
327
			'{DAV:}displayname' => $row['displayname'],
328
			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
329
			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
330
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
331
		];
332
333
		$this->addOwnerPrincipal($addressBook);
334
335
		return $addressBook;
336
	}
337
338
	/**
339
	 * Updates properties for an address book.
340
	 *
341
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
342
	 * To do the actual updates, you must tell this object which properties
343
	 * you're going to process with the handle() method.
344
	 *
345
	 * Calling the handle method is like telling the PropPatch object "I
346
	 * promise I can handle updating this property".
347
	 *
348
	 * Read the PropPatch documentation for more info and examples.
349
	 *
350
	 * @param string $addressBookId
351
	 * @param \Sabre\DAV\PropPatch $propPatch
352
	 * @return void
353
	 */
354
	function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
355
		$supportedProperties = [
356
			'{DAV:}displayname',
357
			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
358
		];
359
360
		/**
361
		 * @suppress SqlInjectionChecker
362
		 */
363
		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
364
365
			$updates = [];
366
			foreach($mutations as $property=>$newValue) {
367
368
				switch($property) {
369
					case '{DAV:}displayname' :
370
						$updates['displayname'] = $newValue;
371
						break;
372
					case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
373
						$updates['description'] = $newValue;
374
						break;
375
				}
376
			}
377
			$query = $this->db->getQueryBuilder();
378
			$query->update('addressbooks');
379
380
			foreach($updates as $key=>$value) {
381
				$query->set($key, $query->createNamedParameter($value));
382
			}
383
			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
384
			->execute();
385
386
			$this->addChange($addressBookId, "", 2);
387
388
			return true;
389
390
		});
391
	}
392
393
	/**
394
	 * Creates a new address book
395
	 *
396
	 * @param string $principalUri
397
	 * @param string $url Just the 'basename' of the url.
398
	 * @param array $properties
399
	 * @return int
400
	 * @throws BadRequest
401
	 */
402
	function createAddressBook($principalUri, $url, array $properties) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
403
		$values = [
404
			'displayname' => null,
405
			'description' => null,
406
			'principaluri' => $principalUri,
407
			'uri' => $url,
408
			'synctoken' => 1
409
		];
410
411
		foreach($properties as $property=>$newValue) {
412
413
			switch($property) {
414
				case '{DAV:}displayname' :
415
					$values['displayname'] = $newValue;
416
					break;
417
				case '{' . Plugin::NS_CARDDAV . '}addressbook-description' :
418
					$values['description'] = $newValue;
419
					break;
420
				default :
421
					throw new BadRequest('Unknown property: ' . $property);
422
			}
423
424
		}
425
426
		// Fallback to make sure the displayname is set. Some clients may refuse
427
		// to work with addressbooks not having a displayname.
428
		if(is_null($values['displayname'])) {
429
			$values['displayname'] = $url;
430
		}
431
432
		$query = $this->db->getQueryBuilder();
433
		$query->insert('addressbooks')
434
			->values([
435
				'uri' => $query->createParameter('uri'),
436
				'displayname' => $query->createParameter('displayname'),
437
				'description' => $query->createParameter('description'),
438
				'principaluri' => $query->createParameter('principaluri'),
439
				'synctoken' => $query->createParameter('synctoken'),
440
			])
441
			->setParameters($values)
442
			->execute();
443
444
		return $query->getLastInsertId();
445
	}
446
447
	/**
448
	 * Deletes an entire addressbook and all its contents
449
	 *
450
	 * @param mixed $addressBookId
451
	 * @return void
452
	 */
453
	function deleteAddressBook($addressBookId) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
454
		$query = $this->db->getQueryBuilder();
455
		$query->delete('cards')
456
			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
457
			->setParameter('addressbookid', $addressBookId)
458
			->execute();
459
460
		$query->delete('addressbookchanges')
461
			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
462
			->setParameter('addressbookid', $addressBookId)
463
			->execute();
464
465
		$query->delete('addressbooks')
466
			->where($query->expr()->eq('id', $query->createParameter('id')))
467
			->setParameter('id', $addressBookId)
468
			->execute();
469
470
		$this->sharingBackend->deleteAllShares($addressBookId);
471
472
		$query->delete($this->dbCardsPropertiesTable)
473
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
474
			->execute();
475
476
	}
477
478
	/**
479
	 * Returns all cards for a specific addressbook id.
480
	 *
481
	 * This method should return the following properties for each card:
482
	 *   * carddata - raw vcard data
483
	 *   * uri - Some unique url
484
	 *   * lastmodified - A unix timestamp
485
	 *
486
	 * It's recommended to also return the following properties:
487
	 *   * etag - A unique etag. This must change every time the card changes.
488
	 *   * size - The size of the card in bytes.
489
	 *
490
	 * If these last two properties are provided, less time will be spent
491
	 * calculating them. If they are specified, you can also ommit carddata.
492
	 * This may speed up certain requests, especially with large cards.
493
	 *
494
	 * @param mixed $addressBookId
495
	 * @return array
496
	 */
497
	function getCards($addressBookId) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
498
		$query = $this->db->getQueryBuilder();
499
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
500
			->from('cards')
501
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
502
503
		$cards = [];
504
505
		$result = $query->execute();
506
		while($row = $result->fetch()) {
507
			$row['etag'] = '"' . $row['etag'] . '"';
508
			$row['carddata'] = $this->readBlob($row['carddata']);
509
			$cards[] = $row;
510
		}
511
		$result->closeCursor();
512
513
		return $cards;
514
	}
515
516
	/**
517
	 * Returns a specific card.
518
	 *
519
	 * The same set of properties must be returned as with getCards. The only
520
	 * exception is that 'carddata' is absolutely required.
521
	 *
522
	 * If the card does not exist, you must return false.
523
	 *
524
	 * @param mixed $addressBookId
525
	 * @param string $cardUri
526
	 * @return array
527
	 */
528
	function getCard($addressBookId, $cardUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
529
		$query = $this->db->getQueryBuilder();
530
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
531
			->from('cards')
532
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
533
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
534
			->setMaxResults(1);
535
536
		$result = $query->execute();
537
		$row = $result->fetch();
538
		if (!$row) {
539
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
540
		}
541
		$row['etag'] = '"' . $row['etag'] . '"';
542
		$row['carddata'] = $this->readBlob($row['carddata']);
543
544
		return $row;
545
	}
546
547
	/**
548
	 * Returns a list of cards.
549
	 *
550
	 * This method should work identical to getCard, but instead return all the
551
	 * cards in the list as an array.
552
	 *
553
	 * If the backend supports this, it may allow for some speed-ups.
554
	 *
555
	 * @param mixed $addressBookId
556
	 * @param string[] $uris
557
	 * @return array
558
	 */
559
	function getMultipleCards($addressBookId, array $uris) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
560
		if (empty($uris)) {
561
			return [];
562
		}
563
564
		$chunks = array_chunk($uris, 100);
565
		$cards = [];
566
567
		$query = $this->db->getQueryBuilder();
568
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
569
			->from('cards')
570
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
571
			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
572
573
		foreach ($chunks as $uris) {
574
			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
575
			$result = $query->execute();
576
577
			while ($row = $result->fetch()) {
578
				$row['etag'] = '"' . $row['etag'] . '"';
579
				$row['carddata'] = $this->readBlob($row['carddata']);
580
				$cards[] = $row;
581
			}
582
			$result->closeCursor();
583
		}
584
		return $cards;
585
	}
586
587
	/**
588
	 * Creates a new card.
589
	 *
590
	 * The addressbook id will be passed as the first argument. This is the
591
	 * same id as it is returned from the getAddressBooksForUser method.
592
	 *
593
	 * The cardUri is a base uri, and doesn't include the full path. The
594
	 * cardData argument is the vcard body, and is passed as a string.
595
	 *
596
	 * It is possible to return an ETag from this method. This ETag is for the
597
	 * newly created resource, and must be enclosed with double quotes (that
598
	 * is, the string itself must contain the double quotes).
599
	 *
600
	 * You should only return the ETag if you store the carddata as-is. If a
601
	 * subsequent GET request on the same card does not have the same body,
602
	 * byte-by-byte and you did return an ETag here, clients tend to get
603
	 * confused.
604
	 *
605
	 * If you don't return an ETag, you can just return null.
606
	 *
607
	 * @param mixed $addressBookId
608
	 * @param string $cardUri
609
	 * @param string $cardData
610
	 * @return string
611
	 */
612
	function createCard($addressBookId, $cardUri, $cardData) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
613
		$etag = md5($cardData);
614
		$uid = $this->getUID($cardData);
615
616
		$q = $this->db->getQueryBuilder();
617
		$q->select('uid')
618
			->from('cards')
619
			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
620
			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
621
			->setMaxResults(1);
622
		$result = $q->execute();
623
		$count = (bool) $result->fetchColumn();
624
		$result->closeCursor();
625
		if ($count) {
626
			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
627
		}
628
629
		$query = $this->db->getQueryBuilder();
630
		$query->insert('cards')
631
			->values([
632
				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
633
				'uri' => $query->createNamedParameter($cardUri),
634
				'lastmodified' => $query->createNamedParameter(time()),
635
				'addressbookid' => $query->createNamedParameter($addressBookId),
636
				'size' => $query->createNamedParameter(strlen($cardData)),
637
				'etag' => $query->createNamedParameter($etag),
638
				'uid' => $query->createNamedParameter($uid),
639
			])
640
			->execute();
641
642
		$this->addChange($addressBookId, $cardUri, 1);
643
		$this->updateProperties($addressBookId, $cardUri, $cardData);
644
645
		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
646
			new GenericEvent(null, [
647
				'addressBookId' => $addressBookId,
648
				'cardUri' => $cardUri,
649
				'cardData' => $cardData]));
650
651
		return '"' . $etag . '"';
652
	}
653
654
	/**
655
	 * Updates a card.
656
	 *
657
	 * The addressbook id will be passed as the first argument. This is the
658
	 * same id as it is returned from the getAddressBooksForUser method.
659
	 *
660
	 * The cardUri is a base uri, and doesn't include the full path. The
661
	 * cardData argument is the vcard body, and is passed as a string.
662
	 *
663
	 * It is possible to return an ETag from this method. This ETag should
664
	 * match that of the updated resource, and must be enclosed with double
665
	 * quotes (that is: the string itself must contain the actual quotes).
666
	 *
667
	 * You should only return the ETag if you store the carddata as-is. If a
668
	 * subsequent GET request on the same card does not have the same body,
669
	 * byte-by-byte and you did return an ETag here, clients tend to get
670
	 * confused.
671
	 *
672
	 * If you don't return an ETag, you can just return null.
673
	 *
674
	 * @param mixed $addressBookId
675
	 * @param string $cardUri
676
	 * @param string $cardData
677
	 * @return string
678
	 */
679
	function updateCard($addressBookId, $cardUri, $cardData) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
680
681
		$uid = $this->getUID($cardData);
682
		$etag = md5($cardData);
683
		$query = $this->db->getQueryBuilder();
684
		$query->update('cards')
685
			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
686
			->set('lastmodified', $query->createNamedParameter(time()))
687
			->set('size', $query->createNamedParameter(strlen($cardData)))
688
			->set('etag', $query->createNamedParameter($etag))
689
			->set('uid', $query->createNamedParameter($uid))
690
			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
691
			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
692
			->execute();
693
694
		$this->addChange($addressBookId, $cardUri, 2);
695
		$this->updateProperties($addressBookId, $cardUri, $cardData);
696
697
		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
698
			new GenericEvent(null, [
699
				'addressBookId' => $addressBookId,
700
				'cardUri' => $cardUri,
701
				'cardData' => $cardData]));
702
703
		return '"' . $etag . '"';
704
	}
705
706
	/**
707
	 * Deletes a card
708
	 *
709
	 * @param mixed $addressBookId
710
	 * @param string $cardUri
711
	 * @return bool
712
	 */
713
	function deleteCard($addressBookId, $cardUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
714
		try {
715
			$cardId = $this->getCardId($addressBookId, $cardUri);
716
		} catch (\InvalidArgumentException $e) {
717
			$cardId = null;
718
		}
719
		$query = $this->db->getQueryBuilder();
720
		$ret = $query->delete('cards')
721
			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
722
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
723
			->execute();
724
725
		$this->addChange($addressBookId, $cardUri, 3);
726
727
		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
728
			new GenericEvent(null, [
729
				'addressBookId' => $addressBookId,
730
				'cardUri' => $cardUri]));
731
732
		if ($ret === 1) {
733
			if ($cardId !== null) {
734
				$this->purgeProperties($addressBookId, $cardId);
735
			}
736
			return true;
737
		}
738
739
		return false;
740
	}
741
742
	/**
743
	 * The getChanges method returns all the changes that have happened, since
744
	 * the specified syncToken in the specified address book.
745
	 *
746
	 * This function should return an array, such as the following:
747
	 *
748
	 * [
749
	 *   'syncToken' => 'The current synctoken',
750
	 *   'added'   => [
751
	 *      'new.txt',
752
	 *   ],
753
	 *   'modified'   => [
754
	 *      'modified.txt',
755
	 *   ],
756
	 *   'deleted' => [
757
	 *      'foo.php.bak',
758
	 *      'old.txt'
759
	 *   ]
760
	 * ];
761
	 *
762
	 * The returned syncToken property should reflect the *current* syncToken
763
	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
764
	 * property. This is needed here too, to ensure the operation is atomic.
765
	 *
766
	 * If the $syncToken argument is specified as null, this is an initial
767
	 * sync, and all members should be reported.
768
	 *
769
	 * The modified property is an array of nodenames that have changed since
770
	 * the last token.
771
	 *
772
	 * The deleted property is an array with nodenames, that have been deleted
773
	 * from collection.
774
	 *
775
	 * The $syncLevel argument is basically the 'depth' of the report. If it's
776
	 * 1, you only have to report changes that happened only directly in
777
	 * immediate descendants. If it's 2, it should also include changes from
778
	 * the nodes below the child collections. (grandchildren)
779
	 *
780
	 * The $limit argument allows a client to specify how many results should
781
	 * be returned at most. If the limit is not specified, it should be treated
782
	 * as infinite.
783
	 *
784
	 * If the limit (infinite or not) is higher than you're willing to return,
785
	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
786
	 *
787
	 * If the syncToken is expired (due to data cleanup) or unknown, you must
788
	 * return null.
789
	 *
790
	 * The limit is 'suggestive'. You are free to ignore it.
791
	 *
792
	 * @param string $addressBookId
793
	 * @param string $syncToken
794
	 * @param int $syncLevel
795
	 * @param int $limit
796
	 * @return array
797
	 */
798
	function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
799
		// Current synctoken
800
		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
801
		$stmt->execute([ $addressBookId ]);
802
		$currentToken = $stmt->fetchColumn(0);
803
804
		if (is_null($currentToken)) return null;
0 ignored issues
show
introduced by
The condition is_null($currentToken) is always false.
Loading history...
805
806
		$result = [
807
			'syncToken' => $currentToken,
808
			'added'     => [],
809
			'modified'  => [],
810
			'deleted'   => [],
811
		];
812
813
		if ($syncToken) {
814
815
			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
816
			if ($limit>0) {
817
				$query .= " `LIMIT` " . (int)$limit;
818
			}
819
820
			// Fetching all changes
821
			$stmt = $this->db->prepare($query);
822
			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
823
824
			$changes = [];
825
826
			// This loop ensures that any duplicates are overwritten, only the
827
			// last change on a node is relevant.
828
			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
829
830
				$changes[$row['uri']] = $row['operation'];
831
832
			}
833
834
			foreach($changes as $uri => $operation) {
835
836
				switch($operation) {
837
					case 1:
838
						$result['added'][] = $uri;
839
						break;
840
					case 2:
841
						$result['modified'][] = $uri;
842
						break;
843
					case 3:
844
						$result['deleted'][] = $uri;
845
						break;
846
				}
847
848
			}
849
		} else {
850
			// No synctoken supplied, this is the initial sync.
851
			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
852
			$stmt = $this->db->prepare($query);
853
			$stmt->execute([$addressBookId]);
854
855
			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
856
		}
857
		return $result;
858
	}
859
860
	/**
861
	 * Adds a change record to the addressbookchanges table.
862
	 *
863
	 * @param mixed $addressBookId
864
	 * @param string $objectUri
865
	 * @param int $operation 1 = add, 2 = modify, 3 = delete
866
	 * @return void
867
	 */
868
	protected function addChange($addressBookId, $objectUri, $operation) {
869
		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
870
		$stmt = $this->db->prepare($sql);
871
		$stmt->execute([
872
			$objectUri,
873
			$addressBookId,
874
			$operation,
875
			$addressBookId
876
		]);
877
		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
878
		$stmt->execute([
879
			$addressBookId
880
		]);
881
	}
882
883
	private function readBlob($cardData) {
884
		if (is_resource($cardData)) {
885
			return stream_get_contents($cardData);
886
		}
887
888
		return $cardData;
889
	}
890
891
	/**
892
	 * @param IShareable $shareable
893
	 * @param string[] $add
894
	 * @param string[] $remove
895
	 */
896
	public function updateShares(IShareable $shareable, $add, $remove) {
897
		$this->sharingBackend->updateShares($shareable, $add, $remove);
898
	}
899
900
	/**
901
	 * search contact
902
	 *
903
	 * @param int $addressBookId
904
	 * @param string $pattern which should match within the $searchProperties
905
	 * @param array $searchProperties defines the properties within the query pattern should match
906
	 * @return array an array of contacts which are arrays of key-value-pairs
907
	 */
908
	public function search($addressBookId, $pattern, $searchProperties) {
909
		$query = $this->db->getQueryBuilder();
910
		$query2 = $this->db->getQueryBuilder();
911
912
		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
913
		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
914
		$or = $query2->expr()->orX();
915
		foreach ($searchProperties as $property) {
916
			$or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
917
		}
918
		$query2->andWhere($or);
919
920
		// No need for like when the pattern is empty
921
		if ('' !== $pattern) {
922
			$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
923
		}
924
925
		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
926
			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
927
928
		$result = $query->execute();
929
		$cards = $result->fetchAll();
930
931
		$result->closeCursor();
932
933
		return array_map(function($array) {
934
			$array['carddata'] = $this->readBlob($array['carddata']);
935
			return $array;
936
		}, $cards);
937
	}
938
939
	/**
940
	 * @param int $bookId
941
	 * @param string $name
942
	 * @return array
943
	 */
944
	public function collectCardProperties($bookId, $name) {
945
		$query = $this->db->getQueryBuilder();
946
		$result = $query->selectDistinct('value')
947
			->from($this->dbCardsPropertiesTable)
948
			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
949
			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
950
			->execute();
951
952
		$all = $result->fetchAll(PDO::FETCH_COLUMN);
953
		$result->closeCursor();
954
955
		return $all;
956
	}
957
958
	/**
959
	 * get URI from a given contact
960
	 *
961
	 * @param int $id
962
	 * @return string
963
	 */
964
	public function getCardUri($id) {
965
		$query = $this->db->getQueryBuilder();
966
		$query->select('uri')->from($this->dbCardsTable)
967
				->where($query->expr()->eq('id', $query->createParameter('id')))
968
				->setParameter('id', $id);
969
970
		$result = $query->execute();
971
		$uri = $result->fetch();
972
		$result->closeCursor();
973
974
		if (!isset($uri['uri'])) {
975
			throw new \InvalidArgumentException('Card does not exists: ' . $id);
976
		}
977
978
		return $uri['uri'];
979
	}
980
981
	/**
982
	 * return contact with the given URI
983
	 *
984
	 * @param int $addressBookId
985
	 * @param string $uri
986
	 * @returns array
987
	 */
988
	public function getContact($addressBookId, $uri) {
989
		$result = [];
990
		$query = $this->db->getQueryBuilder();
991
		$query->select('*')->from($this->dbCardsTable)
992
				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
993
				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
994
		$queryResult = $query->execute();
995
		$contact = $queryResult->fetch();
996
		$queryResult->closeCursor();
997
998
		if (is_array($contact)) {
999
			$result = $contact;
1000
		}
1001
1002
		return $result;
1003
	}
1004
1005
	/**
1006
	 * Returns the list of people whom this address book is shared with.
1007
	 *
1008
	 * Every element in this array should have the following properties:
1009
	 *   * href - Often a mailto: address
1010
	 *   * commonName - Optional, for example a first + last name
1011
	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1012
	 *   * readOnly - boolean
1013
	 *   * summary - Optional, a description for the share
1014
	 *
1015
	 * @return array
1016
	 */
1017
	public function getShares($addressBookId) {
1018
		return $this->sharingBackend->getShares($addressBookId);
1019
	}
1020
1021
	/**
1022
	 * update properties table
1023
	 *
1024
	 * @param int $addressBookId
1025
	 * @param string $cardUri
1026
	 * @param string $vCardSerialized
1027
	 */
1028
	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1029
		$cardId = $this->getCardId($addressBookId, $cardUri);
1030
		$vCard = $this->readCard($vCardSerialized);
1031
1032
		$this->purgeProperties($addressBookId, $cardId);
1033
1034
		$query = $this->db->getQueryBuilder();
1035
		$query->insert($this->dbCardsPropertiesTable)
1036
			->values(
1037
				[
1038
					'addressbookid' => $query->createNamedParameter($addressBookId),
1039
					'cardid' => $query->createNamedParameter($cardId),
1040
					'name' => $query->createParameter('name'),
1041
					'value' => $query->createParameter('value'),
1042
					'preferred' => $query->createParameter('preferred')
1043
				]
1044
			);
1045
1046
		foreach ($vCard->children() as $property) {
1047
			if(!in_array($property->name, self::$indexProperties)) {
1048
				continue;
1049
			}
1050
			$preferred = 0;
1051
			foreach($property->parameters as $parameter) {
1052
				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1053
					$preferred = 1;
1054
					break;
1055
				}
1056
			}
1057
			$query->setParameter('name', $property->name);
1058
			$query->setParameter('value', substr($property->getValue(), 0, 254));
1059
			$query->setParameter('preferred', $preferred);
1060
			$query->execute();
1061
		}
1062
	}
1063
1064
	/**
1065
	 * read vCard data into a vCard object
1066
	 *
1067
	 * @param string $cardData
1068
	 * @return VCard
1069
	 */
1070
	protected function readCard($cardData) {
1071
		return  Reader::read($cardData);
1072
	}
1073
1074
	/**
1075
	 * delete all properties from a given card
1076
	 *
1077
	 * @param int $addressBookId
1078
	 * @param int $cardId
1079
	 */
1080
	protected function purgeProperties($addressBookId, $cardId) {
1081
		$query = $this->db->getQueryBuilder();
1082
		$query->delete($this->dbCardsPropertiesTable)
1083
			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1084
			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1085
		$query->execute();
1086
	}
1087
1088
	/**
1089
	 * get ID from a given contact
1090
	 *
1091
	 * @param int $addressBookId
1092
	 * @param string $uri
1093
	 * @return int
1094
	 */
1095
	protected function getCardId($addressBookId, $uri) {
1096
		$query = $this->db->getQueryBuilder();
1097
		$query->select('id')->from($this->dbCardsTable)
1098
			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1099
			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1100
1101
		$result = $query->execute();
1102
		$cardIds = $result->fetch();
1103
		$result->closeCursor();
1104
1105
		if (!isset($cardIds['id'])) {
1106
			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1107
		}
1108
1109
		return (int)$cardIds['id'];
1110
	}
1111
1112
	/**
1113
	 * For shared address books the sharee is set in the ACL of the address book
1114
	 * @param $addressBookId
1115
	 * @param $acl
1116
	 * @return array
1117
	 */
1118
	public function applyShareAcl($addressBookId, $acl) {
1119
		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1120
	}
1121
1122
	private function convertPrincipal($principalUri, $toV2) {
1123
		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1124
			list(, $name) = \Sabre\Uri\split($principalUri);
1125
			if ($toV2 === true) {
1126
				return "principals/users/$name";
1127
			}
1128
			return "principals/$name";
1129
		}
1130
		return $principalUri;
1131
	}
1132
1133
	private function addOwnerPrincipal(&$addressbookInfo) {
1134
		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1135
		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1136
		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1137
			$uri = $addressbookInfo[$ownerPrincipalKey];
1138
		} else {
1139
			$uri = $addressbookInfo['principaluri'];
1140
		}
1141
1142
		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1143
		if (isset($principalInformation['{DAV:}displayname'])) {
1144
			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1145
		}
1146
	}
1147
1148
	/**
1149
	 * Extract UID from vcard
1150
	 *
1151
	 * @param string $cardData the vcard raw data
1152
	 * @return string the uid
1153
	 * @throws BadRequest if no UID is available
1154
	 */
1155
	private function getUID($cardData) {
1156
		if ($cardData != '') {
1157
			$vCard = Reader::read($cardData);
1158
			if ($vCard->UID) {
1159
				$uid = $vCard->UID->getValue();
1160
				return $uid;
1161
			}
1162
			// should already be handled, but just in case
1163
			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1164
		}
1165
		// should already be handled, but just in case
1166
		throw new BadRequest('vCard can not be empty');
1167
	}
1168
}
1169