Passed
Push — master ( 24e589...038045 )
by Blizzz
10:34 queued 10s
created
apps/dav/lib/CardDAV/CardDavBackend.php 2 patches
Indentation   +1171 added lines, -1171 removed lines patch added patch discarded remove patch
@@ -55,1175 +55,1175 @@
 block discarded – undo
55 55
 use Symfony\Component\EventDispatcher\GenericEvent;
56 56
 
57 57
 class CardDavBackend implements BackendInterface, SyncSupport {
58
-	public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
59
-	public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
60
-
61
-	/** @var Principal */
62
-	private $principalBackend;
63
-
64
-	/** @var string */
65
-	private $dbCardsTable = 'cards';
66
-
67
-	/** @var string */
68
-	private $dbCardsPropertiesTable = 'cards_properties';
69
-
70
-	/** @var IDBConnection */
71
-	private $db;
72
-
73
-	/** @var Backend */
74
-	private $sharingBackend;
75
-
76
-	/** @var array properties to index */
77
-	public static $indexProperties = [
78
-		'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
79
-		'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
80
-
81
-	/**
82
-	 * @var string[] Map of uid => display name
83
-	 */
84
-	protected $userDisplayNames;
85
-
86
-	/** @var IUserManager */
87
-	private $userManager;
88
-
89
-	/** @var EventDispatcherInterface */
90
-	private $dispatcher;
91
-
92
-	/**
93
-	 * CardDavBackend constructor.
94
-	 *
95
-	 * @param IDBConnection $db
96
-	 * @param Principal $principalBackend
97
-	 * @param IUserManager $userManager
98
-	 * @param IGroupManager $groupManager
99
-	 * @param EventDispatcherInterface $dispatcher
100
-	 */
101
-	public function __construct(IDBConnection $db,
102
-								Principal $principalBackend,
103
-								IUserManager $userManager,
104
-								IGroupManager $groupManager,
105
-								EventDispatcherInterface $dispatcher) {
106
-		$this->db = $db;
107
-		$this->principalBackend = $principalBackend;
108
-		$this->userManager = $userManager;
109
-		$this->dispatcher = $dispatcher;
110
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
111
-	}
112
-
113
-	/**
114
-	 * Return the number of address books for a principal
115
-	 *
116
-	 * @param $principalUri
117
-	 * @return int
118
-	 */
119
-	public function getAddressBooksForUserCount($principalUri) {
120
-		$principalUri = $this->convertPrincipal($principalUri, true);
121
-		$query = $this->db->getQueryBuilder();
122
-		$query->select($query->func()->count('*'))
123
-			->from('addressbooks')
124
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125
-
126
-		return (int)$query->execute()->fetchColumn();
127
-	}
128
-
129
-	/**
130
-	 * Returns the list of address books for a specific user.
131
-	 *
132
-	 * Every addressbook should have the following properties:
133
-	 *   id - an arbitrary unique id
134
-	 *   uri - the 'basename' part of the url
135
-	 *   principaluri - Same as the passed parameter
136
-	 *
137
-	 * Any additional clark-notation property may be passed besides this. Some
138
-	 * common ones are :
139
-	 *   {DAV:}displayname
140
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
141
-	 *   {http://calendarserver.org/ns/}getctag
142
-	 *
143
-	 * @param string $principalUri
144
-	 * @return array
145
-	 */
146
-	public function getAddressBooksForUser($principalUri) {
147
-		$principalUriOriginal = $principalUri;
148
-		$principalUri = $this->convertPrincipal($principalUri, true);
149
-		$query = $this->db->getQueryBuilder();
150
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
151
-			->from('addressbooks')
152
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
153
-
154
-		$addressBooks = [];
155
-
156
-		$result = $query->execute();
157
-		while ($row = $result->fetch()) {
158
-			$addressBooks[$row['id']] = [
159
-				'id'  => $row['id'],
160
-				'uri' => $row['uri'],
161
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162
-				'{DAV:}displayname' => $row['displayname'],
163
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
-			];
167
-
168
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
169
-		}
170
-		$result->closeCursor();
171
-
172
-		// query for shared addressbooks
173
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175
-
176
-		$principals = array_map(function ($principal) {
177
-			return urldecode($principal);
178
-		}, $principals);
179
-		$principals[]= $principalUri;
180
-
181
-		$query = $this->db->getQueryBuilder();
182
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
183
-			->from('dav_shares', 's')
184
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
185
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
186
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
187
-			->setParameter('type', 'addressbook')
188
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189
-			->execute();
190
-
191
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
192
-		while ($row = $result->fetch()) {
193
-			if ($row['principaluri'] === $principalUri) {
194
-				continue;
195
-			}
196
-
197
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
198
-			if (isset($addressBooks[$row['id']])) {
199
-				if ($readOnly) {
200
-					// New share can not have more permissions then the old one.
201
-					continue;
202
-				}
203
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
204
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
205
-					// Old share is already read-write, no more permissions can be gained
206
-					continue;
207
-				}
208
-			}
209
-
210
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
-			$uri = $row['uri'] . '_shared_by_' . $name;
212
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
213
-
214
-			$addressBooks[$row['id']] = [
215
-				'id'  => $row['id'],
216
-				'uri' => $uri,
217
-				'principaluri' => $principalUriOriginal,
218
-				'{DAV:}displayname' => $displayName,
219
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
223
-				$readOnlyPropertyName => $readOnly,
224
-			];
225
-
226
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
227
-		}
228
-		$result->closeCursor();
229
-
230
-		return array_values($addressBooks);
231
-	}
232
-
233
-	public function getUsersOwnAddressBooks($principalUri) {
234
-		$principalUri = $this->convertPrincipal($principalUri, true);
235
-		$query = $this->db->getQueryBuilder();
236
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
237
-			  ->from('addressbooks')
238
-			  ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
239
-
240
-		$addressBooks = [];
241
-
242
-		$result = $query->execute();
243
-		while ($row = $result->fetch()) {
244
-			$addressBooks[$row['id']] = [
245
-				'id'  => $row['id'],
246
-				'uri' => $row['uri'],
247
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248
-				'{DAV:}displayname' => $row['displayname'],
249
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
-			];
253
-
254
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
255
-		}
256
-		$result->closeCursor();
257
-
258
-		return array_values($addressBooks);
259
-	}
260
-
261
-	private function getUserDisplayName($uid) {
262
-		if (!isset($this->userDisplayNames[$uid])) {
263
-			$user = $this->userManager->get($uid);
264
-
265
-			if ($user instanceof IUser) {
266
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
267
-			} else {
268
-				$this->userDisplayNames[$uid] = $uid;
269
-			}
270
-		}
271
-
272
-		return $this->userDisplayNames[$uid];
273
-	}
274
-
275
-	/**
276
-	 * @param int $addressBookId
277
-	 */
278
-	public function getAddressBookById($addressBookId) {
279
-		$query = $this->db->getQueryBuilder();
280
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
281
-			->from('addressbooks')
282
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
283
-			->execute();
284
-
285
-		$row = $result->fetch();
286
-		$result->closeCursor();
287
-		if ($row === false) {
288
-			return null;
289
-		}
290
-
291
-		$addressBook = [
292
-			'id'  => $row['id'],
293
-			'uri' => $row['uri'],
294
-			'principaluri' => $row['principaluri'],
295
-			'{DAV:}displayname' => $row['displayname'],
296
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
-		];
300
-
301
-		$this->addOwnerPrincipal($addressBook);
302
-
303
-		return $addressBook;
304
-	}
305
-
306
-	/**
307
-	 * @param $addressBookUri
308
-	 * @return array|null
309
-	 */
310
-	public function getAddressBooksByUri($principal, $addressBookUri) {
311
-		$query = $this->db->getQueryBuilder();
312
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
313
-			->from('addressbooks')
314
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
315
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
316
-			->setMaxResults(1)
317
-			->execute();
318
-
319
-		$row = $result->fetch();
320
-		$result->closeCursor();
321
-		if ($row === false) {
322
-			return null;
323
-		}
324
-
325
-		$addressBook = [
326
-			'id'  => $row['id'],
327
-			'uri' => $row['uri'],
328
-			'principaluri' => $row['principaluri'],
329
-			'{DAV:}displayname' => $row['displayname'],
330
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
-		];
334
-
335
-		$this->addOwnerPrincipal($addressBook);
336
-
337
-		return $addressBook;
338
-	}
339
-
340
-	/**
341
-	 * Updates properties for an address book.
342
-	 *
343
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
344
-	 * To do the actual updates, you must tell this object which properties
345
-	 * you're going to process with the handle() method.
346
-	 *
347
-	 * Calling the handle method is like telling the PropPatch object "I
348
-	 * promise I can handle updating this property".
349
-	 *
350
-	 * Read the PropPatch documentation for more info and examples.
351
-	 *
352
-	 * @param string $addressBookId
353
-	 * @param \Sabre\DAV\PropPatch $propPatch
354
-	 * @return void
355
-	 */
356
-	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357
-		$supportedProperties = [
358
-			'{DAV:}displayname',
359
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
-		];
361
-
362
-		/**
363
-		 * @suppress SqlInjectionChecker
364
-		 */
365
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
366
-			$updates = [];
367
-			foreach ($mutations as $property=>$newValue) {
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
-	 * Creates a new address book
394
-	 *
395
-	 * @param string $principalUri
396
-	 * @param string $url Just the 'basename' of the url.
397
-	 * @param array $properties
398
-	 * @return int
399
-	 * @throws BadRequest
400
-	 */
401
-	public function createAddressBook($principalUri, $url, array $properties) {
402
-		$values = [
403
-			'displayname' => null,
404
-			'description' => null,
405
-			'principaluri' => $principalUri,
406
-			'uri' => $url,
407
-			'synctoken' => 1
408
-		];
409
-
410
-		foreach ($properties as $property=>$newValue) {
411
-			switch ($property) {
412
-				case '{DAV:}displayname':
413
-					$values['displayname'] = $newValue;
414
-					break;
415
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
416
-					$values['description'] = $newValue;
417
-					break;
418
-				default:
419
-					throw new BadRequest('Unknown property: ' . $property);
420
-			}
421
-		}
422
-
423
-		// Fallback to make sure the displayname is set. Some clients may refuse
424
-		// to work with addressbooks not having a displayname.
425
-		if (is_null($values['displayname'])) {
426
-			$values['displayname'] = $url;
427
-		}
428
-
429
-		$query = $this->db->getQueryBuilder();
430
-		$query->insert('addressbooks')
431
-			->values([
432
-				'uri' => $query->createParameter('uri'),
433
-				'displayname' => $query->createParameter('displayname'),
434
-				'description' => $query->createParameter('description'),
435
-				'principaluri' => $query->createParameter('principaluri'),
436
-				'synctoken' => $query->createParameter('synctoken'),
437
-			])
438
-			->setParameters($values)
439
-			->execute();
440
-
441
-		return $query->getLastInsertId();
442
-	}
443
-
444
-	/**
445
-	 * Deletes an entire addressbook and all its contents
446
-	 *
447
-	 * @param mixed $addressBookId
448
-	 * @return void
449
-	 */
450
-	public function deleteAddressBook($addressBookId) {
451
-		$query = $this->db->getQueryBuilder();
452
-		$query->delete($this->dbCardsTable)
453
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
454
-			->setParameter('addressbookid', $addressBookId)
455
-			->execute();
456
-
457
-		$query->delete('addressbookchanges')
458
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
459
-			->setParameter('addressbookid', $addressBookId)
460
-			->execute();
461
-
462
-		$query->delete('addressbooks')
463
-			->where($query->expr()->eq('id', $query->createParameter('id')))
464
-			->setParameter('id', $addressBookId)
465
-			->execute();
466
-
467
-		$this->sharingBackend->deleteAllShares($addressBookId);
468
-
469
-		$query->delete($this->dbCardsPropertiesTable)
470
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
471
-			->execute();
472
-	}
473
-
474
-	/**
475
-	 * Returns all cards for a specific addressbook id.
476
-	 *
477
-	 * This method should return the following properties for each card:
478
-	 *   * carddata - raw vcard data
479
-	 *   * uri - Some unique url
480
-	 *   * lastmodified - A unix timestamp
481
-	 *
482
-	 * It's recommended to also return the following properties:
483
-	 *   * etag - A unique etag. This must change every time the card changes.
484
-	 *   * size - The size of the card in bytes.
485
-	 *
486
-	 * If these last two properties are provided, less time will be spent
487
-	 * calculating them. If they are specified, you can also ommit carddata.
488
-	 * This may speed up certain requests, especially with large cards.
489
-	 *
490
-	 * @param mixed $addressBookId
491
-	 * @return array
492
-	 */
493
-	public function getCards($addressBookId) {
494
-		$query = $this->db->getQueryBuilder();
495
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
496
-			->from($this->dbCardsTable)
497
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
498
-
499
-		$cards = [];
500
-
501
-		$result = $query->execute();
502
-		while ($row = $result->fetch()) {
503
-			$row['etag'] = '"' . $row['etag'] . '"';
504
-
505
-			$modified = false;
506
-			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
507
-			if ($modified) {
508
-				$row['size'] = strlen($row['carddata']);
509
-			}
510
-
511
-			$cards[] = $row;
512
-		}
513
-		$result->closeCursor();
514
-
515
-		return $cards;
516
-	}
517
-
518
-	/**
519
-	 * Returns a specific card.
520
-	 *
521
-	 * The same set of properties must be returned as with getCards. The only
522
-	 * exception is that 'carddata' is absolutely required.
523
-	 *
524
-	 * If the card does not exist, you must return false.
525
-	 *
526
-	 * @param mixed $addressBookId
527
-	 * @param string $cardUri
528
-	 * @return array
529
-	 */
530
-	public function getCard($addressBookId, $cardUri) {
531
-		$query = $this->db->getQueryBuilder();
532
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
533
-			->from($this->dbCardsTable)
534
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
535
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
536
-			->setMaxResults(1);
537
-
538
-		$result = $query->execute();
539
-		$row = $result->fetch();
540
-		if (!$row) {
541
-			return false;
542
-		}
543
-		$row['etag'] = '"' . $row['etag'] . '"';
544
-
545
-		$modified = false;
546
-		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
547
-		if ($modified) {
548
-			$row['size'] = strlen($row['carddata']);
549
-		}
550
-
551
-		return $row;
552
-	}
553
-
554
-	/**
555
-	 * Returns a list of cards.
556
-	 *
557
-	 * This method should work identical to getCard, but instead return all the
558
-	 * cards in the list as an array.
559
-	 *
560
-	 * If the backend supports this, it may allow for some speed-ups.
561
-	 *
562
-	 * @param mixed $addressBookId
563
-	 * @param string[] $uris
564
-	 * @return array
565
-	 */
566
-	public function getMultipleCards($addressBookId, array $uris) {
567
-		if (empty($uris)) {
568
-			return [];
569
-		}
570
-
571
-		$chunks = array_chunk($uris, 100);
572
-		$cards = [];
573
-
574
-		$query = $this->db->getQueryBuilder();
575
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
576
-			->from($this->dbCardsTable)
577
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
578
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
579
-
580
-		foreach ($chunks as $uris) {
581
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
582
-			$result = $query->execute();
583
-
584
-			while ($row = $result->fetch()) {
585
-				$row['etag'] = '"' . $row['etag'] . '"';
586
-
587
-				$modified = false;
588
-				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
589
-				if ($modified) {
590
-					$row['size'] = strlen($row['carddata']);
591
-				}
592
-
593
-				$cards[] = $row;
594
-			}
595
-			$result->closeCursor();
596
-		}
597
-		return $cards;
598
-	}
599
-
600
-	/**
601
-	 * Creates a new card.
602
-	 *
603
-	 * The addressbook id will be passed as the first argument. This is the
604
-	 * same id as it is returned from the getAddressBooksForUser method.
605
-	 *
606
-	 * The cardUri is a base uri, and doesn't include the full path. The
607
-	 * cardData argument is the vcard body, and is passed as a string.
608
-	 *
609
-	 * It is possible to return an ETag from this method. This ETag is for the
610
-	 * newly created resource, and must be enclosed with double quotes (that
611
-	 * is, the string itself must contain the double quotes).
612
-	 *
613
-	 * You should only return the ETag if you store the carddata as-is. If a
614
-	 * subsequent GET request on the same card does not have the same body,
615
-	 * byte-by-byte and you did return an ETag here, clients tend to get
616
-	 * confused.
617
-	 *
618
-	 * If you don't return an ETag, you can just return null.
619
-	 *
620
-	 * @param mixed $addressBookId
621
-	 * @param string $cardUri
622
-	 * @param string $cardData
623
-	 * @return string
624
-	 */
625
-	public function createCard($addressBookId, $cardUri, $cardData) {
626
-		$etag = md5($cardData);
627
-		$uid = $this->getUID($cardData);
628
-
629
-		$q = $this->db->getQueryBuilder();
630
-		$q->select('uid')
631
-			->from($this->dbCardsTable)
632
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
633
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
634
-			->setMaxResults(1);
635
-		$result = $q->execute();
636
-		$count = (bool) $result->fetchColumn();
637
-		$result->closeCursor();
638
-		if ($count) {
639
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
640
-		}
641
-
642
-		$query = $this->db->getQueryBuilder();
643
-		$query->insert('cards')
644
-			->values([
645
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
646
-				'uri' => $query->createNamedParameter($cardUri),
647
-				'lastmodified' => $query->createNamedParameter(time()),
648
-				'addressbookid' => $query->createNamedParameter($addressBookId),
649
-				'size' => $query->createNamedParameter(strlen($cardData)),
650
-				'etag' => $query->createNamedParameter($etag),
651
-				'uid' => $query->createNamedParameter($uid),
652
-			])
653
-			->execute();
654
-
655
-		$this->addChange($addressBookId, $cardUri, 1);
656
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
657
-
658
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
659
-			new GenericEvent(null, [
660
-				'addressBookId' => $addressBookId,
661
-				'cardUri' => $cardUri,
662
-				'cardData' => $cardData]));
663
-
664
-		return '"' . $etag . '"';
665
-	}
666
-
667
-	/**
668
-	 * Updates a card.
669
-	 *
670
-	 * The addressbook id will be passed as the first argument. This is the
671
-	 * same id as it is returned from the getAddressBooksForUser method.
672
-	 *
673
-	 * The cardUri is a base uri, and doesn't include the full path. The
674
-	 * cardData argument is the vcard body, and is passed as a string.
675
-	 *
676
-	 * It is possible to return an ETag from this method. This ETag should
677
-	 * match that of the updated resource, and must be enclosed with double
678
-	 * quotes (that is: the string itself must contain the actual quotes).
679
-	 *
680
-	 * You should only return the ETag if you store the carddata as-is. If a
681
-	 * subsequent GET request on the same card does not have the same body,
682
-	 * byte-by-byte and you did return an ETag here, clients tend to get
683
-	 * confused.
684
-	 *
685
-	 * If you don't return an ETag, you can just return null.
686
-	 *
687
-	 * @param mixed $addressBookId
688
-	 * @param string $cardUri
689
-	 * @param string $cardData
690
-	 * @return string
691
-	 */
692
-	public function updateCard($addressBookId, $cardUri, $cardData) {
693
-		$uid = $this->getUID($cardData);
694
-		$etag = md5($cardData);
695
-		$query = $this->db->getQueryBuilder();
696
-		$query->update($this->dbCardsTable)
697
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
698
-			->set('lastmodified', $query->createNamedParameter(time()))
699
-			->set('size', $query->createNamedParameter(strlen($cardData)))
700
-			->set('etag', $query->createNamedParameter($etag))
701
-			->set('uid', $query->createNamedParameter($uid))
702
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
703
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
704
-			->execute();
705
-
706
-		$this->addChange($addressBookId, $cardUri, 2);
707
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
708
-
709
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
710
-			new GenericEvent(null, [
711
-				'addressBookId' => $addressBookId,
712
-				'cardUri' => $cardUri,
713
-				'cardData' => $cardData]));
714
-
715
-		return '"' . $etag . '"';
716
-	}
717
-
718
-	/**
719
-	 * Deletes a card
720
-	 *
721
-	 * @param mixed $addressBookId
722
-	 * @param string $cardUri
723
-	 * @return bool
724
-	 */
725
-	public function deleteCard($addressBookId, $cardUri) {
726
-		try {
727
-			$cardId = $this->getCardId($addressBookId, $cardUri);
728
-		} catch (\InvalidArgumentException $e) {
729
-			$cardId = null;
730
-		}
731
-		$query = $this->db->getQueryBuilder();
732
-		$ret = $query->delete($this->dbCardsTable)
733
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
734
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
735
-			->execute();
736
-
737
-		$this->addChange($addressBookId, $cardUri, 3);
738
-
739
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
740
-			new GenericEvent(null, [
741
-				'addressBookId' => $addressBookId,
742
-				'cardUri' => $cardUri]));
743
-
744
-		if ($ret === 1) {
745
-			if ($cardId !== null) {
746
-				$this->purgeProperties($addressBookId, $cardId);
747
-			}
748
-			return true;
749
-		}
750
-
751
-		return false;
752
-	}
753
-
754
-	/**
755
-	 * The getChanges method returns all the changes that have happened, since
756
-	 * the specified syncToken in the specified address book.
757
-	 *
758
-	 * This function should return an array, such as the following:
759
-	 *
760
-	 * [
761
-	 *   'syncToken' => 'The current synctoken',
762
-	 *   'added'   => [
763
-	 *      'new.txt',
764
-	 *   ],
765
-	 *   'modified'   => [
766
-	 *      'modified.txt',
767
-	 *   ],
768
-	 *   'deleted' => [
769
-	 *      'foo.php.bak',
770
-	 *      'old.txt'
771
-	 *   ]
772
-	 * ];
773
-	 *
774
-	 * The returned syncToken property should reflect the *current* syncToken
775
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
776
-	 * property. This is needed here too, to ensure the operation is atomic.
777
-	 *
778
-	 * If the $syncToken argument is specified as null, this is an initial
779
-	 * sync, and all members should be reported.
780
-	 *
781
-	 * The modified property is an array of nodenames that have changed since
782
-	 * the last token.
783
-	 *
784
-	 * The deleted property is an array with nodenames, that have been deleted
785
-	 * from collection.
786
-	 *
787
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
788
-	 * 1, you only have to report changes that happened only directly in
789
-	 * immediate descendants. If it's 2, it should also include changes from
790
-	 * the nodes below the child collections. (grandchildren)
791
-	 *
792
-	 * The $limit argument allows a client to specify how many results should
793
-	 * be returned at most. If the limit is not specified, it should be treated
794
-	 * as infinite.
795
-	 *
796
-	 * If the limit (infinite or not) is higher than you're willing to return,
797
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
798
-	 *
799
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
800
-	 * return null.
801
-	 *
802
-	 * The limit is 'suggestive'. You are free to ignore it.
803
-	 *
804
-	 * @param string $addressBookId
805
-	 * @param string $syncToken
806
-	 * @param int $syncLevel
807
-	 * @param int $limit
808
-	 * @return array
809
-	 */
810
-	public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
811
-		// Current synctoken
812
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
813
-		$stmt->execute([ $addressBookId ]);
814
-		$currentToken = $stmt->fetchColumn(0);
815
-
816
-		if (is_null($currentToken)) {
817
-			return null;
818
-		}
819
-
820
-		$result = [
821
-			'syncToken' => $currentToken,
822
-			'added'     => [],
823
-			'modified'  => [],
824
-			'deleted'   => [],
825
-		];
826
-
827
-		if ($syncToken) {
828
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
829
-			if ($limit>0) {
830
-				$query .= " LIMIT " . (int)$limit;
831
-			}
832
-
833
-			// Fetching all changes
834
-			$stmt = $this->db->prepare($query);
835
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
836
-
837
-			$changes = [];
838
-
839
-			// This loop ensures that any duplicates are overwritten, only the
840
-			// last change on a node is relevant.
841
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
842
-				$changes[$row['uri']] = $row['operation'];
843
-			}
844
-
845
-			foreach ($changes as $uri => $operation) {
846
-				switch ($operation) {
847
-					case 1:
848
-						$result['added'][] = $uri;
849
-						break;
850
-					case 2:
851
-						$result['modified'][] = $uri;
852
-						break;
853
-					case 3:
854
-						$result['deleted'][] = $uri;
855
-						break;
856
-				}
857
-			}
858
-		} else {
859
-			// No synctoken supplied, this is the initial sync.
860
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
861
-			$stmt = $this->db->prepare($query);
862
-			$stmt->execute([$addressBookId]);
863
-
864
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
865
-		}
866
-		return $result;
867
-	}
868
-
869
-	/**
870
-	 * Adds a change record to the addressbookchanges table.
871
-	 *
872
-	 * @param mixed $addressBookId
873
-	 * @param string $objectUri
874
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
875
-	 * @return void
876
-	 */
877
-	protected function addChange($addressBookId, $objectUri, $operation) {
878
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
879
-		$stmt = $this->db->prepare($sql);
880
-		$stmt->execute([
881
-			$objectUri,
882
-			$addressBookId,
883
-			$operation,
884
-			$addressBookId
885
-		]);
886
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
887
-		$stmt->execute([
888
-			$addressBookId
889
-		]);
890
-	}
891
-
892
-	/**
893
-	 * @param resource|string $cardData
894
-	 * @param bool $modified
895
-	 * @return string
896
-	 */
897
-	private function readBlob($cardData, &$modified=false) {
898
-		if (is_resource($cardData)) {
899
-			$cardData = stream_get_contents($cardData);
900
-		}
901
-
902
-		$cardDataArray = explode("\r\n", $cardData);
903
-
904
-		$cardDataFiltered = [];
905
-		$removingPhoto = false;
906
-		foreach ($cardDataArray as $line) {
907
-			if (strpos($line, 'PHOTO:data:') === 0
908
-				&& strpos($line, 'PHOTO:data:image/') !== 0) {
909
-				// Filter out PHOTO data of non-images
910
-				$removingPhoto = true;
911
-				$modified = true;
912
-				continue;
913
-			}
914
-
915
-			if ($removingPhoto) {
916
-				if (strpos($line, ' ') === 0) {
917
-					continue;
918
-				}
919
-				// No leading space means this is a new property
920
-				$removingPhoto = false;
921
-			}
922
-
923
-			$cardDataFiltered[] = $line;
924
-		}
925
-
926
-		return implode("\r\n", $cardDataFiltered);
927
-	}
928
-
929
-	/**
930
-	 * @param IShareable $shareable
931
-	 * @param string[] $add
932
-	 * @param string[] $remove
933
-	 */
934
-	public function updateShares(IShareable $shareable, $add, $remove) {
935
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
936
-	}
937
-
938
-	/**
939
-	 * search contact
940
-	 *
941
-	 * @param int $addressBookId
942
-	 * @param string $pattern which should match within the $searchProperties
943
-	 * @param array $searchProperties defines the properties within the query pattern should match
944
-	 * @param array $options = array() to define the search behavior
945
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
946
-	 * @return array an array of contacts which are arrays of key-value-pairs
947
-	 */
948
-	public function search($addressBookId, $pattern, $searchProperties, $options = []) {
949
-		$query = $this->db->getQueryBuilder();
950
-		$query2 = $this->db->getQueryBuilder();
951
-
952
-		$query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
953
-		$query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
954
-		$or = $query2->expr()->orX();
955
-		foreach ($searchProperties as $property) {
956
-			$or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
957
-		}
958
-		$query2->andWhere($or);
959
-
960
-		// No need for like when the pattern is empty
961
-		if ('' !== $pattern) {
962
-			if (\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
963
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
964
-			} else {
965
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
966
-			}
967
-		}
968
-		if (isset($options['limit'])) {
969
-			$query2->setMaxResults($options['limit']);
970
-		}
971
-		if (isset($options['offset'])) {
972
-			$query2->setFirstResult($options['offset']);
973
-		}
974
-
975
-		$query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
976
-			->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
977
-
978
-		$result = $query->execute();
979
-		$cards = $result->fetchAll();
980
-
981
-		$result->closeCursor();
982
-
983
-		return array_map(function ($array) {
984
-			$modified = false;
985
-			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
986
-			if ($modified) {
987
-				$array['size'] = strlen($array['carddata']);
988
-			}
989
-			return $array;
990
-		}, $cards);
991
-	}
992
-
993
-	/**
994
-	 * @param int $bookId
995
-	 * @param string $name
996
-	 * @return array
997
-	 */
998
-	public function collectCardProperties($bookId, $name) {
999
-		$query = $this->db->getQueryBuilder();
1000
-		$result = $query->selectDistinct('value')
1001
-			->from($this->dbCardsPropertiesTable)
1002
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1003
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1004
-			->execute();
1005
-
1006
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
1007
-		$result->closeCursor();
1008
-
1009
-		return $all;
1010
-	}
1011
-
1012
-	/**
1013
-	 * get URI from a given contact
1014
-	 *
1015
-	 * @param int $id
1016
-	 * @return string
1017
-	 */
1018
-	public function getCardUri($id) {
1019
-		$query = $this->db->getQueryBuilder();
1020
-		$query->select('uri')->from($this->dbCardsTable)
1021
-				->where($query->expr()->eq('id', $query->createParameter('id')))
1022
-				->setParameter('id', $id);
1023
-
1024
-		$result = $query->execute();
1025
-		$uri = $result->fetch();
1026
-		$result->closeCursor();
1027
-
1028
-		if (!isset($uri['uri'])) {
1029
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1030
-		}
1031
-
1032
-		return $uri['uri'];
1033
-	}
1034
-
1035
-	/**
1036
-	 * return contact with the given URI
1037
-	 *
1038
-	 * @param int $addressBookId
1039
-	 * @param string $uri
1040
-	 * @returns array
1041
-	 */
1042
-	public function getContact($addressBookId, $uri) {
1043
-		$result = [];
1044
-		$query = $this->db->getQueryBuilder();
1045
-		$query->select('*')->from($this->dbCardsTable)
1046
-				->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1047
-				->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1048
-		$queryResult = $query->execute();
1049
-		$contact = $queryResult->fetch();
1050
-		$queryResult->closeCursor();
1051
-
1052
-		if (is_array($contact)) {
1053
-			$modified = false;
1054
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1055
-			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1056
-			if ($modified) {
1057
-				$contact['size'] = strlen($contact['carddata']);
1058
-			}
1059
-
1060
-			$result = $contact;
1061
-		}
1062
-
1063
-		return $result;
1064
-	}
1065
-
1066
-	/**
1067
-	 * Returns the list of people whom this address book is shared with.
1068
-	 *
1069
-	 * Every element in this array should have the following properties:
1070
-	 *   * href - Often a mailto: address
1071
-	 *   * commonName - Optional, for example a first + last name
1072
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1073
-	 *   * readOnly - boolean
1074
-	 *   * summary - Optional, a description for the share
1075
-	 *
1076
-	 * @return array
1077
-	 */
1078
-	public function getShares($addressBookId) {
1079
-		return $this->sharingBackend->getShares($addressBookId);
1080
-	}
1081
-
1082
-	/**
1083
-	 * update properties table
1084
-	 *
1085
-	 * @param int $addressBookId
1086
-	 * @param string $cardUri
1087
-	 * @param string $vCardSerialized
1088
-	 */
1089
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1090
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1091
-		$vCard = $this->readCard($vCardSerialized);
1092
-
1093
-		$this->purgeProperties($addressBookId, $cardId);
1094
-
1095
-		$query = $this->db->getQueryBuilder();
1096
-		$query->insert($this->dbCardsPropertiesTable)
1097
-			->values(
1098
-				[
1099
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1100
-					'cardid' => $query->createNamedParameter($cardId),
1101
-					'name' => $query->createParameter('name'),
1102
-					'value' => $query->createParameter('value'),
1103
-					'preferred' => $query->createParameter('preferred')
1104
-				]
1105
-			);
1106
-
1107
-		foreach ($vCard->children() as $property) {
1108
-			if (!in_array($property->name, self::$indexProperties)) {
1109
-				continue;
1110
-			}
1111
-			$preferred = 0;
1112
-			foreach ($property->parameters as $parameter) {
1113
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1114
-					$preferred = 1;
1115
-					break;
1116
-				}
1117
-			}
1118
-			$query->setParameter('name', $property->name);
1119
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1120
-			$query->setParameter('preferred', $preferred);
1121
-			$query->execute();
1122
-		}
1123
-	}
1124
-
1125
-	/**
1126
-	 * read vCard data into a vCard object
1127
-	 *
1128
-	 * @param string $cardData
1129
-	 * @return VCard
1130
-	 */
1131
-	protected function readCard($cardData) {
1132
-		return  Reader::read($cardData);
1133
-	}
1134
-
1135
-	/**
1136
-	 * delete all properties from a given card
1137
-	 *
1138
-	 * @param int $addressBookId
1139
-	 * @param int $cardId
1140
-	 */
1141
-	protected function purgeProperties($addressBookId, $cardId) {
1142
-		$query = $this->db->getQueryBuilder();
1143
-		$query->delete($this->dbCardsPropertiesTable)
1144
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1145
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1146
-		$query->execute();
1147
-	}
1148
-
1149
-	/**
1150
-	 * get ID from a given contact
1151
-	 *
1152
-	 * @param int $addressBookId
1153
-	 * @param string $uri
1154
-	 * @return int
1155
-	 */
1156
-	protected function getCardId($addressBookId, $uri) {
1157
-		$query = $this->db->getQueryBuilder();
1158
-		$query->select('id')->from($this->dbCardsTable)
1159
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1160
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1161
-
1162
-		$result = $query->execute();
1163
-		$cardIds = $result->fetch();
1164
-		$result->closeCursor();
1165
-
1166
-		if (!isset($cardIds['id'])) {
1167
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1168
-		}
1169
-
1170
-		return (int)$cardIds['id'];
1171
-	}
1172
-
1173
-	/**
1174
-	 * For shared address books the sharee is set in the ACL of the address book
1175
-	 * @param $addressBookId
1176
-	 * @param $acl
1177
-	 * @return array
1178
-	 */
1179
-	public function applyShareAcl($addressBookId, $acl) {
1180
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1181
-	}
1182
-
1183
-	private function convertPrincipal($principalUri, $toV2) {
1184
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1185
-			list(, $name) = \Sabre\Uri\split($principalUri);
1186
-			if ($toV2 === true) {
1187
-				return "principals/users/$name";
1188
-			}
1189
-			return "principals/$name";
1190
-		}
1191
-		return $principalUri;
1192
-	}
1193
-
1194
-	private function addOwnerPrincipal(&$addressbookInfo) {
1195
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1196
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1197
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1198
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1199
-		} else {
1200
-			$uri = $addressbookInfo['principaluri'];
1201
-		}
1202
-
1203
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1204
-		if (isset($principalInformation['{DAV:}displayname'])) {
1205
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1206
-		}
1207
-	}
1208
-
1209
-	/**
1210
-	 * Extract UID from vcard
1211
-	 *
1212
-	 * @param string $cardData the vcard raw data
1213
-	 * @return string the uid
1214
-	 * @throws BadRequest if no UID is available
1215
-	 */
1216
-	private function getUID($cardData) {
1217
-		if ($cardData != '') {
1218
-			$vCard = Reader::read($cardData);
1219
-			if ($vCard->UID) {
1220
-				$uid = $vCard->UID->getValue();
1221
-				return $uid;
1222
-			}
1223
-			// should already be handled, but just in case
1224
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1225
-		}
1226
-		// should already be handled, but just in case
1227
-		throw new BadRequest('vCard can not be empty');
1228
-	}
58
+    public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
59
+    public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
60
+
61
+    /** @var Principal */
62
+    private $principalBackend;
63
+
64
+    /** @var string */
65
+    private $dbCardsTable = 'cards';
66
+
67
+    /** @var string */
68
+    private $dbCardsPropertiesTable = 'cards_properties';
69
+
70
+    /** @var IDBConnection */
71
+    private $db;
72
+
73
+    /** @var Backend */
74
+    private $sharingBackend;
75
+
76
+    /** @var array properties to index */
77
+    public static $indexProperties = [
78
+        'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
79
+        'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
80
+
81
+    /**
82
+     * @var string[] Map of uid => display name
83
+     */
84
+    protected $userDisplayNames;
85
+
86
+    /** @var IUserManager */
87
+    private $userManager;
88
+
89
+    /** @var EventDispatcherInterface */
90
+    private $dispatcher;
91
+
92
+    /**
93
+     * CardDavBackend constructor.
94
+     *
95
+     * @param IDBConnection $db
96
+     * @param Principal $principalBackend
97
+     * @param IUserManager $userManager
98
+     * @param IGroupManager $groupManager
99
+     * @param EventDispatcherInterface $dispatcher
100
+     */
101
+    public function __construct(IDBConnection $db,
102
+                                Principal $principalBackend,
103
+                                IUserManager $userManager,
104
+                                IGroupManager $groupManager,
105
+                                EventDispatcherInterface $dispatcher) {
106
+        $this->db = $db;
107
+        $this->principalBackend = $principalBackend;
108
+        $this->userManager = $userManager;
109
+        $this->dispatcher = $dispatcher;
110
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
111
+    }
112
+
113
+    /**
114
+     * Return the number of address books for a principal
115
+     *
116
+     * @param $principalUri
117
+     * @return int
118
+     */
119
+    public function getAddressBooksForUserCount($principalUri) {
120
+        $principalUri = $this->convertPrincipal($principalUri, true);
121
+        $query = $this->db->getQueryBuilder();
122
+        $query->select($query->func()->count('*'))
123
+            ->from('addressbooks')
124
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125
+
126
+        return (int)$query->execute()->fetchColumn();
127
+    }
128
+
129
+    /**
130
+     * Returns the list of address books for a specific user.
131
+     *
132
+     * Every addressbook should have the following properties:
133
+     *   id - an arbitrary unique id
134
+     *   uri - the 'basename' part of the url
135
+     *   principaluri - Same as the passed parameter
136
+     *
137
+     * Any additional clark-notation property may be passed besides this. Some
138
+     * common ones are :
139
+     *   {DAV:}displayname
140
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
141
+     *   {http://calendarserver.org/ns/}getctag
142
+     *
143
+     * @param string $principalUri
144
+     * @return array
145
+     */
146
+    public function getAddressBooksForUser($principalUri) {
147
+        $principalUriOriginal = $principalUri;
148
+        $principalUri = $this->convertPrincipal($principalUri, true);
149
+        $query = $this->db->getQueryBuilder();
150
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
151
+            ->from('addressbooks')
152
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
153
+
154
+        $addressBooks = [];
155
+
156
+        $result = $query->execute();
157
+        while ($row = $result->fetch()) {
158
+            $addressBooks[$row['id']] = [
159
+                'id'  => $row['id'],
160
+                'uri' => $row['uri'],
161
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162
+                '{DAV:}displayname' => $row['displayname'],
163
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
164
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
166
+            ];
167
+
168
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
169
+        }
170
+        $result->closeCursor();
171
+
172
+        // query for shared addressbooks
173
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175
+
176
+        $principals = array_map(function ($principal) {
177
+            return urldecode($principal);
178
+        }, $principals);
179
+        $principals[]= $principalUri;
180
+
181
+        $query = $this->db->getQueryBuilder();
182
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
183
+            ->from('dav_shares', 's')
184
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
185
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
186
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
187
+            ->setParameter('type', 'addressbook')
188
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189
+            ->execute();
190
+
191
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
192
+        while ($row = $result->fetch()) {
193
+            if ($row['principaluri'] === $principalUri) {
194
+                continue;
195
+            }
196
+
197
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
198
+            if (isset($addressBooks[$row['id']])) {
199
+                if ($readOnly) {
200
+                    // New share can not have more permissions then the old one.
201
+                    continue;
202
+                }
203
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
204
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
205
+                    // Old share is already read-write, no more permissions can be gained
206
+                    continue;
207
+                }
208
+            }
209
+
210
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
+            $uri = $row['uri'] . '_shared_by_' . $name;
212
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
213
+
214
+            $addressBooks[$row['id']] = [
215
+                'id'  => $row['id'],
216
+                'uri' => $uri,
217
+                'principaluri' => $principalUriOriginal,
218
+                '{DAV:}displayname' => $displayName,
219
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
220
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
223
+                $readOnlyPropertyName => $readOnly,
224
+            ];
225
+
226
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
227
+        }
228
+        $result->closeCursor();
229
+
230
+        return array_values($addressBooks);
231
+    }
232
+
233
+    public function getUsersOwnAddressBooks($principalUri) {
234
+        $principalUri = $this->convertPrincipal($principalUri, true);
235
+        $query = $this->db->getQueryBuilder();
236
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
237
+                ->from('addressbooks')
238
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
239
+
240
+        $addressBooks = [];
241
+
242
+        $result = $query->execute();
243
+        while ($row = $result->fetch()) {
244
+            $addressBooks[$row['id']] = [
245
+                'id'  => $row['id'],
246
+                'uri' => $row['uri'],
247
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248
+                '{DAV:}displayname' => $row['displayname'],
249
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
250
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
252
+            ];
253
+
254
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
255
+        }
256
+        $result->closeCursor();
257
+
258
+        return array_values($addressBooks);
259
+    }
260
+
261
+    private function getUserDisplayName($uid) {
262
+        if (!isset($this->userDisplayNames[$uid])) {
263
+            $user = $this->userManager->get($uid);
264
+
265
+            if ($user instanceof IUser) {
266
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
267
+            } else {
268
+                $this->userDisplayNames[$uid] = $uid;
269
+            }
270
+        }
271
+
272
+        return $this->userDisplayNames[$uid];
273
+    }
274
+
275
+    /**
276
+     * @param int $addressBookId
277
+     */
278
+    public function getAddressBookById($addressBookId) {
279
+        $query = $this->db->getQueryBuilder();
280
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
281
+            ->from('addressbooks')
282
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
283
+            ->execute();
284
+
285
+        $row = $result->fetch();
286
+        $result->closeCursor();
287
+        if ($row === false) {
288
+            return null;
289
+        }
290
+
291
+        $addressBook = [
292
+            'id'  => $row['id'],
293
+            'uri' => $row['uri'],
294
+            'principaluri' => $row['principaluri'],
295
+            '{DAV:}displayname' => $row['displayname'],
296
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
297
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
299
+        ];
300
+
301
+        $this->addOwnerPrincipal($addressBook);
302
+
303
+        return $addressBook;
304
+    }
305
+
306
+    /**
307
+     * @param $addressBookUri
308
+     * @return array|null
309
+     */
310
+    public function getAddressBooksByUri($principal, $addressBookUri) {
311
+        $query = $this->db->getQueryBuilder();
312
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
313
+            ->from('addressbooks')
314
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
315
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
316
+            ->setMaxResults(1)
317
+            ->execute();
318
+
319
+        $row = $result->fetch();
320
+        $result->closeCursor();
321
+        if ($row === false) {
322
+            return null;
323
+        }
324
+
325
+        $addressBook = [
326
+            'id'  => $row['id'],
327
+            'uri' => $row['uri'],
328
+            'principaluri' => $row['principaluri'],
329
+            '{DAV:}displayname' => $row['displayname'],
330
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
331
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
333
+        ];
334
+
335
+        $this->addOwnerPrincipal($addressBook);
336
+
337
+        return $addressBook;
338
+    }
339
+
340
+    /**
341
+     * Updates properties for an address book.
342
+     *
343
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
344
+     * To do the actual updates, you must tell this object which properties
345
+     * you're going to process with the handle() method.
346
+     *
347
+     * Calling the handle method is like telling the PropPatch object "I
348
+     * promise I can handle updating this property".
349
+     *
350
+     * Read the PropPatch documentation for more info and examples.
351
+     *
352
+     * @param string $addressBookId
353
+     * @param \Sabre\DAV\PropPatch $propPatch
354
+     * @return void
355
+     */
356
+    public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357
+        $supportedProperties = [
358
+            '{DAV:}displayname',
359
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
360
+        ];
361
+
362
+        /**
363
+         * @suppress SqlInjectionChecker
364
+         */
365
+        $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
366
+            $updates = [];
367
+            foreach ($mutations as $property=>$newValue) {
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
+     * Creates a new address book
394
+     *
395
+     * @param string $principalUri
396
+     * @param string $url Just the 'basename' of the url.
397
+     * @param array $properties
398
+     * @return int
399
+     * @throws BadRequest
400
+     */
401
+    public function createAddressBook($principalUri, $url, array $properties) {
402
+        $values = [
403
+            'displayname' => null,
404
+            'description' => null,
405
+            'principaluri' => $principalUri,
406
+            'uri' => $url,
407
+            'synctoken' => 1
408
+        ];
409
+
410
+        foreach ($properties as $property=>$newValue) {
411
+            switch ($property) {
412
+                case '{DAV:}displayname':
413
+                    $values['displayname'] = $newValue;
414
+                    break;
415
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
416
+                    $values['description'] = $newValue;
417
+                    break;
418
+                default:
419
+                    throw new BadRequest('Unknown property: ' . $property);
420
+            }
421
+        }
422
+
423
+        // Fallback to make sure the displayname is set. Some clients may refuse
424
+        // to work with addressbooks not having a displayname.
425
+        if (is_null($values['displayname'])) {
426
+            $values['displayname'] = $url;
427
+        }
428
+
429
+        $query = $this->db->getQueryBuilder();
430
+        $query->insert('addressbooks')
431
+            ->values([
432
+                'uri' => $query->createParameter('uri'),
433
+                'displayname' => $query->createParameter('displayname'),
434
+                'description' => $query->createParameter('description'),
435
+                'principaluri' => $query->createParameter('principaluri'),
436
+                'synctoken' => $query->createParameter('synctoken'),
437
+            ])
438
+            ->setParameters($values)
439
+            ->execute();
440
+
441
+        return $query->getLastInsertId();
442
+    }
443
+
444
+    /**
445
+     * Deletes an entire addressbook and all its contents
446
+     *
447
+     * @param mixed $addressBookId
448
+     * @return void
449
+     */
450
+    public function deleteAddressBook($addressBookId) {
451
+        $query = $this->db->getQueryBuilder();
452
+        $query->delete($this->dbCardsTable)
453
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
454
+            ->setParameter('addressbookid', $addressBookId)
455
+            ->execute();
456
+
457
+        $query->delete('addressbookchanges')
458
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
459
+            ->setParameter('addressbookid', $addressBookId)
460
+            ->execute();
461
+
462
+        $query->delete('addressbooks')
463
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
464
+            ->setParameter('id', $addressBookId)
465
+            ->execute();
466
+
467
+        $this->sharingBackend->deleteAllShares($addressBookId);
468
+
469
+        $query->delete($this->dbCardsPropertiesTable)
470
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
471
+            ->execute();
472
+    }
473
+
474
+    /**
475
+     * Returns all cards for a specific addressbook id.
476
+     *
477
+     * This method should return the following properties for each card:
478
+     *   * carddata - raw vcard data
479
+     *   * uri - Some unique url
480
+     *   * lastmodified - A unix timestamp
481
+     *
482
+     * It's recommended to also return the following properties:
483
+     *   * etag - A unique etag. This must change every time the card changes.
484
+     *   * size - The size of the card in bytes.
485
+     *
486
+     * If these last two properties are provided, less time will be spent
487
+     * calculating them. If they are specified, you can also ommit carddata.
488
+     * This may speed up certain requests, especially with large cards.
489
+     *
490
+     * @param mixed $addressBookId
491
+     * @return array
492
+     */
493
+    public function getCards($addressBookId) {
494
+        $query = $this->db->getQueryBuilder();
495
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
496
+            ->from($this->dbCardsTable)
497
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
498
+
499
+        $cards = [];
500
+
501
+        $result = $query->execute();
502
+        while ($row = $result->fetch()) {
503
+            $row['etag'] = '"' . $row['etag'] . '"';
504
+
505
+            $modified = false;
506
+            $row['carddata'] = $this->readBlob($row['carddata'], $modified);
507
+            if ($modified) {
508
+                $row['size'] = strlen($row['carddata']);
509
+            }
510
+
511
+            $cards[] = $row;
512
+        }
513
+        $result->closeCursor();
514
+
515
+        return $cards;
516
+    }
517
+
518
+    /**
519
+     * Returns a specific card.
520
+     *
521
+     * The same set of properties must be returned as with getCards. The only
522
+     * exception is that 'carddata' is absolutely required.
523
+     *
524
+     * If the card does not exist, you must return false.
525
+     *
526
+     * @param mixed $addressBookId
527
+     * @param string $cardUri
528
+     * @return array
529
+     */
530
+    public function getCard($addressBookId, $cardUri) {
531
+        $query = $this->db->getQueryBuilder();
532
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
533
+            ->from($this->dbCardsTable)
534
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
535
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
536
+            ->setMaxResults(1);
537
+
538
+        $result = $query->execute();
539
+        $row = $result->fetch();
540
+        if (!$row) {
541
+            return false;
542
+        }
543
+        $row['etag'] = '"' . $row['etag'] . '"';
544
+
545
+        $modified = false;
546
+        $row['carddata'] = $this->readBlob($row['carddata'], $modified);
547
+        if ($modified) {
548
+            $row['size'] = strlen($row['carddata']);
549
+        }
550
+
551
+        return $row;
552
+    }
553
+
554
+    /**
555
+     * Returns a list of cards.
556
+     *
557
+     * This method should work identical to getCard, but instead return all the
558
+     * cards in the list as an array.
559
+     *
560
+     * If the backend supports this, it may allow for some speed-ups.
561
+     *
562
+     * @param mixed $addressBookId
563
+     * @param string[] $uris
564
+     * @return array
565
+     */
566
+    public function getMultipleCards($addressBookId, array $uris) {
567
+        if (empty($uris)) {
568
+            return [];
569
+        }
570
+
571
+        $chunks = array_chunk($uris, 100);
572
+        $cards = [];
573
+
574
+        $query = $this->db->getQueryBuilder();
575
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
576
+            ->from($this->dbCardsTable)
577
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
578
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
579
+
580
+        foreach ($chunks as $uris) {
581
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
582
+            $result = $query->execute();
583
+
584
+            while ($row = $result->fetch()) {
585
+                $row['etag'] = '"' . $row['etag'] . '"';
586
+
587
+                $modified = false;
588
+                $row['carddata'] = $this->readBlob($row['carddata'], $modified);
589
+                if ($modified) {
590
+                    $row['size'] = strlen($row['carddata']);
591
+                }
592
+
593
+                $cards[] = $row;
594
+            }
595
+            $result->closeCursor();
596
+        }
597
+        return $cards;
598
+    }
599
+
600
+    /**
601
+     * Creates a new card.
602
+     *
603
+     * The addressbook id will be passed as the first argument. This is the
604
+     * same id as it is returned from the getAddressBooksForUser method.
605
+     *
606
+     * The cardUri is a base uri, and doesn't include the full path. The
607
+     * cardData argument is the vcard body, and is passed as a string.
608
+     *
609
+     * It is possible to return an ETag from this method. This ETag is for the
610
+     * newly created resource, and must be enclosed with double quotes (that
611
+     * is, the string itself must contain the double quotes).
612
+     *
613
+     * You should only return the ETag if you store the carddata as-is. If a
614
+     * subsequent GET request on the same card does not have the same body,
615
+     * byte-by-byte and you did return an ETag here, clients tend to get
616
+     * confused.
617
+     *
618
+     * If you don't return an ETag, you can just return null.
619
+     *
620
+     * @param mixed $addressBookId
621
+     * @param string $cardUri
622
+     * @param string $cardData
623
+     * @return string
624
+     */
625
+    public function createCard($addressBookId, $cardUri, $cardData) {
626
+        $etag = md5($cardData);
627
+        $uid = $this->getUID($cardData);
628
+
629
+        $q = $this->db->getQueryBuilder();
630
+        $q->select('uid')
631
+            ->from($this->dbCardsTable)
632
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
633
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
634
+            ->setMaxResults(1);
635
+        $result = $q->execute();
636
+        $count = (bool) $result->fetchColumn();
637
+        $result->closeCursor();
638
+        if ($count) {
639
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
640
+        }
641
+
642
+        $query = $this->db->getQueryBuilder();
643
+        $query->insert('cards')
644
+            ->values([
645
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
646
+                'uri' => $query->createNamedParameter($cardUri),
647
+                'lastmodified' => $query->createNamedParameter(time()),
648
+                'addressbookid' => $query->createNamedParameter($addressBookId),
649
+                'size' => $query->createNamedParameter(strlen($cardData)),
650
+                'etag' => $query->createNamedParameter($etag),
651
+                'uid' => $query->createNamedParameter($uid),
652
+            ])
653
+            ->execute();
654
+
655
+        $this->addChange($addressBookId, $cardUri, 1);
656
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
657
+
658
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
659
+            new GenericEvent(null, [
660
+                'addressBookId' => $addressBookId,
661
+                'cardUri' => $cardUri,
662
+                'cardData' => $cardData]));
663
+
664
+        return '"' . $etag . '"';
665
+    }
666
+
667
+    /**
668
+     * Updates a card.
669
+     *
670
+     * The addressbook id will be passed as the first argument. This is the
671
+     * same id as it is returned from the getAddressBooksForUser method.
672
+     *
673
+     * The cardUri is a base uri, and doesn't include the full path. The
674
+     * cardData argument is the vcard body, and is passed as a string.
675
+     *
676
+     * It is possible to return an ETag from this method. This ETag should
677
+     * match that of the updated resource, and must be enclosed with double
678
+     * quotes (that is: the string itself must contain the actual quotes).
679
+     *
680
+     * You should only return the ETag if you store the carddata as-is. If a
681
+     * subsequent GET request on the same card does not have the same body,
682
+     * byte-by-byte and you did return an ETag here, clients tend to get
683
+     * confused.
684
+     *
685
+     * If you don't return an ETag, you can just return null.
686
+     *
687
+     * @param mixed $addressBookId
688
+     * @param string $cardUri
689
+     * @param string $cardData
690
+     * @return string
691
+     */
692
+    public function updateCard($addressBookId, $cardUri, $cardData) {
693
+        $uid = $this->getUID($cardData);
694
+        $etag = md5($cardData);
695
+        $query = $this->db->getQueryBuilder();
696
+        $query->update($this->dbCardsTable)
697
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
698
+            ->set('lastmodified', $query->createNamedParameter(time()))
699
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
700
+            ->set('etag', $query->createNamedParameter($etag))
701
+            ->set('uid', $query->createNamedParameter($uid))
702
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
703
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
704
+            ->execute();
705
+
706
+        $this->addChange($addressBookId, $cardUri, 2);
707
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
708
+
709
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
710
+            new GenericEvent(null, [
711
+                'addressBookId' => $addressBookId,
712
+                'cardUri' => $cardUri,
713
+                'cardData' => $cardData]));
714
+
715
+        return '"' . $etag . '"';
716
+    }
717
+
718
+    /**
719
+     * Deletes a card
720
+     *
721
+     * @param mixed $addressBookId
722
+     * @param string $cardUri
723
+     * @return bool
724
+     */
725
+    public function deleteCard($addressBookId, $cardUri) {
726
+        try {
727
+            $cardId = $this->getCardId($addressBookId, $cardUri);
728
+        } catch (\InvalidArgumentException $e) {
729
+            $cardId = null;
730
+        }
731
+        $query = $this->db->getQueryBuilder();
732
+        $ret = $query->delete($this->dbCardsTable)
733
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
734
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
735
+            ->execute();
736
+
737
+        $this->addChange($addressBookId, $cardUri, 3);
738
+
739
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
740
+            new GenericEvent(null, [
741
+                'addressBookId' => $addressBookId,
742
+                'cardUri' => $cardUri]));
743
+
744
+        if ($ret === 1) {
745
+            if ($cardId !== null) {
746
+                $this->purgeProperties($addressBookId, $cardId);
747
+            }
748
+            return true;
749
+        }
750
+
751
+        return false;
752
+    }
753
+
754
+    /**
755
+     * The getChanges method returns all the changes that have happened, since
756
+     * the specified syncToken in the specified address book.
757
+     *
758
+     * This function should return an array, such as the following:
759
+     *
760
+     * [
761
+     *   'syncToken' => 'The current synctoken',
762
+     *   'added'   => [
763
+     *      'new.txt',
764
+     *   ],
765
+     *   'modified'   => [
766
+     *      'modified.txt',
767
+     *   ],
768
+     *   'deleted' => [
769
+     *      'foo.php.bak',
770
+     *      'old.txt'
771
+     *   ]
772
+     * ];
773
+     *
774
+     * The returned syncToken property should reflect the *current* syncToken
775
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
776
+     * property. This is needed here too, to ensure the operation is atomic.
777
+     *
778
+     * If the $syncToken argument is specified as null, this is an initial
779
+     * sync, and all members should be reported.
780
+     *
781
+     * The modified property is an array of nodenames that have changed since
782
+     * the last token.
783
+     *
784
+     * The deleted property is an array with nodenames, that have been deleted
785
+     * from collection.
786
+     *
787
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
788
+     * 1, you only have to report changes that happened only directly in
789
+     * immediate descendants. If it's 2, it should also include changes from
790
+     * the nodes below the child collections. (grandchildren)
791
+     *
792
+     * The $limit argument allows a client to specify how many results should
793
+     * be returned at most. If the limit is not specified, it should be treated
794
+     * as infinite.
795
+     *
796
+     * If the limit (infinite or not) is higher than you're willing to return,
797
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
798
+     *
799
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
800
+     * return null.
801
+     *
802
+     * The limit is 'suggestive'. You are free to ignore it.
803
+     *
804
+     * @param string $addressBookId
805
+     * @param string $syncToken
806
+     * @param int $syncLevel
807
+     * @param int $limit
808
+     * @return array
809
+     */
810
+    public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
811
+        // Current synctoken
812
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
813
+        $stmt->execute([ $addressBookId ]);
814
+        $currentToken = $stmt->fetchColumn(0);
815
+
816
+        if (is_null($currentToken)) {
817
+            return null;
818
+        }
819
+
820
+        $result = [
821
+            'syncToken' => $currentToken,
822
+            'added'     => [],
823
+            'modified'  => [],
824
+            'deleted'   => [],
825
+        ];
826
+
827
+        if ($syncToken) {
828
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
829
+            if ($limit>0) {
830
+                $query .= " LIMIT " . (int)$limit;
831
+            }
832
+
833
+            // Fetching all changes
834
+            $stmt = $this->db->prepare($query);
835
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
836
+
837
+            $changes = [];
838
+
839
+            // This loop ensures that any duplicates are overwritten, only the
840
+            // last change on a node is relevant.
841
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
842
+                $changes[$row['uri']] = $row['operation'];
843
+            }
844
+
845
+            foreach ($changes as $uri => $operation) {
846
+                switch ($operation) {
847
+                    case 1:
848
+                        $result['added'][] = $uri;
849
+                        break;
850
+                    case 2:
851
+                        $result['modified'][] = $uri;
852
+                        break;
853
+                    case 3:
854
+                        $result['deleted'][] = $uri;
855
+                        break;
856
+                }
857
+            }
858
+        } else {
859
+            // No synctoken supplied, this is the initial sync.
860
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
861
+            $stmt = $this->db->prepare($query);
862
+            $stmt->execute([$addressBookId]);
863
+
864
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
865
+        }
866
+        return $result;
867
+    }
868
+
869
+    /**
870
+     * Adds a change record to the addressbookchanges table.
871
+     *
872
+     * @param mixed $addressBookId
873
+     * @param string $objectUri
874
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
875
+     * @return void
876
+     */
877
+    protected function addChange($addressBookId, $objectUri, $operation) {
878
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
879
+        $stmt = $this->db->prepare($sql);
880
+        $stmt->execute([
881
+            $objectUri,
882
+            $addressBookId,
883
+            $operation,
884
+            $addressBookId
885
+        ]);
886
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
887
+        $stmt->execute([
888
+            $addressBookId
889
+        ]);
890
+    }
891
+
892
+    /**
893
+     * @param resource|string $cardData
894
+     * @param bool $modified
895
+     * @return string
896
+     */
897
+    private function readBlob($cardData, &$modified=false) {
898
+        if (is_resource($cardData)) {
899
+            $cardData = stream_get_contents($cardData);
900
+        }
901
+
902
+        $cardDataArray = explode("\r\n", $cardData);
903
+
904
+        $cardDataFiltered = [];
905
+        $removingPhoto = false;
906
+        foreach ($cardDataArray as $line) {
907
+            if (strpos($line, 'PHOTO:data:') === 0
908
+                && strpos($line, 'PHOTO:data:image/') !== 0) {
909
+                // Filter out PHOTO data of non-images
910
+                $removingPhoto = true;
911
+                $modified = true;
912
+                continue;
913
+            }
914
+
915
+            if ($removingPhoto) {
916
+                if (strpos($line, ' ') === 0) {
917
+                    continue;
918
+                }
919
+                // No leading space means this is a new property
920
+                $removingPhoto = false;
921
+            }
922
+
923
+            $cardDataFiltered[] = $line;
924
+        }
925
+
926
+        return implode("\r\n", $cardDataFiltered);
927
+    }
928
+
929
+    /**
930
+     * @param IShareable $shareable
931
+     * @param string[] $add
932
+     * @param string[] $remove
933
+     */
934
+    public function updateShares(IShareable $shareable, $add, $remove) {
935
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
936
+    }
937
+
938
+    /**
939
+     * search contact
940
+     *
941
+     * @param int $addressBookId
942
+     * @param string $pattern which should match within the $searchProperties
943
+     * @param array $searchProperties defines the properties within the query pattern should match
944
+     * @param array $options = array() to define the search behavior
945
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
946
+     * @return array an array of contacts which are arrays of key-value-pairs
947
+     */
948
+    public function search($addressBookId, $pattern, $searchProperties, $options = []) {
949
+        $query = $this->db->getQueryBuilder();
950
+        $query2 = $this->db->getQueryBuilder();
951
+
952
+        $query2->selectDistinct('cp.cardid')->from($this->dbCardsPropertiesTable, 'cp');
953
+        $query2->andWhere($query2->expr()->eq('cp.addressbookid', $query->createNamedParameter($addressBookId)));
954
+        $or = $query2->expr()->orX();
955
+        foreach ($searchProperties as $property) {
956
+            $or->add($query2->expr()->eq('cp.name', $query->createNamedParameter($property)));
957
+        }
958
+        $query2->andWhere($or);
959
+
960
+        // No need for like when the pattern is empty
961
+        if ('' !== $pattern) {
962
+            if (\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
963
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
964
+            } else {
965
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
966
+            }
967
+        }
968
+        if (isset($options['limit'])) {
969
+            $query2->setMaxResults($options['limit']);
970
+        }
971
+        if (isset($options['offset'])) {
972
+            $query2->setFirstResult($options['offset']);
973
+        }
974
+
975
+        $query->select('c.carddata', 'c.uri')->from($this->dbCardsTable, 'c')
976
+            ->where($query->expr()->in('c.id', $query->createFunction($query2->getSQL())));
977
+
978
+        $result = $query->execute();
979
+        $cards = $result->fetchAll();
980
+
981
+        $result->closeCursor();
982
+
983
+        return array_map(function ($array) {
984
+            $modified = false;
985
+            $array['carddata'] = $this->readBlob($array['carddata'], $modified);
986
+            if ($modified) {
987
+                $array['size'] = strlen($array['carddata']);
988
+            }
989
+            return $array;
990
+        }, $cards);
991
+    }
992
+
993
+    /**
994
+     * @param int $bookId
995
+     * @param string $name
996
+     * @return array
997
+     */
998
+    public function collectCardProperties($bookId, $name) {
999
+        $query = $this->db->getQueryBuilder();
1000
+        $result = $query->selectDistinct('value')
1001
+            ->from($this->dbCardsPropertiesTable)
1002
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1003
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1004
+            ->execute();
1005
+
1006
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
1007
+        $result->closeCursor();
1008
+
1009
+        return $all;
1010
+    }
1011
+
1012
+    /**
1013
+     * get URI from a given contact
1014
+     *
1015
+     * @param int $id
1016
+     * @return string
1017
+     */
1018
+    public function getCardUri($id) {
1019
+        $query = $this->db->getQueryBuilder();
1020
+        $query->select('uri')->from($this->dbCardsTable)
1021
+                ->where($query->expr()->eq('id', $query->createParameter('id')))
1022
+                ->setParameter('id', $id);
1023
+
1024
+        $result = $query->execute();
1025
+        $uri = $result->fetch();
1026
+        $result->closeCursor();
1027
+
1028
+        if (!isset($uri['uri'])) {
1029
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
1030
+        }
1031
+
1032
+        return $uri['uri'];
1033
+    }
1034
+
1035
+    /**
1036
+     * return contact with the given URI
1037
+     *
1038
+     * @param int $addressBookId
1039
+     * @param string $uri
1040
+     * @returns array
1041
+     */
1042
+    public function getContact($addressBookId, $uri) {
1043
+        $result = [];
1044
+        $query = $this->db->getQueryBuilder();
1045
+        $query->select('*')->from($this->dbCardsTable)
1046
+                ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1047
+                ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1048
+        $queryResult = $query->execute();
1049
+        $contact = $queryResult->fetch();
1050
+        $queryResult->closeCursor();
1051
+
1052
+        if (is_array($contact)) {
1053
+            $modified = false;
1054
+            $contact['etag'] = '"' . $contact['etag'] . '"';
1055
+            $contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1056
+            if ($modified) {
1057
+                $contact['size'] = strlen($contact['carddata']);
1058
+            }
1059
+
1060
+            $result = $contact;
1061
+        }
1062
+
1063
+        return $result;
1064
+    }
1065
+
1066
+    /**
1067
+     * Returns the list of people whom this address book is shared with.
1068
+     *
1069
+     * Every element in this array should have the following properties:
1070
+     *   * href - Often a mailto: address
1071
+     *   * commonName - Optional, for example a first + last name
1072
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1073
+     *   * readOnly - boolean
1074
+     *   * summary - Optional, a description for the share
1075
+     *
1076
+     * @return array
1077
+     */
1078
+    public function getShares($addressBookId) {
1079
+        return $this->sharingBackend->getShares($addressBookId);
1080
+    }
1081
+
1082
+    /**
1083
+     * update properties table
1084
+     *
1085
+     * @param int $addressBookId
1086
+     * @param string $cardUri
1087
+     * @param string $vCardSerialized
1088
+     */
1089
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1090
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1091
+        $vCard = $this->readCard($vCardSerialized);
1092
+
1093
+        $this->purgeProperties($addressBookId, $cardId);
1094
+
1095
+        $query = $this->db->getQueryBuilder();
1096
+        $query->insert($this->dbCardsPropertiesTable)
1097
+            ->values(
1098
+                [
1099
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1100
+                    'cardid' => $query->createNamedParameter($cardId),
1101
+                    'name' => $query->createParameter('name'),
1102
+                    'value' => $query->createParameter('value'),
1103
+                    'preferred' => $query->createParameter('preferred')
1104
+                ]
1105
+            );
1106
+
1107
+        foreach ($vCard->children() as $property) {
1108
+            if (!in_array($property->name, self::$indexProperties)) {
1109
+                continue;
1110
+            }
1111
+            $preferred = 0;
1112
+            foreach ($property->parameters as $parameter) {
1113
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1114
+                    $preferred = 1;
1115
+                    break;
1116
+                }
1117
+            }
1118
+            $query->setParameter('name', $property->name);
1119
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1120
+            $query->setParameter('preferred', $preferred);
1121
+            $query->execute();
1122
+        }
1123
+    }
1124
+
1125
+    /**
1126
+     * read vCard data into a vCard object
1127
+     *
1128
+     * @param string $cardData
1129
+     * @return VCard
1130
+     */
1131
+    protected function readCard($cardData) {
1132
+        return  Reader::read($cardData);
1133
+    }
1134
+
1135
+    /**
1136
+     * delete all properties from a given card
1137
+     *
1138
+     * @param int $addressBookId
1139
+     * @param int $cardId
1140
+     */
1141
+    protected function purgeProperties($addressBookId, $cardId) {
1142
+        $query = $this->db->getQueryBuilder();
1143
+        $query->delete($this->dbCardsPropertiesTable)
1144
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1145
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1146
+        $query->execute();
1147
+    }
1148
+
1149
+    /**
1150
+     * get ID from a given contact
1151
+     *
1152
+     * @param int $addressBookId
1153
+     * @param string $uri
1154
+     * @return int
1155
+     */
1156
+    protected function getCardId($addressBookId, $uri) {
1157
+        $query = $this->db->getQueryBuilder();
1158
+        $query->select('id')->from($this->dbCardsTable)
1159
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1160
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1161
+
1162
+        $result = $query->execute();
1163
+        $cardIds = $result->fetch();
1164
+        $result->closeCursor();
1165
+
1166
+        if (!isset($cardIds['id'])) {
1167
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1168
+        }
1169
+
1170
+        return (int)$cardIds['id'];
1171
+    }
1172
+
1173
+    /**
1174
+     * For shared address books the sharee is set in the ACL of the address book
1175
+     * @param $addressBookId
1176
+     * @param $acl
1177
+     * @return array
1178
+     */
1179
+    public function applyShareAcl($addressBookId, $acl) {
1180
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1181
+    }
1182
+
1183
+    private function convertPrincipal($principalUri, $toV2) {
1184
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1185
+            list(, $name) = \Sabre\Uri\split($principalUri);
1186
+            if ($toV2 === true) {
1187
+                return "principals/users/$name";
1188
+            }
1189
+            return "principals/$name";
1190
+        }
1191
+        return $principalUri;
1192
+    }
1193
+
1194
+    private function addOwnerPrincipal(&$addressbookInfo) {
1195
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1196
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1197
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1198
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1199
+        } else {
1200
+            $uri = $addressbookInfo['principaluri'];
1201
+        }
1202
+
1203
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1204
+        if (isset($principalInformation['{DAV:}displayname'])) {
1205
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1206
+        }
1207
+    }
1208
+
1209
+    /**
1210
+     * Extract UID from vcard
1211
+     *
1212
+     * @param string $cardData the vcard raw data
1213
+     * @return string the uid
1214
+     * @throws BadRequest if no UID is available
1215
+     */
1216
+    private function getUID($cardData) {
1217
+        if ($cardData != '') {
1218
+            $vCard = Reader::read($cardData);
1219
+            if ($vCard->UID) {
1220
+                $uid = $vCard->UID->getValue();
1221
+                return $uid;
1222
+            }
1223
+            // should already be handled, but just in case
1224
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1225
+        }
1226
+        // should already be handled, but just in case
1227
+        throw new BadRequest('vCard can not be empty');
1228
+    }
1229 1229
 }
Please login to merge, or discard this patch.
Spacing   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -123,7 +123,7 @@  discard block
 block discarded – undo
123 123
 			->from('addressbooks')
124 124
 			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
125 125
 
126
-		return (int)$query->execute()->fetchColumn();
126
+		return (int) $query->execute()->fetchColumn();
127 127
 	}
128 128
 
129 129
 	/**
@@ -160,9 +160,9 @@  discard block
 block discarded – undo
160 160
 				'uri' => $row['uri'],
161 161
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
162 162
 				'{DAV:}displayname' => $row['displayname'],
163
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
163
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
164 164
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
165
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
165
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
166 166
 			];
167 167
 
168 168
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -173,10 +173,10 @@  discard block
 block discarded – undo
173 173
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
174 174
 		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
175 175
 
176
-		$principals = array_map(function ($principal) {
176
+		$principals = array_map(function($principal) {
177 177
 			return urldecode($principal);
178 178
 		}, $principals);
179
-		$principals[]= $principalUri;
179
+		$principals[] = $principalUri;
180 180
 
181 181
 		$query = $this->db->getQueryBuilder();
182 182
 		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
189 189
 			->execute();
190 190
 
191
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
191
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
192 192
 		while ($row = $result->fetch()) {
193 193
 			if ($row['principaluri'] === $principalUri) {
194 194
 				continue;
@@ -208,18 +208,18 @@  discard block
 block discarded – undo
208 208
 			}
209 209
 
210 210
 			list(, $name) = \Sabre\Uri\split($row['principaluri']);
211
-			$uri = $row['uri'] . '_shared_by_' . $name;
212
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
211
+			$uri = $row['uri'].'_shared_by_'.$name;
212
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
213 213
 
214 214
 			$addressBooks[$row['id']] = [
215 215
 				'id'  => $row['id'],
216 216
 				'uri' => $uri,
217 217
 				'principaluri' => $principalUriOriginal,
218 218
 				'{DAV:}displayname' => $displayName,
219
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
219
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
220 220
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
221
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
222
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
221
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
222
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
223 223
 				$readOnlyPropertyName => $readOnly,
224 224
 			];
225 225
 
@@ -246,9 +246,9 @@  discard block
 block discarded – undo
246 246
 				'uri' => $row['uri'],
247 247
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
248 248
 				'{DAV:}displayname' => $row['displayname'],
249
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
249
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
250 250
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
251
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
251
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
252 252
 			];
253 253
 
254 254
 			$this->addOwnerPrincipal($addressBooks[$row['id']]);
@@ -293,9 +293,9 @@  discard block
 block discarded – undo
293 293
 			'uri' => $row['uri'],
294 294
 			'principaluri' => $row['principaluri'],
295 295
 			'{DAV:}displayname' => $row['displayname'],
296
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
296
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
297 297
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
298
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
298
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
299 299
 		];
300 300
 
301 301
 		$this->addOwnerPrincipal($addressBook);
@@ -327,9 +327,9 @@  discard block
 block discarded – undo
327 327
 			'uri' => $row['uri'],
328 328
 			'principaluri' => $row['principaluri'],
329 329
 			'{DAV:}displayname' => $row['displayname'],
330
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
330
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
331 331
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
332
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
332
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
333 333
 		];
334 334
 
335 335
 		$this->addOwnerPrincipal($addressBook);
@@ -356,20 +356,20 @@  discard block
 block discarded – undo
356 356
 	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
357 357
 		$supportedProperties = [
358 358
 			'{DAV:}displayname',
359
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
359
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
360 360
 		];
361 361
 
362 362
 		/**
363 363
 		 * @suppress SqlInjectionChecker
364 364
 		 */
365
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
365
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
366 366
 			$updates = [];
367 367
 			foreach ($mutations as $property=>$newValue) {
368 368
 				switch ($property) {
369 369
 					case '{DAV:}displayname':
370 370
 						$updates['displayname'] = $newValue;
371 371
 						break;
372
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
372
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
373 373
 						$updates['description'] = $newValue;
374 374
 						break;
375 375
 				}
@@ -412,11 +412,11 @@  discard block
 block discarded – undo
412 412
 				case '{DAV:}displayname':
413 413
 					$values['displayname'] = $newValue;
414 414
 					break;
415
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
415
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
416 416
 					$values['description'] = $newValue;
417 417
 					break;
418 418
 				default:
419
-					throw new BadRequest('Unknown property: ' . $property);
419
+					throw new BadRequest('Unknown property: '.$property);
420 420
 			}
421 421
 		}
422 422
 
@@ -500,7 +500,7 @@  discard block
 block discarded – undo
500 500
 
501 501
 		$result = $query->execute();
502 502
 		while ($row = $result->fetch()) {
503
-			$row['etag'] = '"' . $row['etag'] . '"';
503
+			$row['etag'] = '"'.$row['etag'].'"';
504 504
 
505 505
 			$modified = false;
506 506
 			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -540,7 +540,7 @@  discard block
 block discarded – undo
540 540
 		if (!$row) {
541 541
 			return false;
542 542
 		}
543
-		$row['etag'] = '"' . $row['etag'] . '"';
543
+		$row['etag'] = '"'.$row['etag'].'"';
544 544
 
545 545
 		$modified = false;
546 546
 		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -582,7 +582,7 @@  discard block
 block discarded – undo
582 582
 			$result = $query->execute();
583 583
 
584 584
 			while ($row = $result->fetch()) {
585
-				$row['etag'] = '"' . $row['etag'] . '"';
585
+				$row['etag'] = '"'.$row['etag'].'"';
586 586
 
587 587
 				$modified = false;
588 588
 				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -661,7 +661,7 @@  discard block
 block discarded – undo
661 661
 				'cardUri' => $cardUri,
662 662
 				'cardData' => $cardData]));
663 663
 
664
-		return '"' . $etag . '"';
664
+		return '"'.$etag.'"';
665 665
 	}
666 666
 
667 667
 	/**
@@ -712,7 +712,7 @@  discard block
 block discarded – undo
712 712
 				'cardUri' => $cardUri,
713 713
 				'cardData' => $cardData]));
714 714
 
715
-		return '"' . $etag . '"';
715
+		return '"'.$etag.'"';
716 716
 	}
717 717
 
718 718
 	/**
@@ -810,7 +810,7 @@  discard block
 block discarded – undo
810 810
 	public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
811 811
 		// Current synctoken
812 812
 		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
813
-		$stmt->execute([ $addressBookId ]);
813
+		$stmt->execute([$addressBookId]);
814 814
 		$currentToken = $stmt->fetchColumn(0);
815 815
 
816 816
 		if (is_null($currentToken)) {
@@ -826,8 +826,8 @@  discard block
 block discarded – undo
826 826
 
827 827
 		if ($syncToken) {
828 828
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
829
-			if ($limit>0) {
830
-				$query .= " LIMIT " . (int)$limit;
829
+			if ($limit > 0) {
830
+				$query .= " LIMIT ".(int) $limit;
831 831
 			}
832 832
 
833 833
 			// Fetching all changes
@@ -894,7 +894,7 @@  discard block
 block discarded – undo
894 894
 	 * @param bool $modified
895 895
 	 * @return string
896 896
 	 */
897
-	private function readBlob($cardData, &$modified=false) {
897
+	private function readBlob($cardData, &$modified = false) {
898 898
 		if (is_resource($cardData)) {
899 899
 			$cardData = stream_get_contents($cardData);
900 900
 		}
@@ -962,7 +962,7 @@  discard block
 block discarded – undo
962 962
 			if (\array_key_exists('escape_like_param', $options) && $options['escape_like_param'] === false) {
963 963
 				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter($pattern)));
964 964
 			} else {
965
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
965
+				$query2->andWhere($query2->expr()->ilike('cp.value', $query->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
966 966
 			}
967 967
 		}
968 968
 		if (isset($options['limit'])) {
@@ -980,7 +980,7 @@  discard block
 block discarded – undo
980 980
 
981 981
 		$result->closeCursor();
982 982
 
983
-		return array_map(function ($array) {
983
+		return array_map(function($array) {
984 984
 			$modified = false;
985 985
 			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
986 986
 			if ($modified) {
@@ -1026,7 +1026,7 @@  discard block
 block discarded – undo
1026 1026
 		$result->closeCursor();
1027 1027
 
1028 1028
 		if (!isset($uri['uri'])) {
1029
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1029
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
1030 1030
 		}
1031 1031
 
1032 1032
 		return $uri['uri'];
@@ -1051,7 +1051,7 @@  discard block
 block discarded – undo
1051 1051
 
1052 1052
 		if (is_array($contact)) {
1053 1053
 			$modified = false;
1054
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1054
+			$contact['etag'] = '"'.$contact['etag'].'"';
1055 1055
 			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1056 1056
 			if ($modified) {
1057 1057
 				$contact['size'] = strlen($contact['carddata']);
@@ -1164,10 +1164,10 @@  discard block
 block discarded – undo
1164 1164
 		$result->closeCursor();
1165 1165
 
1166 1166
 		if (!isset($cardIds['id'])) {
1167
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1167
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1168 1168
 		}
1169 1169
 
1170
-		return (int)$cardIds['id'];
1170
+		return (int) $cardIds['id'];
1171 1171
 	}
1172 1172
 
1173 1173
 	/**
@@ -1192,8 +1192,8 @@  discard block
 block discarded – undo
1192 1192
 	}
1193 1193
 
1194 1194
 	private function addOwnerPrincipal(&$addressbookInfo) {
1195
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1196
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1195
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1196
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1197 1197
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1198 1198
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1199 1199
 		} else {
Please login to merge, or discard this patch.
apps/dav/lib/CardDAV/AddressBookImpl.php 1 patch
Indentation   +269 added lines, -269 removed lines patch added patch discarded remove patch
@@ -40,273 +40,273 @@
 block discarded – undo
40 40
 
41 41
 class AddressBookImpl implements IAddressBook {
42 42
 
43
-	/** @var CardDavBackend */
44
-	private $backend;
45
-
46
-	/** @var array */
47
-	private $addressBookInfo;
48
-
49
-	/** @var AddressBook */
50
-	private $addressBook;
51
-
52
-	/** @var IURLGenerator */
53
-	private $urlGenerator;
54
-
55
-	/**
56
-	 * AddressBookImpl constructor.
57
-	 *
58
-	 * @param AddressBook $addressBook
59
-	 * @param array $addressBookInfo
60
-	 * @param CardDavBackend $backend
61
-	 * @param IUrlGenerator $urlGenerator
62
-	 */
63
-	public function __construct(
64
-			AddressBook $addressBook,
65
-			array $addressBookInfo,
66
-			CardDavBackend $backend,
67
-			IURLGenerator $urlGenerator) {
68
-		$this->addressBook = $addressBook;
69
-		$this->addressBookInfo = $addressBookInfo;
70
-		$this->backend = $backend;
71
-		$this->urlGenerator = $urlGenerator;
72
-	}
73
-
74
-	/**
75
-	 * @return string defining the technical unique key
76
-	 * @since 5.0.0
77
-	 */
78
-	public function getKey() {
79
-		return $this->addressBookInfo['id'];
80
-	}
81
-
82
-	/**
83
-	 * @return string defining the unique uri
84
-	 * @since 16.0.0
85
-	 * @return string
86
-	 */
87
-	public function getUri(): string {
88
-		return $this->addressBookInfo['uri'];
89
-	}
90
-
91
-	/**
92
-	 * In comparison to getKey() this function returns a human readable (maybe translated) name
93
-	 *
94
-	 * @return mixed
95
-	 * @since 5.0.0
96
-	 */
97
-	public function getDisplayName() {
98
-		return $this->addressBookInfo['{DAV:}displayname'];
99
-	}
100
-
101
-	/**
102
-	 * @param string $pattern which should match within the $searchProperties
103
-	 * @param array $searchProperties defines the properties within the query pattern should match
104
-	 * @param array $options Options to define the output format and search behavior
105
-	 * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
106
-	 *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
107
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
108
-	 * 	- 'limit' - Set a numeric limit for the search results
109
-	 * 	- 'offset' - Set the offset for the limited search results
110
-	 * @return array an array of contacts which are arrays of key-value-pairs
111
-	 *  example result:
112
-	 *  [
113
-	 *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
114
-	 *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
115
-	 *	]
116
-	 * @since 5.0.0
117
-	 */
118
-	public function search($pattern, $searchProperties, $options) {
119
-		$results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
120
-
121
-		$withTypes = \array_key_exists('types', $options) && $options['types'] === true;
122
-
123
-		$vCards = [];
124
-		foreach ($results as $result) {
125
-			$vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
126
-		}
127
-
128
-		return $vCards;
129
-	}
130
-
131
-	/**
132
-	 * @param array $properties this array if key-value-pairs defines a contact
133
-	 * @return array an array representing the contact just created or updated
134
-	 * @since 5.0.0
135
-	 */
136
-	public function createOrUpdate($properties) {
137
-		$update = false;
138
-		if (!isset($properties['URI'])) { // create a new contact
139
-			$uid = $this->createUid();
140
-			$uri = $uid . '.vcf';
141
-			$vCard = $this->createEmptyVCard($uid);
142
-		} else { // update existing contact
143
-			$uri = $properties['URI'];
144
-			$vCardData = $this->backend->getCard($this->getKey(), $uri);
145
-			$vCard = $this->readCard($vCardData['carddata']);
146
-			$update = true;
147
-		}
148
-
149
-		foreach ($properties as $key => $value) {
150
-			$vCard->$key = $vCard->createProperty($key, $value);
151
-		}
152
-
153
-		if ($update) {
154
-			$this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
155
-		} else {
156
-			$this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
157
-		}
158
-
159
-		return $this->vCard2Array($uri, $vCard);
160
-	}
161
-
162
-	/**
163
-	 * @return mixed
164
-	 * @since 5.0.0
165
-	 */
166
-	public function getPermissions() {
167
-		$permissions = $this->addressBook->getACL();
168
-		$result = 0;
169
-		foreach ($permissions as $permission) {
170
-			switch ($permission['privilege']) {
171
-				case '{DAV:}read':
172
-					$result |= Constants::PERMISSION_READ;
173
-					break;
174
-				case '{DAV:}write':
175
-					$result |= Constants::PERMISSION_CREATE;
176
-					$result |= Constants::PERMISSION_UPDATE;
177
-					break;
178
-				case '{DAV:}all':
179
-					$result |= Constants::PERMISSION_ALL;
180
-					break;
181
-			}
182
-		}
183
-
184
-		return $result;
185
-	}
186
-
187
-	/**
188
-	 * @param object $id the unique identifier to a contact
189
-	 * @return bool successful or not
190
-	 * @since 5.0.0
191
-	 */
192
-	public function delete($id) {
193
-		$uri = $this->backend->getCardUri($id);
194
-		return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
195
-	}
196
-
197
-	/**
198
-	 * read vCard data into a vCard object
199
-	 *
200
-	 * @param string $cardData
201
-	 * @return VCard
202
-	 */
203
-	protected function readCard($cardData) {
204
-		return  Reader::read($cardData);
205
-	}
206
-
207
-	/**
208
-	 * create UID for contact
209
-	 *
210
-	 * @return string
211
-	 */
212
-	protected function createUid() {
213
-		do {
214
-			$uid = $this->getUid();
215
-			$contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
216
-		} while (!empty($contact));
217
-
218
-		return $uid;
219
-	}
220
-
221
-	/**
222
-	 * getUid is only there for testing, use createUid instead
223
-	 */
224
-	protected function getUid() {
225
-		return UUIDUtil::getUUID();
226
-	}
227
-
228
-	/**
229
-	 * create empty vcard
230
-	 *
231
-	 * @param string $uid
232
-	 * @return VCard
233
-	 */
234
-	protected function createEmptyVCard($uid) {
235
-		$vCard = new VCard();
236
-		$vCard->UID = $uid;
237
-		return $vCard;
238
-	}
239
-
240
-	/**
241
-	 * create array with all vCard properties
242
-	 *
243
-	 * @param string $uri
244
-	 * @param VCard $vCard
245
-	 * @param boolean $withTypes (optional) return the values as arrays of value/type pairs
246
-	 * @return array
247
-	 */
248
-	protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
249
-		$result = [
250
-			'URI' => $uri,
251
-		];
252
-
253
-		foreach ($vCard->children() as $property) {
254
-			if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
255
-				$url = $this->urlGenerator->getAbsoluteURL(
256
-					$this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
257
-				$url .= implode('/', [
258
-					'addressbooks',
259
-					substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
260
-					$this->addressBookInfo['uri'],
261
-					$uri
262
-				]) . '?photo';
263
-
264
-				$result['PHOTO'] = 'VALUE=uri:' . $url;
265
-			} elseif (in_array($property->name, ['URL', 'GEO', 'CLOUD', 'ADR', 'EMAIL', 'IMPP', 'TEL', 'X-SOCIALPROFILE', 'RELATED', 'LANG', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
266
-				if (!isset($result[$property->name])) {
267
-					$result[$property->name] = [];
268
-				}
269
-
270
-				$type = $this->getTypeFromProperty($property);
271
-				if ($withTypes) {
272
-					$result[$property->name][] = [
273
-						'type' => $type,
274
-						'value' => $property->getValue()
275
-					];
276
-				} else {
277
-					$result[$property->name][] = $property->getValue();
278
-				}
279
-			} else {
280
-				$result[$property->name] = $property->getValue();
281
-			}
282
-		}
283
-
284
-		if (
285
-			$this->addressBookInfo['principaluri'] === 'principals/system/system' && (
286
-				$this->addressBookInfo['uri'] === 'system' ||
287
-				$this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
288
-			)
289
-		) {
290
-			$result['isLocalSystemBook'] = true;
291
-		}
292
-		return $result;
293
-	}
294
-
295
-	/**
296
-	 * Get the type of the current property
297
-	 *
298
-	 * @param Property $property
299
-	 * @return null|string
300
-	 */
301
-	protected function getTypeFromProperty(Property $property) {
302
-		$parameters = $property->parameters();
303
-		// Type is the social network, when it's empty we don't need this.
304
-		if (isset($parameters['TYPE'])) {
305
-			/** @var \Sabre\VObject\Parameter $type */
306
-			$type = $parameters['TYPE'];
307
-			return $type->getValue();
308
-		}
309
-
310
-		return null;
311
-	}
43
+    /** @var CardDavBackend */
44
+    private $backend;
45
+
46
+    /** @var array */
47
+    private $addressBookInfo;
48
+
49
+    /** @var AddressBook */
50
+    private $addressBook;
51
+
52
+    /** @var IURLGenerator */
53
+    private $urlGenerator;
54
+
55
+    /**
56
+     * AddressBookImpl constructor.
57
+     *
58
+     * @param AddressBook $addressBook
59
+     * @param array $addressBookInfo
60
+     * @param CardDavBackend $backend
61
+     * @param IUrlGenerator $urlGenerator
62
+     */
63
+    public function __construct(
64
+            AddressBook $addressBook,
65
+            array $addressBookInfo,
66
+            CardDavBackend $backend,
67
+            IURLGenerator $urlGenerator) {
68
+        $this->addressBook = $addressBook;
69
+        $this->addressBookInfo = $addressBookInfo;
70
+        $this->backend = $backend;
71
+        $this->urlGenerator = $urlGenerator;
72
+    }
73
+
74
+    /**
75
+     * @return string defining the technical unique key
76
+     * @since 5.0.0
77
+     */
78
+    public function getKey() {
79
+        return $this->addressBookInfo['id'];
80
+    }
81
+
82
+    /**
83
+     * @return string defining the unique uri
84
+     * @since 16.0.0
85
+     * @return string
86
+     */
87
+    public function getUri(): string {
88
+        return $this->addressBookInfo['uri'];
89
+    }
90
+
91
+    /**
92
+     * In comparison to getKey() this function returns a human readable (maybe translated) name
93
+     *
94
+     * @return mixed
95
+     * @since 5.0.0
96
+     */
97
+    public function getDisplayName() {
98
+        return $this->addressBookInfo['{DAV:}displayname'];
99
+    }
100
+
101
+    /**
102
+     * @param string $pattern which should match within the $searchProperties
103
+     * @param array $searchProperties defines the properties within the query pattern should match
104
+     * @param array $options Options to define the output format and search behavior
105
+     * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
106
+     *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
107
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
108
+     * 	- 'limit' - Set a numeric limit for the search results
109
+     * 	- 'offset' - Set the offset for the limited search results
110
+     * @return array an array of contacts which are arrays of key-value-pairs
111
+     *  example result:
112
+     *  [
113
+     *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
114
+     *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
115
+     *	]
116
+     * @since 5.0.0
117
+     */
118
+    public function search($pattern, $searchProperties, $options) {
119
+        $results = $this->backend->search($this->getKey(), $pattern, $searchProperties, $options);
120
+
121
+        $withTypes = \array_key_exists('types', $options) && $options['types'] === true;
122
+
123
+        $vCards = [];
124
+        foreach ($results as $result) {
125
+            $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes);
126
+        }
127
+
128
+        return $vCards;
129
+    }
130
+
131
+    /**
132
+     * @param array $properties this array if key-value-pairs defines a contact
133
+     * @return array an array representing the contact just created or updated
134
+     * @since 5.0.0
135
+     */
136
+    public function createOrUpdate($properties) {
137
+        $update = false;
138
+        if (!isset($properties['URI'])) { // create a new contact
139
+            $uid = $this->createUid();
140
+            $uri = $uid . '.vcf';
141
+            $vCard = $this->createEmptyVCard($uid);
142
+        } else { // update existing contact
143
+            $uri = $properties['URI'];
144
+            $vCardData = $this->backend->getCard($this->getKey(), $uri);
145
+            $vCard = $this->readCard($vCardData['carddata']);
146
+            $update = true;
147
+        }
148
+
149
+        foreach ($properties as $key => $value) {
150
+            $vCard->$key = $vCard->createProperty($key, $value);
151
+        }
152
+
153
+        if ($update) {
154
+            $this->backend->updateCard($this->getKey(), $uri, $vCard->serialize());
155
+        } else {
156
+            $this->backend->createCard($this->getKey(), $uri, $vCard->serialize());
157
+        }
158
+
159
+        return $this->vCard2Array($uri, $vCard);
160
+    }
161
+
162
+    /**
163
+     * @return mixed
164
+     * @since 5.0.0
165
+     */
166
+    public function getPermissions() {
167
+        $permissions = $this->addressBook->getACL();
168
+        $result = 0;
169
+        foreach ($permissions as $permission) {
170
+            switch ($permission['privilege']) {
171
+                case '{DAV:}read':
172
+                    $result |= Constants::PERMISSION_READ;
173
+                    break;
174
+                case '{DAV:}write':
175
+                    $result |= Constants::PERMISSION_CREATE;
176
+                    $result |= Constants::PERMISSION_UPDATE;
177
+                    break;
178
+                case '{DAV:}all':
179
+                    $result |= Constants::PERMISSION_ALL;
180
+                    break;
181
+            }
182
+        }
183
+
184
+        return $result;
185
+    }
186
+
187
+    /**
188
+     * @param object $id the unique identifier to a contact
189
+     * @return bool successful or not
190
+     * @since 5.0.0
191
+     */
192
+    public function delete($id) {
193
+        $uri = $this->backend->getCardUri($id);
194
+        return $this->backend->deleteCard($this->addressBookInfo['id'], $uri);
195
+    }
196
+
197
+    /**
198
+     * read vCard data into a vCard object
199
+     *
200
+     * @param string $cardData
201
+     * @return VCard
202
+     */
203
+    protected function readCard($cardData) {
204
+        return  Reader::read($cardData);
205
+    }
206
+
207
+    /**
208
+     * create UID for contact
209
+     *
210
+     * @return string
211
+     */
212
+    protected function createUid() {
213
+        do {
214
+            $uid = $this->getUid();
215
+            $contact = $this->backend->getContact($this->getKey(), $uid . '.vcf');
216
+        } while (!empty($contact));
217
+
218
+        return $uid;
219
+    }
220
+
221
+    /**
222
+     * getUid is only there for testing, use createUid instead
223
+     */
224
+    protected function getUid() {
225
+        return UUIDUtil::getUUID();
226
+    }
227
+
228
+    /**
229
+     * create empty vcard
230
+     *
231
+     * @param string $uid
232
+     * @return VCard
233
+     */
234
+    protected function createEmptyVCard($uid) {
235
+        $vCard = new VCard();
236
+        $vCard->UID = $uid;
237
+        return $vCard;
238
+    }
239
+
240
+    /**
241
+     * create array with all vCard properties
242
+     *
243
+     * @param string $uri
244
+     * @param VCard $vCard
245
+     * @param boolean $withTypes (optional) return the values as arrays of value/type pairs
246
+     * @return array
247
+     */
248
+    protected function vCard2Array($uri, VCard $vCard, $withTypes = false) {
249
+        $result = [
250
+            'URI' => $uri,
251
+        ];
252
+
253
+        foreach ($vCard->children() as $property) {
254
+            if ($property->name === 'PHOTO' && $property->getValueType() === 'BINARY') {
255
+                $url = $this->urlGenerator->getAbsoluteURL(
256
+                    $this->urlGenerator->linkTo('', 'remote.php') . '/dav/');
257
+                $url .= implode('/', [
258
+                    'addressbooks',
259
+                    substr($this->addressBookInfo['principaluri'], 11), //cut off 'principals/'
260
+                    $this->addressBookInfo['uri'],
261
+                    $uri
262
+                ]) . '?photo';
263
+
264
+                $result['PHOTO'] = 'VALUE=uri:' . $url;
265
+            } elseif (in_array($property->name, ['URL', 'GEO', 'CLOUD', 'ADR', 'EMAIL', 'IMPP', 'TEL', 'X-SOCIALPROFILE', 'RELATED', 'LANG', 'X-ADDRESSBOOKSERVER-MEMBER'])) {
266
+                if (!isset($result[$property->name])) {
267
+                    $result[$property->name] = [];
268
+                }
269
+
270
+                $type = $this->getTypeFromProperty($property);
271
+                if ($withTypes) {
272
+                    $result[$property->name][] = [
273
+                        'type' => $type,
274
+                        'value' => $property->getValue()
275
+                    ];
276
+                } else {
277
+                    $result[$property->name][] = $property->getValue();
278
+                }
279
+            } else {
280
+                $result[$property->name] = $property->getValue();
281
+            }
282
+        }
283
+
284
+        if (
285
+            $this->addressBookInfo['principaluri'] === 'principals/system/system' && (
286
+                $this->addressBookInfo['uri'] === 'system' ||
287
+                $this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl()
288
+            )
289
+        ) {
290
+            $result['isLocalSystemBook'] = true;
291
+        }
292
+        return $result;
293
+    }
294
+
295
+    /**
296
+     * Get the type of the current property
297
+     *
298
+     * @param Property $property
299
+     * @return null|string
300
+     */
301
+    protected function getTypeFromProperty(Property $property) {
302
+        $parameters = $property->parameters();
303
+        // Type is the social network, when it's empty we don't need this.
304
+        if (isset($parameters['TYPE'])) {
305
+            /** @var \Sabre\VObject\Parameter $type */
306
+            $type = $parameters['TYPE'];
307
+            return $type->getValue();
308
+        }
309
+
310
+        return null;
311
+    }
312 312
 }
Please login to merge, or discard this patch.
lib/private/ContactsManager.php 1 patch
Indentation   +176 added lines, -176 removed lines patch added patch discarded remove patch
@@ -30,180 +30,180 @@
 block discarded – undo
30 30
 
31 31
 namespace OC {
32 32
 
33
-	class ContactsManager implements \OCP\Contacts\IManager {
34
-
35
-		/**
36
-		 * This function is used to search and find contacts within the users address books.
37
-		 * In case $pattern is empty all contacts will be returned.
38
-		 *
39
-		 * @param string $pattern which should match within the $searchProperties
40
-		 * @param array $searchProperties defines the properties within the query pattern should match
41
-		 * @param array $options = array() to define the search behavior
42
-		 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
43
-		 * 	- 'limit' - Set a numeric limit for the search results
44
-		 * 	- 'offset' - Set the offset for the limited search results
45
-		 * @return array an array of contacts which are arrays of key-value-pairs
46
-		 */
47
-		public function search($pattern, $searchProperties = [], $options = []) {
48
-			$this->loadAddressBooks();
49
-			$result = [];
50
-			foreach ($this->addressBooks as $addressBook) {
51
-				$r = $addressBook->search($pattern, $searchProperties, $options);
52
-				$contacts = [];
53
-				foreach ($r as $c) {
54
-					$c['addressbook-key'] = $addressBook->getKey();
55
-					$contacts[] = $c;
56
-				}
57
-				$result = array_merge($result, $contacts);
58
-			}
59
-
60
-			return $result;
61
-		}
62
-
63
-		/**
64
-		 * This function can be used to delete the contact identified by the given id
65
-		 *
66
-		 * @param object $id the unique identifier to a contact
67
-		 * @param string $addressBookKey identifier of the address book in which the contact shall be deleted
68
-		 * @return bool successful or not
69
-		 */
70
-		public function delete($id, $addressBookKey) {
71
-			$addressBook = $this->getAddressBook($addressBookKey);
72
-			if (!$addressBook) {
73
-				return null;
74
-			}
75
-
76
-			if ($addressBook->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
77
-				return $addressBook->delete($id);
78
-			}
79
-
80
-			return null;
81
-		}
82
-
83
-		/**
84
-		 * This function is used to create a new contact if 'id' is not given or not present.
85
-		 * Otherwise the contact will be updated by replacing the entire data set.
86
-		 *
87
-		 * @param array $properties this array if key-value-pairs defines a contact
88
-		 * @param string $addressBookKey identifier of the address book in which the contact shall be created or updated
89
-		 * @return array representing the contact just created or updated
90
-		 */
91
-		public function createOrUpdate($properties, $addressBookKey) {
92
-			$addressBook = $this->getAddressBook($addressBookKey);
93
-			if (!$addressBook) {
94
-				return null;
95
-			}
96
-
97
-			if ($addressBook->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
98
-				return $addressBook->createOrUpdate($properties);
99
-			}
100
-
101
-			return null;
102
-		}
103
-
104
-		/**
105
-		 * Check if contacts are available (e.g. contacts app enabled)
106
-		 *
107
-		 * @return bool true if enabled, false if not
108
-		 */
109
-		public function isEnabled() {
110
-			return !empty($this->addressBooks) || !empty($this->addressBookLoaders);
111
-		}
112
-
113
-		/**
114
-		 * @param \OCP\IAddressBook $addressBook
115
-		 */
116
-		public function registerAddressBook(\OCP\IAddressBook $addressBook) {
117
-			$this->addressBooks[$addressBook->getKey()] = $addressBook;
118
-		}
119
-
120
-		/**
121
-		 * @param \OCP\IAddressBook $addressBook
122
-		 */
123
-		public function unregisterAddressBook(\OCP\IAddressBook $addressBook) {
124
-			unset($this->addressBooks[$addressBook->getKey()]);
125
-		}
126
-
127
-		/**
128
-		 * Return a list of the user's addressbooks display names
129
-		 * ! The addressBook displayName are not unique, please use getUserAddressBooks
130
-		 *
131
-		 * @return array
132
-		 * @since 6.0.0
133
-		 * @deprecated 16.0.0 - Use `$this->getUserAddressBooks()` instead
134
-		 */
135
-		public function getAddressBooks() {
136
-			$this->loadAddressBooks();
137
-			$result = [];
138
-			foreach ($this->addressBooks as $addressBook) {
139
-				$result[$addressBook->getKey()] = $addressBook->getDisplayName();
140
-			}
141
-
142
-			return $result;
143
-		}
144
-
145
-		/**
146
-		 * Return a list of the user's addressbooks
147
-		 *
148
-		 * @return IAddressBook[]
149
-		 * @since 16.0.0
150
-		 */
151
-		public function getUserAddressBooks(): array {
152
-			$this->loadAddressBooks();
153
-			return $this->addressBooks;
154
-		}
155
-
156
-		/**
157
-		 * removes all registered address book instances
158
-		 */
159
-		public function clear() {
160
-			$this->addressBooks = [];
161
-			$this->addressBookLoaders = [];
162
-		}
163
-
164
-		/**
165
-		 * @var \OCP\IAddressBook[] which holds all registered address books
166
-		 */
167
-		private $addressBooks = [];
168
-
169
-		/**
170
-		 * @var \Closure[] to call to load/register address books
171
-		 */
172
-		private $addressBookLoaders = [];
173
-
174
-		/**
175
-		 * In order to improve lazy loading a closure can be registered which will be called in case
176
-		 * address books are actually requested
177
-		 *
178
-		 * @param \Closure $callable
179
-		 */
180
-		public function register(\Closure $callable) {
181
-			$this->addressBookLoaders[] = $callable;
182
-		}
183
-
184
-		/**
185
-		 * Get (and load when needed) the address book for $key
186
-		 *
187
-		 * @param string $addressBookKey
188
-		 * @return \OCP\IAddressBook
189
-		 */
190
-		protected function getAddressBook($addressBookKey) {
191
-			$this->loadAddressBooks();
192
-			if (!array_key_exists($addressBookKey, $this->addressBooks)) {
193
-				return null;
194
-			}
195
-
196
-			return $this->addressBooks[$addressBookKey];
197
-		}
198
-
199
-		/**
200
-		 * Load all address books registered with 'register'
201
-		 */
202
-		protected function loadAddressBooks() {
203
-			foreach ($this->addressBookLoaders as $callable) {
204
-				$callable($this);
205
-			}
206
-			$this->addressBookLoaders = [];
207
-		}
208
-	}
33
+    class ContactsManager implements \OCP\Contacts\IManager {
34
+
35
+        /**
36
+         * This function is used to search and find contacts within the users address books.
37
+         * In case $pattern is empty all contacts will be returned.
38
+         *
39
+         * @param string $pattern which should match within the $searchProperties
40
+         * @param array $searchProperties defines the properties within the query pattern should match
41
+         * @param array $options = array() to define the search behavior
42
+         * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
43
+         * 	- 'limit' - Set a numeric limit for the search results
44
+         * 	- 'offset' - Set the offset for the limited search results
45
+         * @return array an array of contacts which are arrays of key-value-pairs
46
+         */
47
+        public function search($pattern, $searchProperties = [], $options = []) {
48
+            $this->loadAddressBooks();
49
+            $result = [];
50
+            foreach ($this->addressBooks as $addressBook) {
51
+                $r = $addressBook->search($pattern, $searchProperties, $options);
52
+                $contacts = [];
53
+                foreach ($r as $c) {
54
+                    $c['addressbook-key'] = $addressBook->getKey();
55
+                    $contacts[] = $c;
56
+                }
57
+                $result = array_merge($result, $contacts);
58
+            }
59
+
60
+            return $result;
61
+        }
62
+
63
+        /**
64
+         * This function can be used to delete the contact identified by the given id
65
+         *
66
+         * @param object $id the unique identifier to a contact
67
+         * @param string $addressBookKey identifier of the address book in which the contact shall be deleted
68
+         * @return bool successful or not
69
+         */
70
+        public function delete($id, $addressBookKey) {
71
+            $addressBook = $this->getAddressBook($addressBookKey);
72
+            if (!$addressBook) {
73
+                return null;
74
+            }
75
+
76
+            if ($addressBook->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
77
+                return $addressBook->delete($id);
78
+            }
79
+
80
+            return null;
81
+        }
82
+
83
+        /**
84
+         * This function is used to create a new contact if 'id' is not given or not present.
85
+         * Otherwise the contact will be updated by replacing the entire data set.
86
+         *
87
+         * @param array $properties this array if key-value-pairs defines a contact
88
+         * @param string $addressBookKey identifier of the address book in which the contact shall be created or updated
89
+         * @return array representing the contact just created or updated
90
+         */
91
+        public function createOrUpdate($properties, $addressBookKey) {
92
+            $addressBook = $this->getAddressBook($addressBookKey);
93
+            if (!$addressBook) {
94
+                return null;
95
+            }
96
+
97
+            if ($addressBook->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
98
+                return $addressBook->createOrUpdate($properties);
99
+            }
100
+
101
+            return null;
102
+        }
103
+
104
+        /**
105
+         * Check if contacts are available (e.g. contacts app enabled)
106
+         *
107
+         * @return bool true if enabled, false if not
108
+         */
109
+        public function isEnabled() {
110
+            return !empty($this->addressBooks) || !empty($this->addressBookLoaders);
111
+        }
112
+
113
+        /**
114
+         * @param \OCP\IAddressBook $addressBook
115
+         */
116
+        public function registerAddressBook(\OCP\IAddressBook $addressBook) {
117
+            $this->addressBooks[$addressBook->getKey()] = $addressBook;
118
+        }
119
+
120
+        /**
121
+         * @param \OCP\IAddressBook $addressBook
122
+         */
123
+        public function unregisterAddressBook(\OCP\IAddressBook $addressBook) {
124
+            unset($this->addressBooks[$addressBook->getKey()]);
125
+        }
126
+
127
+        /**
128
+         * Return a list of the user's addressbooks display names
129
+         * ! The addressBook displayName are not unique, please use getUserAddressBooks
130
+         *
131
+         * @return array
132
+         * @since 6.0.0
133
+         * @deprecated 16.0.0 - Use `$this->getUserAddressBooks()` instead
134
+         */
135
+        public function getAddressBooks() {
136
+            $this->loadAddressBooks();
137
+            $result = [];
138
+            foreach ($this->addressBooks as $addressBook) {
139
+                $result[$addressBook->getKey()] = $addressBook->getDisplayName();
140
+            }
141
+
142
+            return $result;
143
+        }
144
+
145
+        /**
146
+         * Return a list of the user's addressbooks
147
+         *
148
+         * @return IAddressBook[]
149
+         * @since 16.0.0
150
+         */
151
+        public function getUserAddressBooks(): array {
152
+            $this->loadAddressBooks();
153
+            return $this->addressBooks;
154
+        }
155
+
156
+        /**
157
+         * removes all registered address book instances
158
+         */
159
+        public function clear() {
160
+            $this->addressBooks = [];
161
+            $this->addressBookLoaders = [];
162
+        }
163
+
164
+        /**
165
+         * @var \OCP\IAddressBook[] which holds all registered address books
166
+         */
167
+        private $addressBooks = [];
168
+
169
+        /**
170
+         * @var \Closure[] to call to load/register address books
171
+         */
172
+        private $addressBookLoaders = [];
173
+
174
+        /**
175
+         * In order to improve lazy loading a closure can be registered which will be called in case
176
+         * address books are actually requested
177
+         *
178
+         * @param \Closure $callable
179
+         */
180
+        public function register(\Closure $callable) {
181
+            $this->addressBookLoaders[] = $callable;
182
+        }
183
+
184
+        /**
185
+         * Get (and load when needed) the address book for $key
186
+         *
187
+         * @param string $addressBookKey
188
+         * @return \OCP\IAddressBook
189
+         */
190
+        protected function getAddressBook($addressBookKey) {
191
+            $this->loadAddressBooks();
192
+            if (!array_key_exists($addressBookKey, $this->addressBooks)) {
193
+                return null;
194
+            }
195
+
196
+            return $this->addressBooks[$addressBookKey];
197
+        }
198
+
199
+        /**
200
+         * Load all address books registered with 'register'
201
+         */
202
+        protected function loadAddressBooks() {
203
+            foreach ($this->addressBookLoaders as $callable) {
204
+                $callable($this);
205
+            }
206
+            $this->addressBookLoaders = [];
207
+        }
208
+    }
209 209
 }
Please login to merge, or discard this patch.
lib/private/Collaboration/Collaborators/RemotePlugin.php 1 patch
Indentation   +139 added lines, -139 removed lines patch added patch discarded remove patch
@@ -37,154 +37,154 @@
 block discarded – undo
37 37
 use OCP\Share;
38 38
 
39 39
 class RemotePlugin implements ISearchPlugin {
40
-	protected $shareeEnumeration;
40
+    protected $shareeEnumeration;
41 41
 
42
-	/** @var IManager */
43
-	private $contactsManager;
44
-	/** @var ICloudIdManager */
45
-	private $cloudIdManager;
46
-	/** @var IConfig */
47
-	private $config;
48
-	/** @var IUserManager */
49
-	private $userManager;
50
-	/** @var string */
51
-	private $userId = '';
42
+    /** @var IManager */
43
+    private $contactsManager;
44
+    /** @var ICloudIdManager */
45
+    private $cloudIdManager;
46
+    /** @var IConfig */
47
+    private $config;
48
+    /** @var IUserManager */
49
+    private $userManager;
50
+    /** @var string */
51
+    private $userId = '';
52 52
 
53
-	public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) {
54
-		$this->contactsManager = $contactsManager;
55
-		$this->cloudIdManager = $cloudIdManager;
56
-		$this->config = $config;
57
-		$this->userManager = $userManager;
58
-		$user = $userSession->getUser();
59
-		if ($user !== null) {
60
-			$this->userId = $user->getUID();
61
-		}
62
-		$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
63
-	}
53
+    public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) {
54
+        $this->contactsManager = $contactsManager;
55
+        $this->cloudIdManager = $cloudIdManager;
56
+        $this->config = $config;
57
+        $this->userManager = $userManager;
58
+        $user = $userSession->getUser();
59
+        if ($user !== null) {
60
+            $this->userId = $user->getUID();
61
+        }
62
+        $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
63
+    }
64 64
 
65
-	public function search($search, $limit, $offset, ISearchResult $searchResult) {
66
-		$result = ['wide' => [], 'exact' => []];
67
-		$resultType = new SearchResultType('remotes');
65
+    public function search($search, $limit, $offset, ISearchResult $searchResult) {
66
+        $result = ['wide' => [], 'exact' => []];
67
+        $resultType = new SearchResultType('remotes');
68 68
 
69
-		// Search in contacts
70
-		$addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN'], ['limit' => $limit, 'offset' => $offset]);
71
-		foreach ($addressBookContacts as $contact) {
72
-			if (isset($contact['isLocalSystemBook'])) {
73
-				continue;
74
-			}
75
-			if (isset($contact['CLOUD'])) {
76
-				$cloudIds = $contact['CLOUD'];
77
-				if (is_string($cloudIds)) {
78
-					$cloudIds = [$cloudIds];
79
-				}
80
-				$lowerSearch = strtolower($search);
81
-				foreach ($cloudIds as $cloudId) {
82
-					$cloudIdType = '';
83
-					if (\is_array($cloudId)) {
84
-						$cloudIdData = $cloudId;
85
-						$cloudId = $cloudIdData['value'];
86
-						$cloudIdType = $cloudIdData['type'];
87
-					}
88
-					try {
89
-						list($remoteUser, $serverUrl) = $this->splitUserRemote($cloudId);
90
-					} catch (\InvalidArgumentException $e) {
91
-						continue;
92
-					}
69
+        // Search in contacts
70
+        $addressBookContacts = $this->contactsManager->search($search, ['CLOUD', 'FN'], ['limit' => $limit, 'offset' => $offset]);
71
+        foreach ($addressBookContacts as $contact) {
72
+            if (isset($contact['isLocalSystemBook'])) {
73
+                continue;
74
+            }
75
+            if (isset($contact['CLOUD'])) {
76
+                $cloudIds = $contact['CLOUD'];
77
+                if (is_string($cloudIds)) {
78
+                    $cloudIds = [$cloudIds];
79
+                }
80
+                $lowerSearch = strtolower($search);
81
+                foreach ($cloudIds as $cloudId) {
82
+                    $cloudIdType = '';
83
+                    if (\is_array($cloudId)) {
84
+                        $cloudIdData = $cloudId;
85
+                        $cloudId = $cloudIdData['value'];
86
+                        $cloudIdType = $cloudIdData['type'];
87
+                    }
88
+                    try {
89
+                        list($remoteUser, $serverUrl) = $this->splitUserRemote($cloudId);
90
+                    } catch (\InvalidArgumentException $e) {
91
+                        continue;
92
+                    }
93 93
 
94
-					$localUser = $this->userManager->get($remoteUser);
95
-					/**
96
-					 * Add local share if remote cloud id matches a local user ones
97
-					 */
98
-					if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId()) {
99
-						$result['wide'][] = [
100
-							'label' => $contact['FN'],
101
-							'uuid' => $contact['UID'],
102
-							'value' => [
103
-								'shareType' => Share::SHARE_TYPE_USER,
104
-								'shareWith' => $remoteUser
105
-							]
106
-						];
107
-					}
94
+                    $localUser = $this->userManager->get($remoteUser);
95
+                    /**
96
+                     * Add local share if remote cloud id matches a local user ones
97
+                     */
98
+                    if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId()) {
99
+                        $result['wide'][] = [
100
+                            'label' => $contact['FN'],
101
+                            'uuid' => $contact['UID'],
102
+                            'value' => [
103
+                                'shareType' => Share::SHARE_TYPE_USER,
104
+                                'shareWith' => $remoteUser
105
+                            ]
106
+                        ];
107
+                    }
108 108
 
109
-					if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
110
-						if (strtolower($cloudId) === $lowerSearch) {
111
-							$searchResult->markExactIdMatch($resultType);
112
-						}
113
-						$result['exact'][] = [
114
-							'label' => $contact['FN'] . " ($cloudId)",
115
-							'uuid' => $contact['UID'],
116
-							'name' => $contact['FN'],
117
-							'type' => $cloudIdType,
118
-							'value' => [
119
-								'shareType' => Share::SHARE_TYPE_REMOTE,
120
-								'shareWith' => $cloudId,
121
-								'server' => $serverUrl,
122
-							],
123
-						];
124
-					} else {
125
-						$result['wide'][] = [
126
-							'label' => $contact['FN'] . " ($cloudId)",
127
-							'uuid' => $contact['UID'],
128
-							'name' => $contact['FN'],
129
-							'type' => $cloudIdType,
130
-							'value' => [
131
-								'shareType' => Share::SHARE_TYPE_REMOTE,
132
-								'shareWith' => $cloudId,
133
-								'server' => $serverUrl,
134
-							],
135
-						];
136
-					}
137
-				}
138
-			}
139
-		}
109
+                    if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
110
+                        if (strtolower($cloudId) === $lowerSearch) {
111
+                            $searchResult->markExactIdMatch($resultType);
112
+                        }
113
+                        $result['exact'][] = [
114
+                            'label' => $contact['FN'] . " ($cloudId)",
115
+                            'uuid' => $contact['UID'],
116
+                            'name' => $contact['FN'],
117
+                            'type' => $cloudIdType,
118
+                            'value' => [
119
+                                'shareType' => Share::SHARE_TYPE_REMOTE,
120
+                                'shareWith' => $cloudId,
121
+                                'server' => $serverUrl,
122
+                            ],
123
+                        ];
124
+                    } else {
125
+                        $result['wide'][] = [
126
+                            'label' => $contact['FN'] . " ($cloudId)",
127
+                            'uuid' => $contact['UID'],
128
+                            'name' => $contact['FN'],
129
+                            'type' => $cloudIdType,
130
+                            'value' => [
131
+                                'shareType' => Share::SHARE_TYPE_REMOTE,
132
+                                'shareWith' => $cloudId,
133
+                                'server' => $serverUrl,
134
+                            ],
135
+                        ];
136
+                    }
137
+                }
138
+            }
139
+        }
140 140
 
141
-		if (!$this->shareeEnumeration) {
142
-			$result['wide'] = [];
143
-		} else {
144
-			$result['wide'] = array_slice($result['wide'], $offset, $limit);
145
-		}
141
+        if (!$this->shareeEnumeration) {
142
+            $result['wide'] = [];
143
+        } else {
144
+            $result['wide'] = array_slice($result['wide'], $offset, $limit);
145
+        }
146 146
 
147
-		/**
148
-		 * Add generic share with remote item for valid cloud ids that are not users of the local instance
149
-		 */
150
-		if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) {
151
-			try {
152
-				list($remoteUser, $serverUrl) = $this->splitUserRemote($search);
153
-				$localUser = $this->userManager->get($remoteUser);
154
-				if ($localUser === null || $search !== $localUser->getCloudId()) {
155
-					$result['exact'][] = [
156
-						'label' => $remoteUser . " ($serverUrl)",
157
-						'uuid' => $remoteUser,
158
-						'name' => $remoteUser,
159
-						'value' => [
160
-							'shareType' => Share::SHARE_TYPE_REMOTE,
161
-							'shareWith' => $search,
162
-							'server' => $serverUrl,
163
-						],
164
-					];
165
-				}
166
-			} catch (\InvalidArgumentException $e) {
167
-			}
168
-		}
147
+        /**
148
+         * Add generic share with remote item for valid cloud ids that are not users of the local instance
149
+         */
150
+        if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) {
151
+            try {
152
+                list($remoteUser, $serverUrl) = $this->splitUserRemote($search);
153
+                $localUser = $this->userManager->get($remoteUser);
154
+                if ($localUser === null || $search !== $localUser->getCloudId()) {
155
+                    $result['exact'][] = [
156
+                        'label' => $remoteUser . " ($serverUrl)",
157
+                        'uuid' => $remoteUser,
158
+                        'name' => $remoteUser,
159
+                        'value' => [
160
+                            'shareType' => Share::SHARE_TYPE_REMOTE,
161
+                            'shareWith' => $search,
162
+                            'server' => $serverUrl,
163
+                        ],
164
+                    ];
165
+                }
166
+            } catch (\InvalidArgumentException $e) {
167
+            }
168
+        }
169 169
 
170
-		$searchResult->addResultSet($resultType, $result['wide'], $result['exact']);
170
+        $searchResult->addResultSet($resultType, $result['wide'], $result['exact']);
171 171
 
172
-		return true;
173
-	}
172
+        return true;
173
+    }
174 174
 
175
-	/**
176
-	 * split user and remote from federated cloud id
177
-	 *
178
-	 * @param string $address federated share address
179
-	 * @return array [user, remoteURL]
180
-	 * @throws \InvalidArgumentException
181
-	 */
182
-	public function splitUserRemote($address) {
183
-		try {
184
-			$cloudId = $this->cloudIdManager->resolveCloudId($address);
185
-			return [$cloudId->getUser(), $cloudId->getRemote()];
186
-		} catch (\InvalidArgumentException $e) {
187
-			throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e);
188
-		}
189
-	}
175
+    /**
176
+     * split user and remote from federated cloud id
177
+     *
178
+     * @param string $address federated share address
179
+     * @return array [user, remoteURL]
180
+     * @throws \InvalidArgumentException
181
+     */
182
+    public function splitUserRemote($address) {
183
+        try {
184
+            $cloudId = $this->cloudIdManager->resolveCloudId($address);
185
+            return [$cloudId->getUser(), $cloudId->getRemote()];
186
+        } catch (\InvalidArgumentException $e) {
187
+            throw new \InvalidArgumentException('Invalid Federated Cloud ID', 0, $e);
188
+        }
189
+    }
190 190
 }
Please login to merge, or discard this patch.
lib/private/Collaboration/Collaborators/MailPlugin.php 1 patch
Indentation   +202 added lines, -202 removed lines patch added patch discarded remove patch
@@ -39,208 +39,208 @@
 block discarded – undo
39 39
 use OCP\Share;
40 40
 
41 41
 class MailPlugin implements ISearchPlugin {
42
-	protected $shareeEnumeration;
43
-	protected $shareWithGroupOnly;
44
-
45
-	/** @var IManager */
46
-	private $contactsManager;
47
-	/** @var ICloudIdManager */
48
-	private $cloudIdManager;
49
-	/** @var IConfig */
50
-	private $config;
51
-
52
-	/** @var IGroupManager */
53
-	private $groupManager;
54
-
55
-	/** @var IUserSession */
56
-	private $userSession;
57
-
58
-	public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IGroupManager $groupManager, IUserSession $userSession) {
59
-		$this->contactsManager = $contactsManager;
60
-		$this->cloudIdManager = $cloudIdManager;
61
-		$this->config = $config;
62
-		$this->groupManager = $groupManager;
63
-		$this->userSession = $userSession;
64
-
65
-		$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
66
-		$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
67
-		$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
68
-	}
69
-
70
-	/**
71
-	 * @param $search
72
-	 * @param $limit
73
-	 * @param $offset
74
-	 * @param ISearchResult $searchResult
75
-	 * @return bool
76
-	 * @since 13.0.0
77
-	 */
78
-	public function search($search, $limit, $offset, ISearchResult $searchResult) {
79
-		$result = $userResults = ['wide' => [], 'exact' => []];
80
-		$userType = new SearchResultType('users');
81
-		$emailType = new SearchResultType('emails');
82
-
83
-		// Search in contacts
84
-		$addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN'], ['limit' => $limit, 'offset' => $offset]);
85
-		$lowerSearch = strtolower($search);
86
-		foreach ($addressBookContacts as $contact) {
87
-			if (isset($contact['EMAIL'])) {
88
-				$emailAddresses = $contact['EMAIL'];
89
-				if (\is_string($emailAddresses)) {
90
-					$emailAddresses = [$emailAddresses];
91
-				}
92
-				foreach ($emailAddresses as $type => $emailAddress) {
93
-					$displayName = $emailAddress;
94
-					$emailAddressType = null;
95
-					if (\is_array($emailAddress)) {
96
-						$emailAddressData = $emailAddress;
97
-						$emailAddress = $emailAddressData['value'];
98
-						$emailAddressType = $emailAddressData['type'];
99
-					}
100
-					if (isset($contact['FN'])) {
101
-						$displayName = $contact['FN'] . ' (' . $emailAddress . ')';
102
-					}
103
-					$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
104
-
105
-					if (isset($contact['isLocalSystemBook'])) {
106
-						if ($this->shareWithGroupOnly) {
107
-							/*
42
+    protected $shareeEnumeration;
43
+    protected $shareWithGroupOnly;
44
+
45
+    /** @var IManager */
46
+    private $contactsManager;
47
+    /** @var ICloudIdManager */
48
+    private $cloudIdManager;
49
+    /** @var IConfig */
50
+    private $config;
51
+
52
+    /** @var IGroupManager */
53
+    private $groupManager;
54
+
55
+    /** @var IUserSession */
56
+    private $userSession;
57
+
58
+    public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IGroupManager $groupManager, IUserSession $userSession) {
59
+        $this->contactsManager = $contactsManager;
60
+        $this->cloudIdManager = $cloudIdManager;
61
+        $this->config = $config;
62
+        $this->groupManager = $groupManager;
63
+        $this->userSession = $userSession;
64
+
65
+        $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
66
+        $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
67
+        $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
68
+    }
69
+
70
+    /**
71
+     * @param $search
72
+     * @param $limit
73
+     * @param $offset
74
+     * @param ISearchResult $searchResult
75
+     * @return bool
76
+     * @since 13.0.0
77
+     */
78
+    public function search($search, $limit, $offset, ISearchResult $searchResult) {
79
+        $result = $userResults = ['wide' => [], 'exact' => []];
80
+        $userType = new SearchResultType('users');
81
+        $emailType = new SearchResultType('emails');
82
+
83
+        // Search in contacts
84
+        $addressBookContacts = $this->contactsManager->search($search, ['EMAIL', 'FN'], ['limit' => $limit, 'offset' => $offset]);
85
+        $lowerSearch = strtolower($search);
86
+        foreach ($addressBookContacts as $contact) {
87
+            if (isset($contact['EMAIL'])) {
88
+                $emailAddresses = $contact['EMAIL'];
89
+                if (\is_string($emailAddresses)) {
90
+                    $emailAddresses = [$emailAddresses];
91
+                }
92
+                foreach ($emailAddresses as $type => $emailAddress) {
93
+                    $displayName = $emailAddress;
94
+                    $emailAddressType = null;
95
+                    if (\is_array($emailAddress)) {
96
+                        $emailAddressData = $emailAddress;
97
+                        $emailAddress = $emailAddressData['value'];
98
+                        $emailAddressType = $emailAddressData['type'];
99
+                    }
100
+                    if (isset($contact['FN'])) {
101
+                        $displayName = $contact['FN'] . ' (' . $emailAddress . ')';
102
+                    }
103
+                    $exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
104
+
105
+                    if (isset($contact['isLocalSystemBook'])) {
106
+                        if ($this->shareWithGroupOnly) {
107
+                            /*
108 108
 							 * Check if the user may share with the user associated with the e-mail of the just found contact
109 109
 							 */
110
-							$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
111
-							$found = false;
112
-							foreach ($userGroups as $userGroup) {
113
-								if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
114
-									$found = true;
115
-									break;
116
-								}
117
-							}
118
-							if (!$found) {
119
-								continue;
120
-							}
121
-						}
122
-						if ($exactEmailMatch) {
123
-							try {
124
-								$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
125
-							} catch (\InvalidArgumentException $e) {
126
-								continue;
127
-							}
128
-
129
-							if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
130
-								$singleResult = [[
131
-									'label' => $displayName,
132
-									'uuid' => $contact['UID'],
133
-									'name' => $contact['FN'],
134
-									'value' => [
135
-										'shareType' => Share::SHARE_TYPE_USER,
136
-										'shareWith' => $cloud->getUser(),
137
-									],
138
-								]];
139
-								$searchResult->addResultSet($userType, [], $singleResult);
140
-								$searchResult->markExactIdMatch($emailType);
141
-							}
142
-							return false;
143
-						}
144
-
145
-						if ($this->shareeEnumeration) {
146
-							try {
147
-								$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
148
-							} catch (\InvalidArgumentException $e) {
149
-								continue;
150
-							}
151
-
152
-							$addToWide = !$this->shareeEnumerationInGroupOnly;
153
-							if ($this->shareeEnumerationInGroupOnly) {
154
-								$addToWide = false;
155
-								$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
156
-								foreach ($userGroups as $userGroup) {
157
-									if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
158
-										$addToWide = true;
159
-										break;
160
-									}
161
-								}
162
-							}
163
-							if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
164
-								$userResults['wide'][] = [
165
-									'label' => $displayName,
166
-									'uuid' => $contact['UID'],
167
-									'name' => $contact['FN'],
168
-									'value' => [
169
-										'shareType' => Share::SHARE_TYPE_USER,
170
-										'shareWith' => $cloud->getUser(),
171
-									],
172
-								];
173
-								continue;
174
-							}
175
-						}
176
-						continue;
177
-					}
178
-
179
-					if ($exactEmailMatch
180
-						|| isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch) {
181
-						if ($exactEmailMatch) {
182
-							$searchResult->markExactIdMatch($emailType);
183
-						}
184
-						$result['exact'][] = [
185
-							'label' => $displayName,
186
-							'uuid' => $contact['UID'],
187
-							'name' => $contact['FN'],
188
-							'type' => $emailAddressType ?? '',
189
-							'value' => [
190
-								'shareType' => Share::SHARE_TYPE_EMAIL,
191
-								'shareWith' => $emailAddress,
192
-							],
193
-						];
194
-					} else {
195
-						$result['wide'][] = [
196
-							'label' => $displayName,
197
-							'uuid' => $contact['UID'],
198
-							'name' => $contact['FN'],
199
-							'type' => $emailAddressType ?? '',
200
-							'value' => [
201
-								'shareType' => Share::SHARE_TYPE_EMAIL,
202
-								'shareWith' => $emailAddress,
203
-							],
204
-						];
205
-					}
206
-				}
207
-			}
208
-		}
209
-
210
-		$reachedEnd = true;
211
-		if (!$this->shareeEnumeration) {
212
-			$result['wide'] = [];
213
-			$userResults['wide'] = [];
214
-		} else {
215
-			$reachedEnd = (count($result['wide']) < $offset + $limit) &&
216
-				(count($userResults['wide']) < $offset + $limit);
217
-
218
-			$result['wide'] = array_slice($result['wide'], $offset, $limit);
219
-			$userResults['wide'] = array_slice($userResults['wide'], $offset, $limit);
220
-		}
221
-
222
-
223
-		if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) {
224
-			$result['exact'][] = [
225
-				'label' => $search,
226
-				'uuid' => $search,
227
-				'value' => [
228
-					'shareType' => Share::SHARE_TYPE_EMAIL,
229
-					'shareWith' => $search,
230
-				],
231
-			];
232
-		}
233
-
234
-		if (!empty($userResults['wide'])) {
235
-			$searchResult->addResultSet($userType, $userResults['wide'], []);
236
-		}
237
-		$searchResult->addResultSet($emailType, $result['wide'], $result['exact']);
238
-
239
-		return !$reachedEnd;
240
-	}
241
-
242
-	public function isCurrentUser(ICloudId $cloud): bool {
243
-		$currentUser = $this->userSession->getUser();
244
-		return $currentUser instanceof IUser ? $currentUser->getUID() === $cloud->getUser() : false;
245
-	}
110
+                            $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
111
+                            $found = false;
112
+                            foreach ($userGroups as $userGroup) {
113
+                                if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
114
+                                    $found = true;
115
+                                    break;
116
+                                }
117
+                            }
118
+                            if (!$found) {
119
+                                continue;
120
+                            }
121
+                        }
122
+                        if ($exactEmailMatch) {
123
+                            try {
124
+                                $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
125
+                            } catch (\InvalidArgumentException $e) {
126
+                                continue;
127
+                            }
128
+
129
+                            if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
130
+                                $singleResult = [[
131
+                                    'label' => $displayName,
132
+                                    'uuid' => $contact['UID'],
133
+                                    'name' => $contact['FN'],
134
+                                    'value' => [
135
+                                        'shareType' => Share::SHARE_TYPE_USER,
136
+                                        'shareWith' => $cloud->getUser(),
137
+                                    ],
138
+                                ]];
139
+                                $searchResult->addResultSet($userType, [], $singleResult);
140
+                                $searchResult->markExactIdMatch($emailType);
141
+                            }
142
+                            return false;
143
+                        }
144
+
145
+                        if ($this->shareeEnumeration) {
146
+                            try {
147
+                                $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0]);
148
+                            } catch (\InvalidArgumentException $e) {
149
+                                continue;
150
+                            }
151
+
152
+                            $addToWide = !$this->shareeEnumerationInGroupOnly;
153
+                            if ($this->shareeEnumerationInGroupOnly) {
154
+                                $addToWide = false;
155
+                                $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
156
+                                foreach ($userGroups as $userGroup) {
157
+                                    if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
158
+                                        $addToWide = true;
159
+                                        break;
160
+                                    }
161
+                                }
162
+                            }
163
+                            if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
164
+                                $userResults['wide'][] = [
165
+                                    'label' => $displayName,
166
+                                    'uuid' => $contact['UID'],
167
+                                    'name' => $contact['FN'],
168
+                                    'value' => [
169
+                                        'shareType' => Share::SHARE_TYPE_USER,
170
+                                        'shareWith' => $cloud->getUser(),
171
+                                    ],
172
+                                ];
173
+                                continue;
174
+                            }
175
+                        }
176
+                        continue;
177
+                    }
178
+
179
+                    if ($exactEmailMatch
180
+                        || isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch) {
181
+                        if ($exactEmailMatch) {
182
+                            $searchResult->markExactIdMatch($emailType);
183
+                        }
184
+                        $result['exact'][] = [
185
+                            'label' => $displayName,
186
+                            'uuid' => $contact['UID'],
187
+                            'name' => $contact['FN'],
188
+                            'type' => $emailAddressType ?? '',
189
+                            'value' => [
190
+                                'shareType' => Share::SHARE_TYPE_EMAIL,
191
+                                'shareWith' => $emailAddress,
192
+                            ],
193
+                        ];
194
+                    } else {
195
+                        $result['wide'][] = [
196
+                            'label' => $displayName,
197
+                            'uuid' => $contact['UID'],
198
+                            'name' => $contact['FN'],
199
+                            'type' => $emailAddressType ?? '',
200
+                            'value' => [
201
+                                'shareType' => Share::SHARE_TYPE_EMAIL,
202
+                                'shareWith' => $emailAddress,
203
+                            ],
204
+                        ];
205
+                    }
206
+                }
207
+            }
208
+        }
209
+
210
+        $reachedEnd = true;
211
+        if (!$this->shareeEnumeration) {
212
+            $result['wide'] = [];
213
+            $userResults['wide'] = [];
214
+        } else {
215
+            $reachedEnd = (count($result['wide']) < $offset + $limit) &&
216
+                (count($userResults['wide']) < $offset + $limit);
217
+
218
+            $result['wide'] = array_slice($result['wide'], $offset, $limit);
219
+            $userResults['wide'] = array_slice($userResults['wide'], $offset, $limit);
220
+        }
221
+
222
+
223
+        if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) {
224
+            $result['exact'][] = [
225
+                'label' => $search,
226
+                'uuid' => $search,
227
+                'value' => [
228
+                    'shareType' => Share::SHARE_TYPE_EMAIL,
229
+                    'shareWith' => $search,
230
+                ],
231
+            ];
232
+        }
233
+
234
+        if (!empty($userResults['wide'])) {
235
+            $searchResult->addResultSet($userType, $userResults['wide'], []);
236
+        }
237
+        $searchResult->addResultSet($emailType, $result['wide'], $result['exact']);
238
+
239
+        return !$reachedEnd;
240
+    }
241
+
242
+    public function isCurrentUser(ICloudId $cloud): bool {
243
+        $currentUser = $this->userSession->getUser();
244
+        return $currentUser instanceof IUser ? $currentUser->getUID() === $cloud->getUser() : false;
245
+    }
246 246
 }
Please login to merge, or discard this patch.
lib/public/IAddressBook.php 1 patch
Indentation   +65 added lines, -65 removed lines patch added patch discarded remove patch
@@ -36,76 +36,76 @@
 block discarded – undo
36 36
 // This means that they should be used by apps instead of the internal ownCloud classes
37 37
 
38 38
 namespace OCP {
39
-	/**
40
-	 * Interface IAddressBook
41
-	 *
42
-	 * @package OCP
43
-	 * @since 5.0.0
44
-	 */
45
-	interface IAddressBook {
39
+    /**
40
+     * Interface IAddressBook
41
+     *
42
+     * @package OCP
43
+     * @since 5.0.0
44
+     */
45
+    interface IAddressBook {
46 46
 
47
-		/**
48
-		 * @return string defining the technical unique key
49
-		 * @since 5.0.0
50
-		 */
51
-		public function getKey();
47
+        /**
48
+         * @return string defining the technical unique key
49
+         * @since 5.0.0
50
+         */
51
+        public function getKey();
52 52
 
53
-		/**
54
-		 * @return string defining the unique uri
55
-		 * @since 16.0.0
56
-		 * @return string
57
-		 */
58
-		public function getUri(): string;
53
+        /**
54
+         * @return string defining the unique uri
55
+         * @since 16.0.0
56
+         * @return string
57
+         */
58
+        public function getUri(): string;
59 59
 
60
-		/**
61
-		 * In comparison to getKey() this function returns a human readable (maybe translated) name
62
-		 * @return mixed
63
-		 * @since 5.0.0
64
-		 */
65
-		public function getDisplayName();
60
+        /**
61
+         * In comparison to getKey() this function returns a human readable (maybe translated) name
62
+         * @return mixed
63
+         * @since 5.0.0
64
+         */
65
+        public function getDisplayName();
66 66
 
67
-		/**
68
-		 * @param string $pattern which should match within the $searchProperties
69
-		 * @param array $searchProperties defines the properties within the query pattern should match
70
-		 * @param array $options Options to define the output format and search behavior
71
-		 * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
72
-		 *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
73
-		 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
74
-		 * 	- 'limit' - Set a numeric limit for the search results
75
-		 * 	- 'offset' - Set the offset for the limited search results
76
-		 * @return array an array of contacts which are arrays of key-value-pairs
77
-		 *  example result:
78
-		 *  [
79
-		 *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
80
-		 *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
81
-		 *	]
82
-		 * @since 5.0.0
83
-		 */
84
-		public function search($pattern, $searchProperties, $options);
67
+        /**
68
+         * @param string $pattern which should match within the $searchProperties
69
+         * @param array $searchProperties defines the properties within the query pattern should match
70
+         * @param array $options Options to define the output format and search behavior
71
+         * 	- 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array
72
+         *    example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => '[email protected]']]
73
+         * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
74
+         * 	- 'limit' - Set a numeric limit for the search results
75
+         * 	- 'offset' - Set the offset for the limited search results
76
+         * @return array an array of contacts which are arrays of key-value-pairs
77
+         *  example result:
78
+         *  [
79
+         *		['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]', 'GEO' => '37.386013;-122.082932'],
80
+         *		['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['[email protected]', '[email protected]']]
81
+         *	]
82
+         * @since 5.0.0
83
+         */
84
+        public function search($pattern, $searchProperties, $options);
85 85
 
86
-		/**
87
-		 * @param array $properties this array if key-value-pairs defines a contact
88
-		 * @return array an array representing the contact just created or updated
89
-		 * @since 5.0.0
90
-		 */
91
-		public function createOrUpdate($properties);
92
-		//	// dummy
93
-		//	return array('id'    => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]',
94
-		//		     'PHOTO' => 'VALUE=uri:http://www.abc.com/pub/photos/jqpublic.gif',
95
-		//		     'ADR'   => ';;123 Main Street;Any Town;CA;91921-1234'
96
-		//	);
86
+        /**
87
+         * @param array $properties this array if key-value-pairs defines a contact
88
+         * @return array an array representing the contact just created or updated
89
+         * @since 5.0.0
90
+         */
91
+        public function createOrUpdate($properties);
92
+        //	// dummy
93
+        //	return array('id'    => 0, 'FN' => 'Thomas Müller', 'EMAIL' => '[email protected]',
94
+        //		     'PHOTO' => 'VALUE=uri:http://www.abc.com/pub/photos/jqpublic.gif',
95
+        //		     'ADR'   => ';;123 Main Street;Any Town;CA;91921-1234'
96
+        //	);
97 97
 
98
-		/**
99
-		 * @return mixed
100
-		 * @since 5.0.0
101
-		 */
102
-		public function getPermissions();
98
+        /**
99
+         * @return mixed
100
+         * @since 5.0.0
101
+         */
102
+        public function getPermissions();
103 103
 
104
-		/**
105
-		 * @param object $id the unique identifier to a contact
106
-		 * @return bool successful or not
107
-		 * @since 5.0.0
108
-		 */
109
-		public function delete($id);
110
-	}
104
+        /**
105
+         * @param object $id the unique identifier to a contact
106
+         * @return bool successful or not
107
+         * @since 5.0.0
108
+         */
109
+        public function delete($id);
110
+    }
111 111
 }
Please login to merge, or discard this patch.
lib/public/Contacts/IManager.php 1 patch
Indentation   +121 added lines, -121 removed lines patch added patch discarded remove patch
@@ -54,134 +54,134 @@
 block discarded – undo
54 54
  */
55 55
 interface IManager {
56 56
 
57
-	/**
58
-	 * This function is used to search and find contacts within the users address books.
59
-	 * In case $pattern is empty all contacts will be returned.
60
-	 *
61
-	 * Example:
62
-	 *  Following function shows how to search for contacts for the name and the email address.
63
-	 *
64
-	 *		public static function getMatchingRecipient($term) {
65
-	 *			$cm = \OC::$server->getContactsManager();
66
-	 *			// The API is not active -> nothing to do
67
-	 *			if (!$cm->isEnabled()) {
68
-	 *				return array();
69
-	 *			}
70
-	 *
71
-	 *			$result = $cm->search($term, array('FN', 'EMAIL'));
72
-	 *			$receivers = array();
73
-	 *			foreach ($result as $r) {
74
-	 *				$id = $r['id'];
75
-	 *				$fn = $r['FN'];
76
-	 *				$email = $r['EMAIL'];
77
-	 *				if (!is_array($email)) {
78
-	 *					$email = array($email);
79
-	 *				}
80
-	 *
81
-	 *				// loop through all email addresses of this contact
82
-	 *				foreach ($email as $e) {
83
-	 *				$displayName = $fn . " <$e>";
84
-	 *				$receivers[] = array(
85
-	 *					'id'    => $id,
86
-	 *					'label' => $displayName,
87
-	 *					'value' => $displayName);
88
-	 *				}
89
-	 *			}
90
-	 *
91
-	 *			return $receivers;
92
-	 *		}
93
-	 *
94
-	 *
95
-	 * @param string $pattern which should match within the $searchProperties
96
-	 * @param array $searchProperties defines the properties within the query pattern should match
97
-	 * @param array $options = array() to define the search behavior
98
-	 * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
99
-	 * 	- 'limit' - Set a numeric limit for the search results
100
-	 * 	- 'offset' - Set the offset for the limited search results
101
-	 * @return array an array of contacts which are arrays of key-value-pairs
102
-	 * @since 6.0.0
103
-	 */
104
-	public function search($pattern, $searchProperties = [], $options = []);
57
+    /**
58
+     * This function is used to search and find contacts within the users address books.
59
+     * In case $pattern is empty all contacts will be returned.
60
+     *
61
+     * Example:
62
+     *  Following function shows how to search for contacts for the name and the email address.
63
+     *
64
+     *		public static function getMatchingRecipient($term) {
65
+     *			$cm = \OC::$server->getContactsManager();
66
+     *			// The API is not active -> nothing to do
67
+     *			if (!$cm->isEnabled()) {
68
+     *				return array();
69
+     *			}
70
+     *
71
+     *			$result = $cm->search($term, array('FN', 'EMAIL'));
72
+     *			$receivers = array();
73
+     *			foreach ($result as $r) {
74
+     *				$id = $r['id'];
75
+     *				$fn = $r['FN'];
76
+     *				$email = $r['EMAIL'];
77
+     *				if (!is_array($email)) {
78
+     *					$email = array($email);
79
+     *				}
80
+     *
81
+     *				// loop through all email addresses of this contact
82
+     *				foreach ($email as $e) {
83
+     *				$displayName = $fn . " <$e>";
84
+     *				$receivers[] = array(
85
+     *					'id'    => $id,
86
+     *					'label' => $displayName,
87
+     *					'value' => $displayName);
88
+     *				}
89
+     *			}
90
+     *
91
+     *			return $receivers;
92
+     *		}
93
+     *
94
+     *
95
+     * @param string $pattern which should match within the $searchProperties
96
+     * @param array $searchProperties defines the properties within the query pattern should match
97
+     * @param array $options = array() to define the search behavior
98
+     * 	- 'escape_like_param' - If set to false wildcards _ and % are not escaped
99
+     * 	- 'limit' - Set a numeric limit for the search results
100
+     * 	- 'offset' - Set the offset for the limited search results
101
+     * @return array an array of contacts which are arrays of key-value-pairs
102
+     * @since 6.0.0
103
+     */
104
+    public function search($pattern, $searchProperties = [], $options = []);
105 105
 
106
-	/**
107
-	 * This function can be used to delete the contact identified by the given id
108
-	 *
109
-	 * @param object $id the unique identifier to a contact
110
-	 * @param string $address_book_key identifier of the address book in which the contact shall be deleted
111
-	 * @return bool successful or not
112
-	 * @since 6.0.0
113
-	 */
114
-	public function delete($id, $address_book_key);
106
+    /**
107
+     * This function can be used to delete the contact identified by the given id
108
+     *
109
+     * @param object $id the unique identifier to a contact
110
+     * @param string $address_book_key identifier of the address book in which the contact shall be deleted
111
+     * @return bool successful or not
112
+     * @since 6.0.0
113
+     */
114
+    public function delete($id, $address_book_key);
115 115
 
116
-	/**
117
-	 * This function is used to create a new contact if 'id' is not given or not present.
118
-	 * Otherwise the contact will be updated by replacing the entire data set.
119
-	 *
120
-	 * @param array $properties this array if key-value-pairs defines a contact
121
-	 * @param string $address_book_key identifier of the address book in which the contact shall be created or updated
122
-	 * @return array an array representing the contact just created or updated
123
-	 * @since 6.0.0
124
-	 */
125
-	public function createOrUpdate($properties, $address_book_key);
116
+    /**
117
+     * This function is used to create a new contact if 'id' is not given or not present.
118
+     * Otherwise the contact will be updated by replacing the entire data set.
119
+     *
120
+     * @param array $properties this array if key-value-pairs defines a contact
121
+     * @param string $address_book_key identifier of the address book in which the contact shall be created or updated
122
+     * @return array an array representing the contact just created or updated
123
+     * @since 6.0.0
124
+     */
125
+    public function createOrUpdate($properties, $address_book_key);
126 126
 
127
-	/**
128
-	 * Check if contacts are available (e.g. contacts app enabled)
129
-	 *
130
-	 * @return bool true if enabled, false if not
131
-	 * @since 6.0.0
132
-	 */
133
-	public function isEnabled();
127
+    /**
128
+     * Check if contacts are available (e.g. contacts app enabled)
129
+     *
130
+     * @return bool true if enabled, false if not
131
+     * @since 6.0.0
132
+     */
133
+    public function isEnabled();
134 134
 
135
-	/**
136
-	 * Registers an address book
137
-	 *
138
-	 * @param \OCP\IAddressBook $address_book
139
-	 * @return void
140
-	 * @since 6.0.0
141
-	 */
142
-	public function registerAddressBook(\OCP\IAddressBook $address_book);
135
+    /**
136
+     * Registers an address book
137
+     *
138
+     * @param \OCP\IAddressBook $address_book
139
+     * @return void
140
+     * @since 6.0.0
141
+     */
142
+    public function registerAddressBook(\OCP\IAddressBook $address_book);
143 143
 
144
-	/**
145
-	 * Unregisters an address book
146
-	 *
147
-	 * @param \OCP\IAddressBook $address_book
148
-	 * @return void
149
-	 * @since 6.0.0
150
-	 */
151
-	public function unregisterAddressBook(\OCP\IAddressBook $address_book);
144
+    /**
145
+     * Unregisters an address book
146
+     *
147
+     * @param \OCP\IAddressBook $address_book
148
+     * @return void
149
+     * @since 6.0.0
150
+     */
151
+    public function unregisterAddressBook(\OCP\IAddressBook $address_book);
152 152
 
153
-	/**
154
-	 * In order to improve lazy loading a closure can be registered which will be called in case
155
-	 * address books are actually requested
156
-	 *
157
-	 * @param \Closure $callable
158
-	 * @return void
159
-	 * @since 6.0.0
160
-	 */
161
-	public function register(\Closure $callable);
153
+    /**
154
+     * In order to improve lazy loading a closure can be registered which will be called in case
155
+     * address books are actually requested
156
+     *
157
+     * @param \Closure $callable
158
+     * @return void
159
+     * @since 6.0.0
160
+     */
161
+    public function register(\Closure $callable);
162 162
 
163
-	/**
164
-	 * Return a list of the user's addressbooks display names
165
-	 *
166
-	 * @return array
167
-	 * @since 6.0.0
168
-	 * @deprecated 16.0.0 - Use `$this->getUserAddressBooks()` instead
169
-	 */
170
-	public function getAddressBooks();
163
+    /**
164
+     * Return a list of the user's addressbooks display names
165
+     *
166
+     * @return array
167
+     * @since 6.0.0
168
+     * @deprecated 16.0.0 - Use `$this->getUserAddressBooks()` instead
169
+     */
170
+    public function getAddressBooks();
171 171
 
172
-	/**
173
-	 * Return a list of the user's addressbooks
174
-	 *
175
-	 * @return IAddressBook[]
176
-	 * @since 16.0.0
177
-	 */
178
-	public function getUserAddressBooks();
172
+    /**
173
+     * Return a list of the user's addressbooks
174
+     *
175
+     * @return IAddressBook[]
176
+     * @since 16.0.0
177
+     */
178
+    public function getUserAddressBooks();
179 179
 
180
-	/**
181
-	 * removes all registered address book instances
182
-	 *
183
-	 * @return void
184
-	 * @since 6.0.0
185
-	 */
186
-	public function clear();
180
+    /**
181
+     * removes all registered address book instances
182
+     *
183
+     * @return void
184
+     * @since 6.0.0
185
+     */
186
+    public function clear();
187 187
 }
Please login to merge, or discard this patch.