Passed
Push — master ( 0ecef7...7d2f5a )
by Blizzz
13:12 queued 11s
created
apps/dav/lib/CardDAV/CardDavBackend.php 2 patches
Indentation   +1262 added lines, -1262 removed lines patch added patch discarded remove patch
@@ -56,1266 +56,1266 @@
 block discarded – undo
56 56
 use Symfony\Component\EventDispatcher\GenericEvent;
57 57
 
58 58
 class CardDavBackend implements BackendInterface, SyncSupport {
59
-	public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
60
-	public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
61
-
62
-	/** @var Principal */
63
-	private $principalBackend;
64
-
65
-	/** @var string */
66
-	private $dbCardsTable = 'cards';
67
-
68
-	/** @var string */
69
-	private $dbCardsPropertiesTable = 'cards_properties';
70
-
71
-	/** @var IDBConnection */
72
-	private $db;
73
-
74
-	/** @var Backend */
75
-	private $sharingBackend;
76
-
77
-	/** @var array properties to index */
78
-	public static $indexProperties = [
79
-		'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
80
-		'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
81
-
82
-	/**
83
-	 * @var string[] Map of uid => display name
84
-	 */
85
-	protected $userDisplayNames;
86
-
87
-	/** @var IUserManager */
88
-	private $userManager;
89
-
90
-	/** @var EventDispatcherInterface */
91
-	private $dispatcher;
92
-
93
-	private $etagCache = [];
94
-
95
-	/**
96
-	 * CardDavBackend constructor.
97
-	 *
98
-	 * @param IDBConnection $db
99
-	 * @param Principal $principalBackend
100
-	 * @param IUserManager $userManager
101
-	 * @param IGroupManager $groupManager
102
-	 * @param EventDispatcherInterface $dispatcher
103
-	 */
104
-	public function __construct(IDBConnection $db,
105
-								Principal $principalBackend,
106
-								IUserManager $userManager,
107
-								IGroupManager $groupManager,
108
-								EventDispatcherInterface $dispatcher) {
109
-		$this->db = $db;
110
-		$this->principalBackend = $principalBackend;
111
-		$this->userManager = $userManager;
112
-		$this->dispatcher = $dispatcher;
113
-		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
114
-	}
115
-
116
-	/**
117
-	 * Return the number of address books for a principal
118
-	 *
119
-	 * @param $principalUri
120
-	 * @return int
121
-	 */
122
-	public function getAddressBooksForUserCount($principalUri) {
123
-		$principalUri = $this->convertPrincipal($principalUri, true);
124
-		$query = $this->db->getQueryBuilder();
125
-		$query->select($query->func()->count('*'))
126
-			->from('addressbooks')
127
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
128
-
129
-		return (int)$query->execute()->fetchColumn();
130
-	}
131
-
132
-	/**
133
-	 * Returns the list of address books for a specific user.
134
-	 *
135
-	 * Every addressbook should have the following properties:
136
-	 *   id - an arbitrary unique id
137
-	 *   uri - the 'basename' part of the url
138
-	 *   principaluri - Same as the passed parameter
139
-	 *
140
-	 * Any additional clark-notation property may be passed besides this. Some
141
-	 * common ones are :
142
-	 *   {DAV:}displayname
143
-	 *   {urn:ietf:params:xml:ns:carddav}addressbook-description
144
-	 *   {http://calendarserver.org/ns/}getctag
145
-	 *
146
-	 * @param string $principalUri
147
-	 * @return array
148
-	 */
149
-	public function getAddressBooksForUser($principalUri) {
150
-		$principalUriOriginal = $principalUri;
151
-		$principalUri = $this->convertPrincipal($principalUri, true);
152
-		$query = $this->db->getQueryBuilder();
153
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
154
-			->from('addressbooks')
155
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
156
-
157
-		$addressBooks = [];
158
-
159
-		$result = $query->execute();
160
-		while ($row = $result->fetch()) {
161
-			$addressBooks[$row['id']] = [
162
-				'id' => $row['id'],
163
-				'uri' => $row['uri'],
164
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
165
-				'{DAV:}displayname' => $row['displayname'],
166
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
167
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
168
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
169
-			];
170
-
171
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
172
-		}
173
-		$result->closeCursor();
174
-
175
-		// query for shared addressbooks
176
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
177
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
178
-
179
-		$principals = array_map(function ($principal) {
180
-			return urldecode($principal);
181
-		}, $principals);
182
-		$principals[] = $principalUri;
183
-
184
-		$query = $this->db->getQueryBuilder();
185
-		$result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
186
-			->from('dav_shares', 's')
187
-			->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
188
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
189
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
190
-			->setParameter('type', 'addressbook')
191
-			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
192
-			->execute();
193
-
194
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
195
-		while ($row = $result->fetch()) {
196
-			if ($row['principaluri'] === $principalUri) {
197
-				continue;
198
-			}
199
-
200
-			$readOnly = (int)$row['access'] === Backend::ACCESS_READ;
201
-			if (isset($addressBooks[$row['id']])) {
202
-				if ($readOnly) {
203
-					// New share can not have more permissions then the old one.
204
-					continue;
205
-				}
206
-				if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
207
-					$addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
208
-					// Old share is already read-write, no more permissions can be gained
209
-					continue;
210
-				}
211
-			}
212
-
213
-			list(, $name) = \Sabre\Uri\split($row['principaluri']);
214
-			$uri = $row['uri'] . '_shared_by_' . $name;
215
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
216
-
217
-			$addressBooks[$row['id']] = [
218
-				'id' => $row['id'],
219
-				'uri' => $uri,
220
-				'principaluri' => $principalUriOriginal,
221
-				'{DAV:}displayname' => $displayName,
222
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
223
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
224
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
225
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
226
-				$readOnlyPropertyName => $readOnly,
227
-			];
228
-
229
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
230
-		}
231
-		$result->closeCursor();
232
-
233
-		return array_values($addressBooks);
234
-	}
235
-
236
-	public function getUsersOwnAddressBooks($principalUri) {
237
-		$principalUri = $this->convertPrincipal($principalUri, true);
238
-		$query = $this->db->getQueryBuilder();
239
-		$query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
240
-			->from('addressbooks')
241
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
242
-
243
-		$addressBooks = [];
244
-
245
-		$result = $query->execute();
246
-		while ($row = $result->fetch()) {
247
-			$addressBooks[$row['id']] = [
248
-				'id' => $row['id'],
249
-				'uri' => $row['uri'],
250
-				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
251
-				'{DAV:}displayname' => $row['displayname'],
252
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
253
-				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
254
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
255
-			];
256
-
257
-			$this->addOwnerPrincipal($addressBooks[$row['id']]);
258
-		}
259
-		$result->closeCursor();
260
-
261
-		return array_values($addressBooks);
262
-	}
263
-
264
-	private function getUserDisplayName($uid) {
265
-		if (!isset($this->userDisplayNames[$uid])) {
266
-			$user = $this->userManager->get($uid);
267
-
268
-			if ($user instanceof IUser) {
269
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
270
-			} else {
271
-				$this->userDisplayNames[$uid] = $uid;
272
-			}
273
-		}
274
-
275
-		return $this->userDisplayNames[$uid];
276
-	}
277
-
278
-	/**
279
-	 * @param int $addressBookId
280
-	 */
281
-	public function getAddressBookById($addressBookId) {
282
-		$query = $this->db->getQueryBuilder();
283
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
284
-			->from('addressbooks')
285
-			->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
286
-			->execute();
287
-
288
-		$row = $result->fetch();
289
-		$result->closeCursor();
290
-		if ($row === false) {
291
-			return null;
292
-		}
293
-
294
-		$addressBook = [
295
-			'id' => $row['id'],
296
-			'uri' => $row['uri'],
297
-			'principaluri' => $row['principaluri'],
298
-			'{DAV:}displayname' => $row['displayname'],
299
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
300
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
301
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
302
-		];
303
-
304
-		$this->addOwnerPrincipal($addressBook);
305
-
306
-		return $addressBook;
307
-	}
308
-
309
-	/**
310
-	 * @param $addressBookUri
311
-	 * @return array|null
312
-	 */
313
-	public function getAddressBooksByUri($principal, $addressBookUri) {
314
-		$query = $this->db->getQueryBuilder();
315
-		$result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
316
-			->from('addressbooks')
317
-			->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
318
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
319
-			->setMaxResults(1)
320
-			->execute();
321
-
322
-		$row = $result->fetch();
323
-		$result->closeCursor();
324
-		if ($row === false) {
325
-			return null;
326
-		}
327
-
328
-		$addressBook = [
329
-			'id' => $row['id'],
330
-			'uri' => $row['uri'],
331
-			'principaluri' => $row['principaluri'],
332
-			'{DAV:}displayname' => $row['displayname'],
333
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
334
-			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
335
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
336
-		];
337
-
338
-		$this->addOwnerPrincipal($addressBook);
339
-
340
-		return $addressBook;
341
-	}
342
-
343
-	/**
344
-	 * Updates properties for an address book.
345
-	 *
346
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
347
-	 * To do the actual updates, you must tell this object which properties
348
-	 * you're going to process with the handle() method.
349
-	 *
350
-	 * Calling the handle method is like telling the PropPatch object "I
351
-	 * promise I can handle updating this property".
352
-	 *
353
-	 * Read the PropPatch documentation for more info and examples.
354
-	 *
355
-	 * @param string $addressBookId
356
-	 * @param \Sabre\DAV\PropPatch $propPatch
357
-	 * @return void
358
-	 */
359
-	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
360
-		$supportedProperties = [
361
-			'{DAV:}displayname',
362
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
363
-		];
364
-
365
-		/**
366
-		 * @suppress SqlInjectionChecker
367
-		 */
368
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
369
-			$updates = [];
370
-			foreach ($mutations as $property => $newValue) {
371
-				switch ($property) {
372
-					case '{DAV:}displayname':
373
-						$updates['displayname'] = $newValue;
374
-						break;
375
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
376
-						$updates['description'] = $newValue;
377
-						break;
378
-				}
379
-			}
380
-			$query = $this->db->getQueryBuilder();
381
-			$query->update('addressbooks');
382
-
383
-			foreach ($updates as $key => $value) {
384
-				$query->set($key, $query->createNamedParameter($value));
385
-			}
386
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
387
-				->execute();
388
-
389
-			$this->addChange($addressBookId, "", 2);
390
-
391
-			return true;
392
-		});
393
-	}
394
-
395
-	/**
396
-	 * Creates a new address book
397
-	 *
398
-	 * @param string $principalUri
399
-	 * @param string $url Just the 'basename' of the url.
400
-	 * @param array $properties
401
-	 * @return int
402
-	 * @throws BadRequest
403
-	 */
404
-	public function createAddressBook($principalUri, $url, array $properties) {
405
-		$values = [
406
-			'displayname' => null,
407
-			'description' => null,
408
-			'principaluri' => $principalUri,
409
-			'uri' => $url,
410
-			'synctoken' => 1
411
-		];
412
-
413
-		foreach ($properties as $property => $newValue) {
414
-			switch ($property) {
415
-				case '{DAV:}displayname':
416
-					$values['displayname'] = $newValue;
417
-					break;
418
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
419
-					$values['description'] = $newValue;
420
-					break;
421
-				default:
422
-					throw new BadRequest('Unknown property: ' . $property);
423
-			}
424
-		}
425
-
426
-		// Fallback to make sure the displayname is set. Some clients may refuse
427
-		// to work with addressbooks not having a displayname.
428
-		if (is_null($values['displayname'])) {
429
-			$values['displayname'] = $url;
430
-		}
431
-
432
-		$query = $this->db->getQueryBuilder();
433
-		$query->insert('addressbooks')
434
-			->values([
435
-				'uri' => $query->createParameter('uri'),
436
-				'displayname' => $query->createParameter('displayname'),
437
-				'description' => $query->createParameter('description'),
438
-				'principaluri' => $query->createParameter('principaluri'),
439
-				'synctoken' => $query->createParameter('synctoken'),
440
-			])
441
-			->setParameters($values)
442
-			->execute();
443
-
444
-		return $query->getLastInsertId();
445
-	}
446
-
447
-	/**
448
-	 * Deletes an entire addressbook and all its contents
449
-	 *
450
-	 * @param mixed $addressBookId
451
-	 * @return void
452
-	 */
453
-	public function deleteAddressBook($addressBookId) {
454
-		$query = $this->db->getQueryBuilder();
455
-		$query->delete($this->dbCardsTable)
456
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
457
-			->setParameter('addressbookid', $addressBookId)
458
-			->execute();
459
-
460
-		$query->delete('addressbookchanges')
461
-			->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
462
-			->setParameter('addressbookid', $addressBookId)
463
-			->execute();
464
-
465
-		$query->delete('addressbooks')
466
-			->where($query->expr()->eq('id', $query->createParameter('id')))
467
-			->setParameter('id', $addressBookId)
468
-			->execute();
469
-
470
-		$this->sharingBackend->deleteAllShares($addressBookId);
471
-
472
-		$query->delete($this->dbCardsPropertiesTable)
473
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
474
-			->execute();
475
-	}
476
-
477
-	/**
478
-	 * Returns all cards for a specific addressbook id.
479
-	 *
480
-	 * This method should return the following properties for each card:
481
-	 *   * carddata - raw vcard data
482
-	 *   * uri - Some unique url
483
-	 *   * lastmodified - A unix timestamp
484
-	 *
485
-	 * It's recommended to also return the following properties:
486
-	 *   * etag - A unique etag. This must change every time the card changes.
487
-	 *   * size - The size of the card in bytes.
488
-	 *
489
-	 * If these last two properties are provided, less time will be spent
490
-	 * calculating them. If they are specified, you can also ommit carddata.
491
-	 * This may speed up certain requests, especially with large cards.
492
-	 *
493
-	 * @param mixed $addressBookId
494
-	 * @return array
495
-	 */
496
-	public function getCards($addressBookId) {
497
-		$query = $this->db->getQueryBuilder();
498
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
499
-			->from($this->dbCardsTable)
500
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
501
-
502
-		$cards = [];
503
-
504
-		$result = $query->execute();
505
-		while ($row = $result->fetch()) {
506
-			$row['etag'] = '"' . $row['etag'] . '"';
507
-
508
-			$modified = false;
509
-			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
510
-			if ($modified) {
511
-				$row['size'] = strlen($row['carddata']);
512
-			}
513
-
514
-			$cards[] = $row;
515
-		}
516
-		$result->closeCursor();
517
-
518
-		return $cards;
519
-	}
520
-
521
-	/**
522
-	 * Returns a specific card.
523
-	 *
524
-	 * The same set of properties must be returned as with getCards. The only
525
-	 * exception is that 'carddata' is absolutely required.
526
-	 *
527
-	 * If the card does not exist, you must return false.
528
-	 *
529
-	 * @param mixed $addressBookId
530
-	 * @param string $cardUri
531
-	 * @return array
532
-	 */
533
-	public function getCard($addressBookId, $cardUri) {
534
-		$query = $this->db->getQueryBuilder();
535
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
536
-			->from($this->dbCardsTable)
537
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
538
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
539
-			->setMaxResults(1);
540
-
541
-		$result = $query->execute();
542
-		$row = $result->fetch();
543
-		if (!$row) {
544
-			return false;
545
-		}
546
-		$row['etag'] = '"' . $row['etag'] . '"';
547
-
548
-		$modified = false;
549
-		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
550
-		if ($modified) {
551
-			$row['size'] = strlen($row['carddata']);
552
-		}
553
-
554
-		return $row;
555
-	}
556
-
557
-	/**
558
-	 * Returns a list of cards.
559
-	 *
560
-	 * This method should work identical to getCard, but instead return all the
561
-	 * cards in the list as an array.
562
-	 *
563
-	 * If the backend supports this, it may allow for some speed-ups.
564
-	 *
565
-	 * @param mixed $addressBookId
566
-	 * @param string[] $uris
567
-	 * @return array
568
-	 */
569
-	public function getMultipleCards($addressBookId, array $uris) {
570
-		if (empty($uris)) {
571
-			return [];
572
-		}
573
-
574
-		$chunks = array_chunk($uris, 100);
575
-		$cards = [];
576
-
577
-		$query = $this->db->getQueryBuilder();
578
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
579
-			->from($this->dbCardsTable)
580
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
581
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
582
-
583
-		foreach ($chunks as $uris) {
584
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
585
-			$result = $query->execute();
586
-
587
-			while ($row = $result->fetch()) {
588
-				$row['etag'] = '"' . $row['etag'] . '"';
589
-
590
-				$modified = false;
591
-				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
592
-				if ($modified) {
593
-					$row['size'] = strlen($row['carddata']);
594
-				}
595
-
596
-				$cards[] = $row;
597
-			}
598
-			$result->closeCursor();
599
-		}
600
-		return $cards;
601
-	}
602
-
603
-	/**
604
-	 * Creates a new card.
605
-	 *
606
-	 * The addressbook id will be passed as the first argument. This is the
607
-	 * same id as it is returned from the getAddressBooksForUser method.
608
-	 *
609
-	 * The cardUri is a base uri, and doesn't include the full path. The
610
-	 * cardData argument is the vcard body, and is passed as a string.
611
-	 *
612
-	 * It is possible to return an ETag from this method. This ETag is for the
613
-	 * newly created resource, and must be enclosed with double quotes (that
614
-	 * is, the string itself must contain the double quotes).
615
-	 *
616
-	 * You should only return the ETag if you store the carddata as-is. If a
617
-	 * subsequent GET request on the same card does not have the same body,
618
-	 * byte-by-byte and you did return an ETag here, clients tend to get
619
-	 * confused.
620
-	 *
621
-	 * If you don't return an ETag, you can just return null.
622
-	 *
623
-	 * @param mixed $addressBookId
624
-	 * @param string $cardUri
625
-	 * @param string $cardData
626
-	 * @return string
627
-	 */
628
-	public function createCard($addressBookId, $cardUri, $cardData) {
629
-		$etag = md5($cardData);
630
-		$uid = $this->getUID($cardData);
631
-
632
-		$q = $this->db->getQueryBuilder();
633
-		$q->select('uid')
634
-			->from($this->dbCardsTable)
635
-			->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
636
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
637
-			->setMaxResults(1);
638
-		$result = $q->execute();
639
-		$count = (bool)$result->fetchColumn();
640
-		$result->closeCursor();
641
-		if ($count) {
642
-			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
643
-		}
644
-
645
-		$query = $this->db->getQueryBuilder();
646
-		$query->insert('cards')
647
-			->values([
648
-				'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
649
-				'uri' => $query->createNamedParameter($cardUri),
650
-				'lastmodified' => $query->createNamedParameter(time()),
651
-				'addressbookid' => $query->createNamedParameter($addressBookId),
652
-				'size' => $query->createNamedParameter(strlen($cardData)),
653
-				'etag' => $query->createNamedParameter($etag),
654
-				'uid' => $query->createNamedParameter($uid),
655
-			])
656
-			->execute();
657
-
658
-		$etagCacheKey = "$addressBookId#$cardUri";
659
-		$this->etagCache[$etagCacheKey] = $etag;
660
-
661
-		$this->addChange($addressBookId, $cardUri, 1);
662
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
663
-
664
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
665
-			new GenericEvent(null, [
666
-				'addressBookId' => $addressBookId,
667
-				'cardUri' => $cardUri,
668
-				'cardData' => $cardData]));
669
-
670
-		return '"' . $etag . '"';
671
-	}
672
-
673
-	/**
674
-	 * Updates a card.
675
-	 *
676
-	 * The addressbook id will be passed as the first argument. This is the
677
-	 * same id as it is returned from the getAddressBooksForUser method.
678
-	 *
679
-	 * The cardUri is a base uri, and doesn't include the full path. The
680
-	 * cardData argument is the vcard body, and is passed as a string.
681
-	 *
682
-	 * It is possible to return an ETag from this method. This ETag should
683
-	 * match that of the updated resource, and must be enclosed with double
684
-	 * quotes (that is: the string itself must contain the actual quotes).
685
-	 *
686
-	 * You should only return the ETag if you store the carddata as-is. If a
687
-	 * subsequent GET request on the same card does not have the same body,
688
-	 * byte-by-byte and you did return an ETag here, clients tend to get
689
-	 * confused.
690
-	 *
691
-	 * If you don't return an ETag, you can just return null.
692
-	 *
693
-	 * @param mixed $addressBookId
694
-	 * @param string $cardUri
695
-	 * @param string $cardData
696
-	 * @return string
697
-	 */
698
-	public function updateCard($addressBookId, $cardUri, $cardData) {
699
-		$uid = $this->getUID($cardData);
700
-		$etag = md5($cardData);
701
-		$query = $this->db->getQueryBuilder();
702
-
703
-		// check for recently stored etag and stop if it is the same
704
-		$etagCacheKey = "$addressBookId#$cardUri";
705
-		if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
706
-			return '"' . $etag . '"';
707
-		}
708
-
709
-		$query->update($this->dbCardsTable)
710
-			->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
711
-			->set('lastmodified', $query->createNamedParameter(time()))
712
-			->set('size', $query->createNamedParameter(strlen($cardData)))
713
-			->set('etag', $query->createNamedParameter($etag))
714
-			->set('uid', $query->createNamedParameter($uid))
715
-			->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
716
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
717
-			->execute();
718
-
719
-		$this->etagCache[$etagCacheKey] = $etag;
720
-
721
-		$this->addChange($addressBookId, $cardUri, 2);
722
-		$this->updateProperties($addressBookId, $cardUri, $cardData);
723
-
724
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
725
-			new GenericEvent(null, [
726
-				'addressBookId' => $addressBookId,
727
-				'cardUri' => $cardUri,
728
-				'cardData' => $cardData]));
729
-
730
-		return '"' . $etag . '"';
731
-	}
732
-
733
-	/**
734
-	 * Deletes a card
735
-	 *
736
-	 * @param mixed $addressBookId
737
-	 * @param string $cardUri
738
-	 * @return bool
739
-	 */
740
-	public function deleteCard($addressBookId, $cardUri) {
741
-		try {
742
-			$cardId = $this->getCardId($addressBookId, $cardUri);
743
-		} catch (\InvalidArgumentException $e) {
744
-			$cardId = null;
745
-		}
746
-		$query = $this->db->getQueryBuilder();
747
-		$ret = $query->delete($this->dbCardsTable)
748
-			->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
749
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
750
-			->execute();
751
-
752
-		$this->addChange($addressBookId, $cardUri, 3);
753
-
754
-		$this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
755
-			new GenericEvent(null, [
756
-				'addressBookId' => $addressBookId,
757
-				'cardUri' => $cardUri]));
758
-
759
-		if ($ret === 1) {
760
-			if ($cardId !== null) {
761
-				$this->purgeProperties($addressBookId, $cardId);
762
-			}
763
-			return true;
764
-		}
765
-
766
-		return false;
767
-	}
768
-
769
-	/**
770
-	 * The getChanges method returns all the changes that have happened, since
771
-	 * the specified syncToken in the specified address book.
772
-	 *
773
-	 * This function should return an array, such as the following:
774
-	 *
775
-	 * [
776
-	 *   'syncToken' => 'The current synctoken',
777
-	 *   'added'   => [
778
-	 *      'new.txt',
779
-	 *   ],
780
-	 *   'modified'   => [
781
-	 *      'modified.txt',
782
-	 *   ],
783
-	 *   'deleted' => [
784
-	 *      'foo.php.bak',
785
-	 *      'old.txt'
786
-	 *   ]
787
-	 * ];
788
-	 *
789
-	 * The returned syncToken property should reflect the *current* syncToken
790
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
791
-	 * property. This is needed here too, to ensure the operation is atomic.
792
-	 *
793
-	 * If the $syncToken argument is specified as null, this is an initial
794
-	 * sync, and all members should be reported.
795
-	 *
796
-	 * The modified property is an array of nodenames that have changed since
797
-	 * the last token.
798
-	 *
799
-	 * The deleted property is an array with nodenames, that have been deleted
800
-	 * from collection.
801
-	 *
802
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
803
-	 * 1, you only have to report changes that happened only directly in
804
-	 * immediate descendants. If it's 2, it should also include changes from
805
-	 * the nodes below the child collections. (grandchildren)
806
-	 *
807
-	 * The $limit argument allows a client to specify how many results should
808
-	 * be returned at most. If the limit is not specified, it should be treated
809
-	 * as infinite.
810
-	 *
811
-	 * If the limit (infinite or not) is higher than you're willing to return,
812
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
813
-	 *
814
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
815
-	 * return null.
816
-	 *
817
-	 * The limit is 'suggestive'. You are free to ignore it.
818
-	 *
819
-	 * @param string $addressBookId
820
-	 * @param string $syncToken
821
-	 * @param int $syncLevel
822
-	 * @param int $limit
823
-	 * @return array
824
-	 */
825
-	public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
826
-		// Current synctoken
827
-		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
828
-		$stmt->execute([$addressBookId]);
829
-		$currentToken = $stmt->fetchColumn(0);
830
-
831
-		if (is_null($currentToken)) {
832
-			return null;
833
-		}
834
-
835
-		$result = [
836
-			'syncToken' => $currentToken,
837
-			'added' => [],
838
-			'modified' => [],
839
-			'deleted' => [],
840
-		];
841
-
842
-		if ($syncToken) {
843
-			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
844
-			if ($limit > 0) {
845
-				$query .= " LIMIT " . (int)$limit;
846
-			}
847
-
848
-			// Fetching all changes
849
-			$stmt = $this->db->prepare($query);
850
-			$stmt->execute([$syncToken, $currentToken, $addressBookId]);
851
-
852
-			$changes = [];
853
-
854
-			// This loop ensures that any duplicates are overwritten, only the
855
-			// last change on a node is relevant.
856
-			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
857
-				$changes[$row['uri']] = $row['operation'];
858
-			}
859
-
860
-			foreach ($changes as $uri => $operation) {
861
-				switch ($operation) {
862
-					case 1:
863
-						$result['added'][] = $uri;
864
-						break;
865
-					case 2:
866
-						$result['modified'][] = $uri;
867
-						break;
868
-					case 3:
869
-						$result['deleted'][] = $uri;
870
-						break;
871
-				}
872
-			}
873
-		} else {
874
-			// No synctoken supplied, this is the initial sync.
875
-			$query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
876
-			$stmt = $this->db->prepare($query);
877
-			$stmt->execute([$addressBookId]);
878
-
879
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
880
-		}
881
-		return $result;
882
-	}
883
-
884
-	/**
885
-	 * Adds a change record to the addressbookchanges table.
886
-	 *
887
-	 * @param mixed $addressBookId
888
-	 * @param string $objectUri
889
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete
890
-	 * @return void
891
-	 */
892
-	protected function addChange($addressBookId, $objectUri, $operation) {
893
-		$sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
894
-		$stmt = $this->db->prepare($sql);
895
-		$stmt->execute([
896
-			$objectUri,
897
-			$addressBookId,
898
-			$operation,
899
-			$addressBookId
900
-		]);
901
-		$stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
902
-		$stmt->execute([
903
-			$addressBookId
904
-		]);
905
-	}
906
-
907
-	/**
908
-	 * @param resource|string $cardData
909
-	 * @param bool $modified
910
-	 * @return string
911
-	 */
912
-	private function readBlob($cardData, &$modified = false) {
913
-		if (is_resource($cardData)) {
914
-			$cardData = stream_get_contents($cardData);
915
-		}
916
-
917
-		$cardDataArray = explode("\r\n", $cardData);
918
-
919
-		$cardDataFiltered = [];
920
-		$removingPhoto = false;
921
-		foreach ($cardDataArray as $line) {
922
-			if (strpos($line, 'PHOTO:data:') === 0
923
-				&& strpos($line, 'PHOTO:data:image/') !== 0) {
924
-				// Filter out PHOTO data of non-images
925
-				$removingPhoto = true;
926
-				$modified = true;
927
-				continue;
928
-			}
929
-
930
-			if ($removingPhoto) {
931
-				if (strpos($line, ' ') === 0) {
932
-					continue;
933
-				}
934
-				// No leading space means this is a new property
935
-				$removingPhoto = false;
936
-			}
937
-
938
-			$cardDataFiltered[] = $line;
939
-		}
940
-
941
-		return implode("\r\n", $cardDataFiltered);
942
-	}
943
-
944
-	/**
945
-	 * @param IShareable $shareable
946
-	 * @param string[] $add
947
-	 * @param string[] $remove
948
-	 */
949
-	public function updateShares(IShareable $shareable, $add, $remove) {
950
-		$this->sharingBackend->updateShares($shareable, $add, $remove);
951
-	}
952
-
953
-	/**
954
-	 * Search contacts in a specific address-book
955
-	 *
956
-	 * @param int $addressBookId
957
-	 * @param string $pattern which should match within the $searchProperties
958
-	 * @param array $searchProperties defines the properties within the query pattern should match
959
-	 * @param array $options = array() to define the search behavior
960
-	 *    - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
961
-	 *    - 'limit' - Set a numeric limit for the search results
962
-	 *    - 'offset' - Set the offset for the limited search results
963
-	 * @return array an array of contacts which are arrays of key-value-pairs
964
-	 */
965
-	public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
966
-		return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
967
-	}
968
-
969
-	/**
970
-	 * Search contacts in all address-books accessible by a user
971
-	 *
972
-	 * @param string $principalUri
973
-	 * @param string $pattern
974
-	 * @param array $searchProperties
975
-	 * @param array $options
976
-	 * @return array
977
-	 */
978
-	public function searchPrincipalUri(string $principalUri,
979
-									   string $pattern,
980
-									   array $searchProperties,
981
-									   array $options = []): array {
982
-		$addressBookIds = array_map(static function ($row):int {
983
-			return (int) $row['id'];
984
-		}, $this->getAddressBooksForUser($principalUri));
985
-
986
-		return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
987
-	}
988
-
989
-	/**
990
-	 * @param array $addressBookIds
991
-	 * @param string $pattern
992
-	 * @param array $searchProperties
993
-	 * @param array $options
994
-	 * @return array
995
-	 */
996
-	private function searchByAddressBookIds(array $addressBookIds,
997
-											string $pattern,
998
-											array $searchProperties,
999
-											array $options = []): array {
1000
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1001
-
1002
-		$query2 = $this->db->getQueryBuilder();
1003
-
1004
-		$addressBookOr =  $query2->expr()->orX();
1005
-		foreach ($addressBookIds as $addressBookId) {
1006
-			$addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1007
-		}
1008
-
1009
-		if ($addressBookOr->count() === 0) {
1010
-			return [];
1011
-		}
1012
-
1013
-		$propertyOr = $query2->expr()->orX();
1014
-		foreach ($searchProperties as $property) {
1015
-			if ($escapePattern) {
1016
-				if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
1017
-					// There can be no spaces in emails
1018
-					continue;
1019
-				}
1020
-
1021
-				if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
1022
-					// There can be no chars in cloud ids which are not valid for user ids plus :/
1023
-					// worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
1024
-					continue;
1025
-				}
1026
-			}
1027
-
1028
-			$propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
1029
-		}
1030
-
1031
-		if ($propertyOr->count() === 0) {
1032
-			return [];
1033
-		}
1034
-
1035
-		$query2->selectDistinct('cp.cardid')
1036
-			->from($this->dbCardsPropertiesTable, 'cp')
1037
-			->andWhere($addressBookOr)
1038
-			->andWhere($propertyOr);
1039
-
1040
-		// No need for like when the pattern is empty
1041
-		if ('' !== $pattern) {
1042
-			if (!$escapePattern) {
1043
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1044
-			} else {
1045
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1046
-			}
1047
-		}
1048
-
1049
-		if (isset($options['limit'])) {
1050
-			$query2->setMaxResults($options['limit']);
1051
-		}
1052
-		if (isset($options['offset'])) {
1053
-			$query2->setFirstResult($options['offset']);
1054
-		}
1055
-
1056
-		$result = $query2->execute();
1057
-		$matches = $result->fetchAll();
1058
-		$result->closeCursor();
1059
-		$matches = array_map(function ($match) {
1060
-			return (int)$match['cardid'];
1061
-		}, $matches);
1062
-
1063
-		$query = $this->db->getQueryBuilder();
1064
-		$query->select('c.addressbookid', 'c.carddata', 'c.uri')
1065
-			->from($this->dbCardsTable, 'c')
1066
-			->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1067
-
1068
-		$result = $query->execute();
1069
-		$cards = $result->fetchAll();
1070
-
1071
-		$result->closeCursor();
1072
-
1073
-		return array_map(function ($array) {
1074
-			$array['addressbookid'] = (int) $array['addressbookid'];
1075
-			$modified = false;
1076
-			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
1077
-			if ($modified) {
1078
-				$array['size'] = strlen($array['carddata']);
1079
-			}
1080
-			return $array;
1081
-		}, $cards);
1082
-	}
1083
-
1084
-	/**
1085
-	 * @param int $bookId
1086
-	 * @param string $name
1087
-	 * @return array
1088
-	 */
1089
-	public function collectCardProperties($bookId, $name) {
1090
-		$query = $this->db->getQueryBuilder();
1091
-		$result = $query->selectDistinct('value')
1092
-			->from($this->dbCardsPropertiesTable)
1093
-			->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1094
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1095
-			->execute();
1096
-
1097
-		$all = $result->fetchAll(PDO::FETCH_COLUMN);
1098
-		$result->closeCursor();
1099
-
1100
-		return $all;
1101
-	}
1102
-
1103
-	/**
1104
-	 * get URI from a given contact
1105
-	 *
1106
-	 * @param int $id
1107
-	 * @return string
1108
-	 */
1109
-	public function getCardUri($id) {
1110
-		$query = $this->db->getQueryBuilder();
1111
-		$query->select('uri')->from($this->dbCardsTable)
1112
-			->where($query->expr()->eq('id', $query->createParameter('id')))
1113
-			->setParameter('id', $id);
1114
-
1115
-		$result = $query->execute();
1116
-		$uri = $result->fetch();
1117
-		$result->closeCursor();
1118
-
1119
-		if (!isset($uri['uri'])) {
1120
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1121
-		}
1122
-
1123
-		return $uri['uri'];
1124
-	}
1125
-
1126
-	/**
1127
-	 * return contact with the given URI
1128
-	 *
1129
-	 * @param int $addressBookId
1130
-	 * @param string $uri
1131
-	 * @returns array
1132
-	 */
1133
-	public function getContact($addressBookId, $uri) {
1134
-		$result = [];
1135
-		$query = $this->db->getQueryBuilder();
1136
-		$query->select('*')->from($this->dbCardsTable)
1137
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1138
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1139
-		$queryResult = $query->execute();
1140
-		$contact = $queryResult->fetch();
1141
-		$queryResult->closeCursor();
1142
-
1143
-		if (is_array($contact)) {
1144
-			$modified = false;
1145
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1146
-			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1147
-			if ($modified) {
1148
-				$contact['size'] = strlen($contact['carddata']);
1149
-			}
1150
-
1151
-			$result = $contact;
1152
-		}
1153
-
1154
-		return $result;
1155
-	}
1156
-
1157
-	/**
1158
-	 * Returns the list of people whom this address book is shared with.
1159
-	 *
1160
-	 * Every element in this array should have the following properties:
1161
-	 *   * href - Often a mailto: address
1162
-	 *   * commonName - Optional, for example a first + last name
1163
-	 *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1164
-	 *   * readOnly - boolean
1165
-	 *   * summary - Optional, a description for the share
1166
-	 *
1167
-	 * @return array
1168
-	 */
1169
-	public function getShares($addressBookId) {
1170
-		return $this->sharingBackend->getShares($addressBookId);
1171
-	}
1172
-
1173
-	/**
1174
-	 * update properties table
1175
-	 *
1176
-	 * @param int $addressBookId
1177
-	 * @param string $cardUri
1178
-	 * @param string $vCardSerialized
1179
-	 */
1180
-	protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1181
-		$cardId = $this->getCardId($addressBookId, $cardUri);
1182
-		$vCard = $this->readCard($vCardSerialized);
1183
-
1184
-		$this->purgeProperties($addressBookId, $cardId);
1185
-
1186
-		$query = $this->db->getQueryBuilder();
1187
-		$query->insert($this->dbCardsPropertiesTable)
1188
-			->values(
1189
-				[
1190
-					'addressbookid' => $query->createNamedParameter($addressBookId),
1191
-					'cardid' => $query->createNamedParameter($cardId),
1192
-					'name' => $query->createParameter('name'),
1193
-					'value' => $query->createParameter('value'),
1194
-					'preferred' => $query->createParameter('preferred')
1195
-				]
1196
-			);
1197
-
1198
-		foreach ($vCard->children() as $property) {
1199
-			if (!in_array($property->name, self::$indexProperties)) {
1200
-				continue;
1201
-			}
1202
-			$preferred = 0;
1203
-			foreach ($property->parameters as $parameter) {
1204
-				if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1205
-					$preferred = 1;
1206
-					break;
1207
-				}
1208
-			}
1209
-			$query->setParameter('name', $property->name);
1210
-			$query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1211
-			$query->setParameter('preferred', $preferred);
1212
-			$query->execute();
1213
-		}
1214
-	}
1215
-
1216
-	/**
1217
-	 * read vCard data into a vCard object
1218
-	 *
1219
-	 * @param string $cardData
1220
-	 * @return VCard
1221
-	 */
1222
-	protected function readCard($cardData) {
1223
-		return Reader::read($cardData);
1224
-	}
1225
-
1226
-	/**
1227
-	 * delete all properties from a given card
1228
-	 *
1229
-	 * @param int $addressBookId
1230
-	 * @param int $cardId
1231
-	 */
1232
-	protected function purgeProperties($addressBookId, $cardId) {
1233
-		$query = $this->db->getQueryBuilder();
1234
-		$query->delete($this->dbCardsPropertiesTable)
1235
-			->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1236
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1237
-		$query->execute();
1238
-	}
1239
-
1240
-	/**
1241
-	 * get ID from a given contact
1242
-	 *
1243
-	 * @param int $addressBookId
1244
-	 * @param string $uri
1245
-	 * @return int
1246
-	 */
1247
-	protected function getCardId($addressBookId, $uri) {
1248
-		$query = $this->db->getQueryBuilder();
1249
-		$query->select('id')->from($this->dbCardsTable)
1250
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1251
-			->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1252
-
1253
-		$result = $query->execute();
1254
-		$cardIds = $result->fetch();
1255
-		$result->closeCursor();
1256
-
1257
-		if (!isset($cardIds['id'])) {
1258
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1259
-		}
1260
-
1261
-		return (int)$cardIds['id'];
1262
-	}
1263
-
1264
-	/**
1265
-	 * For shared address books the sharee is set in the ACL of the address book
1266
-	 *
1267
-	 * @param $addressBookId
1268
-	 * @param $acl
1269
-	 * @return array
1270
-	 */
1271
-	public function applyShareAcl($addressBookId, $acl) {
1272
-		return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1273
-	}
1274
-
1275
-	private function convertPrincipal($principalUri, $toV2) {
1276
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1277
-			list(, $name) = \Sabre\Uri\split($principalUri);
1278
-			if ($toV2 === true) {
1279
-				return "principals/users/$name";
1280
-			}
1281
-			return "principals/$name";
1282
-		}
1283
-		return $principalUri;
1284
-	}
1285
-
1286
-	private function addOwnerPrincipal(&$addressbookInfo) {
1287
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1288
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1289
-		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1290
-			$uri = $addressbookInfo[$ownerPrincipalKey];
1291
-		} else {
1292
-			$uri = $addressbookInfo['principaluri'];
1293
-		}
1294
-
1295
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1296
-		if (isset($principalInformation['{DAV:}displayname'])) {
1297
-			$addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1298
-		}
1299
-	}
1300
-
1301
-	/**
1302
-	 * Extract UID from vcard
1303
-	 *
1304
-	 * @param string $cardData the vcard raw data
1305
-	 * @return string the uid
1306
-	 * @throws BadRequest if no UID is available
1307
-	 */
1308
-	private function getUID($cardData) {
1309
-		if ($cardData != '') {
1310
-			$vCard = Reader::read($cardData);
1311
-			if ($vCard->UID) {
1312
-				$uid = $vCard->UID->getValue();
1313
-				return $uid;
1314
-			}
1315
-			// should already be handled, but just in case
1316
-			throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1317
-		}
1318
-		// should already be handled, but just in case
1319
-		throw new BadRequest('vCard can not be empty');
1320
-	}
59
+    public const PERSONAL_ADDRESSBOOK_URI = 'contacts';
60
+    public const PERSONAL_ADDRESSBOOK_NAME = 'Contacts';
61
+
62
+    /** @var Principal */
63
+    private $principalBackend;
64
+
65
+    /** @var string */
66
+    private $dbCardsTable = 'cards';
67
+
68
+    /** @var string */
69
+    private $dbCardsPropertiesTable = 'cards_properties';
70
+
71
+    /** @var IDBConnection */
72
+    private $db;
73
+
74
+    /** @var Backend */
75
+    private $sharingBackend;
76
+
77
+    /** @var array properties to index */
78
+    public static $indexProperties = [
79
+        'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME',
80
+        'ORG', 'CATEGORIES', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL', 'GEO', 'CLOUD'];
81
+
82
+    /**
83
+     * @var string[] Map of uid => display name
84
+     */
85
+    protected $userDisplayNames;
86
+
87
+    /** @var IUserManager */
88
+    private $userManager;
89
+
90
+    /** @var EventDispatcherInterface */
91
+    private $dispatcher;
92
+
93
+    private $etagCache = [];
94
+
95
+    /**
96
+     * CardDavBackend constructor.
97
+     *
98
+     * @param IDBConnection $db
99
+     * @param Principal $principalBackend
100
+     * @param IUserManager $userManager
101
+     * @param IGroupManager $groupManager
102
+     * @param EventDispatcherInterface $dispatcher
103
+     */
104
+    public function __construct(IDBConnection $db,
105
+                                Principal $principalBackend,
106
+                                IUserManager $userManager,
107
+                                IGroupManager $groupManager,
108
+                                EventDispatcherInterface $dispatcher) {
109
+        $this->db = $db;
110
+        $this->principalBackend = $principalBackend;
111
+        $this->userManager = $userManager;
112
+        $this->dispatcher = $dispatcher;
113
+        $this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'addressbook');
114
+    }
115
+
116
+    /**
117
+     * Return the number of address books for a principal
118
+     *
119
+     * @param $principalUri
120
+     * @return int
121
+     */
122
+    public function getAddressBooksForUserCount($principalUri) {
123
+        $principalUri = $this->convertPrincipal($principalUri, true);
124
+        $query = $this->db->getQueryBuilder();
125
+        $query->select($query->func()->count('*'))
126
+            ->from('addressbooks')
127
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
128
+
129
+        return (int)$query->execute()->fetchColumn();
130
+    }
131
+
132
+    /**
133
+     * Returns the list of address books for a specific user.
134
+     *
135
+     * Every addressbook should have the following properties:
136
+     *   id - an arbitrary unique id
137
+     *   uri - the 'basename' part of the url
138
+     *   principaluri - Same as the passed parameter
139
+     *
140
+     * Any additional clark-notation property may be passed besides this. Some
141
+     * common ones are :
142
+     *   {DAV:}displayname
143
+     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
144
+     *   {http://calendarserver.org/ns/}getctag
145
+     *
146
+     * @param string $principalUri
147
+     * @return array
148
+     */
149
+    public function getAddressBooksForUser($principalUri) {
150
+        $principalUriOriginal = $principalUri;
151
+        $principalUri = $this->convertPrincipal($principalUri, true);
152
+        $query = $this->db->getQueryBuilder();
153
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
154
+            ->from('addressbooks')
155
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
156
+
157
+        $addressBooks = [];
158
+
159
+        $result = $query->execute();
160
+        while ($row = $result->fetch()) {
161
+            $addressBooks[$row['id']] = [
162
+                'id' => $row['id'],
163
+                'uri' => $row['uri'],
164
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
165
+                '{DAV:}displayname' => $row['displayname'],
166
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
167
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
168
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
169
+            ];
170
+
171
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
172
+        }
173
+        $result->closeCursor();
174
+
175
+        // query for shared addressbooks
176
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
177
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
178
+
179
+        $principals = array_map(function ($principal) {
180
+            return urldecode($principal);
181
+        }, $principals);
182
+        $principals[] = $principalUri;
183
+
184
+        $query = $this->db->getQueryBuilder();
185
+        $result = $query->select(['a.id', 'a.uri', 'a.displayname', 'a.principaluri', 'a.description', 'a.synctoken', 's.access'])
186
+            ->from('dav_shares', 's')
187
+            ->join('s', 'addressbooks', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
188
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
189
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
190
+            ->setParameter('type', 'addressbook')
191
+            ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
192
+            ->execute();
193
+
194
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
195
+        while ($row = $result->fetch()) {
196
+            if ($row['principaluri'] === $principalUri) {
197
+                continue;
198
+            }
199
+
200
+            $readOnly = (int)$row['access'] === Backend::ACCESS_READ;
201
+            if (isset($addressBooks[$row['id']])) {
202
+                if ($readOnly) {
203
+                    // New share can not have more permissions then the old one.
204
+                    continue;
205
+                }
206
+                if (isset($addressBooks[$row['id']][$readOnlyPropertyName]) &&
207
+                    $addressBooks[$row['id']][$readOnlyPropertyName] === 0) {
208
+                    // Old share is already read-write, no more permissions can be gained
209
+                    continue;
210
+                }
211
+            }
212
+
213
+            list(, $name) = \Sabre\Uri\split($row['principaluri']);
214
+            $uri = $row['uri'] . '_shared_by_' . $name;
215
+            $displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
216
+
217
+            $addressBooks[$row['id']] = [
218
+                'id' => $row['id'],
219
+                'uri' => $uri,
220
+                'principaluri' => $principalUriOriginal,
221
+                '{DAV:}displayname' => $displayName,
222
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
223
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
224
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
225
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
226
+                $readOnlyPropertyName => $readOnly,
227
+            ];
228
+
229
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
230
+        }
231
+        $result->closeCursor();
232
+
233
+        return array_values($addressBooks);
234
+    }
235
+
236
+    public function getUsersOwnAddressBooks($principalUri) {
237
+        $principalUri = $this->convertPrincipal($principalUri, true);
238
+        $query = $this->db->getQueryBuilder();
239
+        $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
240
+            ->from('addressbooks')
241
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
242
+
243
+        $addressBooks = [];
244
+
245
+        $result = $query->execute();
246
+        while ($row = $result->fetch()) {
247
+            $addressBooks[$row['id']] = [
248
+                'id' => $row['id'],
249
+                'uri' => $row['uri'],
250
+                'principaluri' => $this->convertPrincipal($row['principaluri'], false),
251
+                '{DAV:}displayname' => $row['displayname'],
252
+                '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
253
+                '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
254
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
255
+            ];
256
+
257
+            $this->addOwnerPrincipal($addressBooks[$row['id']]);
258
+        }
259
+        $result->closeCursor();
260
+
261
+        return array_values($addressBooks);
262
+    }
263
+
264
+    private function getUserDisplayName($uid) {
265
+        if (!isset($this->userDisplayNames[$uid])) {
266
+            $user = $this->userManager->get($uid);
267
+
268
+            if ($user instanceof IUser) {
269
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
270
+            } else {
271
+                $this->userDisplayNames[$uid] = $uid;
272
+            }
273
+        }
274
+
275
+        return $this->userDisplayNames[$uid];
276
+    }
277
+
278
+    /**
279
+     * @param int $addressBookId
280
+     */
281
+    public function getAddressBookById($addressBookId) {
282
+        $query = $this->db->getQueryBuilder();
283
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
284
+            ->from('addressbooks')
285
+            ->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
286
+            ->execute();
287
+
288
+        $row = $result->fetch();
289
+        $result->closeCursor();
290
+        if ($row === false) {
291
+            return null;
292
+        }
293
+
294
+        $addressBook = [
295
+            'id' => $row['id'],
296
+            'uri' => $row['uri'],
297
+            'principaluri' => $row['principaluri'],
298
+            '{DAV:}displayname' => $row['displayname'],
299
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
300
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
301
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
302
+        ];
303
+
304
+        $this->addOwnerPrincipal($addressBook);
305
+
306
+        return $addressBook;
307
+    }
308
+
309
+    /**
310
+     * @param $addressBookUri
311
+     * @return array|null
312
+     */
313
+    public function getAddressBooksByUri($principal, $addressBookUri) {
314
+        $query = $this->db->getQueryBuilder();
315
+        $result = $query->select(['id', 'uri', 'displayname', 'principaluri', 'description', 'synctoken'])
316
+            ->from('addressbooks')
317
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($addressBookUri)))
318
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
319
+            ->setMaxResults(1)
320
+            ->execute();
321
+
322
+        $row = $result->fetch();
323
+        $result->closeCursor();
324
+        if ($row === false) {
325
+            return null;
326
+        }
327
+
328
+        $addressBook = [
329
+            'id' => $row['id'],
330
+            'uri' => $row['uri'],
331
+            'principaluri' => $row['principaluri'],
332
+            '{DAV:}displayname' => $row['displayname'],
333
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
334
+            '{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
335
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
336
+        ];
337
+
338
+        $this->addOwnerPrincipal($addressBook);
339
+
340
+        return $addressBook;
341
+    }
342
+
343
+    /**
344
+     * Updates properties for an address book.
345
+     *
346
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
347
+     * To do the actual updates, you must tell this object which properties
348
+     * you're going to process with the handle() method.
349
+     *
350
+     * Calling the handle method is like telling the PropPatch object "I
351
+     * promise I can handle updating this property".
352
+     *
353
+     * Read the PropPatch documentation for more info and examples.
354
+     *
355
+     * @param string $addressBookId
356
+     * @param \Sabre\DAV\PropPatch $propPatch
357
+     * @return void
358
+     */
359
+    public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
360
+        $supportedProperties = [
361
+            '{DAV:}displayname',
362
+            '{' . Plugin::NS_CARDDAV . '}addressbook-description',
363
+        ];
364
+
365
+        /**
366
+         * @suppress SqlInjectionChecker
367
+         */
368
+        $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
369
+            $updates = [];
370
+            foreach ($mutations as $property => $newValue) {
371
+                switch ($property) {
372
+                    case '{DAV:}displayname':
373
+                        $updates['displayname'] = $newValue;
374
+                        break;
375
+                    case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
376
+                        $updates['description'] = $newValue;
377
+                        break;
378
+                }
379
+            }
380
+            $query = $this->db->getQueryBuilder();
381
+            $query->update('addressbooks');
382
+
383
+            foreach ($updates as $key => $value) {
384
+                $query->set($key, $query->createNamedParameter($value));
385
+            }
386
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($addressBookId)))
387
+                ->execute();
388
+
389
+            $this->addChange($addressBookId, "", 2);
390
+
391
+            return true;
392
+        });
393
+    }
394
+
395
+    /**
396
+     * Creates a new address book
397
+     *
398
+     * @param string $principalUri
399
+     * @param string $url Just the 'basename' of the url.
400
+     * @param array $properties
401
+     * @return int
402
+     * @throws BadRequest
403
+     */
404
+    public function createAddressBook($principalUri, $url, array $properties) {
405
+        $values = [
406
+            'displayname' => null,
407
+            'description' => null,
408
+            'principaluri' => $principalUri,
409
+            'uri' => $url,
410
+            'synctoken' => 1
411
+        ];
412
+
413
+        foreach ($properties as $property => $newValue) {
414
+            switch ($property) {
415
+                case '{DAV:}displayname':
416
+                    $values['displayname'] = $newValue;
417
+                    break;
418
+                case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
419
+                    $values['description'] = $newValue;
420
+                    break;
421
+                default:
422
+                    throw new BadRequest('Unknown property: ' . $property);
423
+            }
424
+        }
425
+
426
+        // Fallback to make sure the displayname is set. Some clients may refuse
427
+        // to work with addressbooks not having a displayname.
428
+        if (is_null($values['displayname'])) {
429
+            $values['displayname'] = $url;
430
+        }
431
+
432
+        $query = $this->db->getQueryBuilder();
433
+        $query->insert('addressbooks')
434
+            ->values([
435
+                'uri' => $query->createParameter('uri'),
436
+                'displayname' => $query->createParameter('displayname'),
437
+                'description' => $query->createParameter('description'),
438
+                'principaluri' => $query->createParameter('principaluri'),
439
+                'synctoken' => $query->createParameter('synctoken'),
440
+            ])
441
+            ->setParameters($values)
442
+            ->execute();
443
+
444
+        return $query->getLastInsertId();
445
+    }
446
+
447
+    /**
448
+     * Deletes an entire addressbook and all its contents
449
+     *
450
+     * @param mixed $addressBookId
451
+     * @return void
452
+     */
453
+    public function deleteAddressBook($addressBookId) {
454
+        $query = $this->db->getQueryBuilder();
455
+        $query->delete($this->dbCardsTable)
456
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
457
+            ->setParameter('addressbookid', $addressBookId)
458
+            ->execute();
459
+
460
+        $query->delete('addressbookchanges')
461
+            ->where($query->expr()->eq('addressbookid', $query->createParameter('addressbookid')))
462
+            ->setParameter('addressbookid', $addressBookId)
463
+            ->execute();
464
+
465
+        $query->delete('addressbooks')
466
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
467
+            ->setParameter('id', $addressBookId)
468
+            ->execute();
469
+
470
+        $this->sharingBackend->deleteAllShares($addressBookId);
471
+
472
+        $query->delete($this->dbCardsPropertiesTable)
473
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
474
+            ->execute();
475
+    }
476
+
477
+    /**
478
+     * Returns all cards for a specific addressbook id.
479
+     *
480
+     * This method should return the following properties for each card:
481
+     *   * carddata - raw vcard data
482
+     *   * uri - Some unique url
483
+     *   * lastmodified - A unix timestamp
484
+     *
485
+     * It's recommended to also return the following properties:
486
+     *   * etag - A unique etag. This must change every time the card changes.
487
+     *   * size - The size of the card in bytes.
488
+     *
489
+     * If these last two properties are provided, less time will be spent
490
+     * calculating them. If they are specified, you can also ommit carddata.
491
+     * This may speed up certain requests, especially with large cards.
492
+     *
493
+     * @param mixed $addressBookId
494
+     * @return array
495
+     */
496
+    public function getCards($addressBookId) {
497
+        $query = $this->db->getQueryBuilder();
498
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
499
+            ->from($this->dbCardsTable)
500
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
501
+
502
+        $cards = [];
503
+
504
+        $result = $query->execute();
505
+        while ($row = $result->fetch()) {
506
+            $row['etag'] = '"' . $row['etag'] . '"';
507
+
508
+            $modified = false;
509
+            $row['carddata'] = $this->readBlob($row['carddata'], $modified);
510
+            if ($modified) {
511
+                $row['size'] = strlen($row['carddata']);
512
+            }
513
+
514
+            $cards[] = $row;
515
+        }
516
+        $result->closeCursor();
517
+
518
+        return $cards;
519
+    }
520
+
521
+    /**
522
+     * Returns a specific card.
523
+     *
524
+     * The same set of properties must be returned as with getCards. The only
525
+     * exception is that 'carddata' is absolutely required.
526
+     *
527
+     * If the card does not exist, you must return false.
528
+     *
529
+     * @param mixed $addressBookId
530
+     * @param string $cardUri
531
+     * @return array
532
+     */
533
+    public function getCard($addressBookId, $cardUri) {
534
+        $query = $this->db->getQueryBuilder();
535
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
536
+            ->from($this->dbCardsTable)
537
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
538
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
539
+            ->setMaxResults(1);
540
+
541
+        $result = $query->execute();
542
+        $row = $result->fetch();
543
+        if (!$row) {
544
+            return false;
545
+        }
546
+        $row['etag'] = '"' . $row['etag'] . '"';
547
+
548
+        $modified = false;
549
+        $row['carddata'] = $this->readBlob($row['carddata'], $modified);
550
+        if ($modified) {
551
+            $row['size'] = strlen($row['carddata']);
552
+        }
553
+
554
+        return $row;
555
+    }
556
+
557
+    /**
558
+     * Returns a list of cards.
559
+     *
560
+     * This method should work identical to getCard, but instead return all the
561
+     * cards in the list as an array.
562
+     *
563
+     * If the backend supports this, it may allow for some speed-ups.
564
+     *
565
+     * @param mixed $addressBookId
566
+     * @param string[] $uris
567
+     * @return array
568
+     */
569
+    public function getMultipleCards($addressBookId, array $uris) {
570
+        if (empty($uris)) {
571
+            return [];
572
+        }
573
+
574
+        $chunks = array_chunk($uris, 100);
575
+        $cards = [];
576
+
577
+        $query = $this->db->getQueryBuilder();
578
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
579
+            ->from($this->dbCardsTable)
580
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
581
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
582
+
583
+        foreach ($chunks as $uris) {
584
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
585
+            $result = $query->execute();
586
+
587
+            while ($row = $result->fetch()) {
588
+                $row['etag'] = '"' . $row['etag'] . '"';
589
+
590
+                $modified = false;
591
+                $row['carddata'] = $this->readBlob($row['carddata'], $modified);
592
+                if ($modified) {
593
+                    $row['size'] = strlen($row['carddata']);
594
+                }
595
+
596
+                $cards[] = $row;
597
+            }
598
+            $result->closeCursor();
599
+        }
600
+        return $cards;
601
+    }
602
+
603
+    /**
604
+     * Creates a new card.
605
+     *
606
+     * The addressbook id will be passed as the first argument. This is the
607
+     * same id as it is returned from the getAddressBooksForUser method.
608
+     *
609
+     * The cardUri is a base uri, and doesn't include the full path. The
610
+     * cardData argument is the vcard body, and is passed as a string.
611
+     *
612
+     * It is possible to return an ETag from this method. This ETag is for the
613
+     * newly created resource, and must be enclosed with double quotes (that
614
+     * is, the string itself must contain the double quotes).
615
+     *
616
+     * You should only return the ETag if you store the carddata as-is. If a
617
+     * subsequent GET request on the same card does not have the same body,
618
+     * byte-by-byte and you did return an ETag here, clients tend to get
619
+     * confused.
620
+     *
621
+     * If you don't return an ETag, you can just return null.
622
+     *
623
+     * @param mixed $addressBookId
624
+     * @param string $cardUri
625
+     * @param string $cardData
626
+     * @return string
627
+     */
628
+    public function createCard($addressBookId, $cardUri, $cardData) {
629
+        $etag = md5($cardData);
630
+        $uid = $this->getUID($cardData);
631
+
632
+        $q = $this->db->getQueryBuilder();
633
+        $q->select('uid')
634
+            ->from($this->dbCardsTable)
635
+            ->where($q->expr()->eq('addressbookid', $q->createNamedParameter($addressBookId)))
636
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
637
+            ->setMaxResults(1);
638
+        $result = $q->execute();
639
+        $count = (bool)$result->fetchColumn();
640
+        $result->closeCursor();
641
+        if ($count) {
642
+            throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
643
+        }
644
+
645
+        $query = $this->db->getQueryBuilder();
646
+        $query->insert('cards')
647
+            ->values([
648
+                'carddata' => $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB),
649
+                'uri' => $query->createNamedParameter($cardUri),
650
+                'lastmodified' => $query->createNamedParameter(time()),
651
+                'addressbookid' => $query->createNamedParameter($addressBookId),
652
+                'size' => $query->createNamedParameter(strlen($cardData)),
653
+                'etag' => $query->createNamedParameter($etag),
654
+                'uid' => $query->createNamedParameter($uid),
655
+            ])
656
+            ->execute();
657
+
658
+        $etagCacheKey = "$addressBookId#$cardUri";
659
+        $this->etagCache[$etagCacheKey] = $etag;
660
+
661
+        $this->addChange($addressBookId, $cardUri, 1);
662
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
663
+
664
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::createCard',
665
+            new GenericEvent(null, [
666
+                'addressBookId' => $addressBookId,
667
+                'cardUri' => $cardUri,
668
+                'cardData' => $cardData]));
669
+
670
+        return '"' . $etag . '"';
671
+    }
672
+
673
+    /**
674
+     * Updates a card.
675
+     *
676
+     * The addressbook id will be passed as the first argument. This is the
677
+     * same id as it is returned from the getAddressBooksForUser method.
678
+     *
679
+     * The cardUri is a base uri, and doesn't include the full path. The
680
+     * cardData argument is the vcard body, and is passed as a string.
681
+     *
682
+     * It is possible to return an ETag from this method. This ETag should
683
+     * match that of the updated resource, and must be enclosed with double
684
+     * quotes (that is: the string itself must contain the actual quotes).
685
+     *
686
+     * You should only return the ETag if you store the carddata as-is. If a
687
+     * subsequent GET request on the same card does not have the same body,
688
+     * byte-by-byte and you did return an ETag here, clients tend to get
689
+     * confused.
690
+     *
691
+     * If you don't return an ETag, you can just return null.
692
+     *
693
+     * @param mixed $addressBookId
694
+     * @param string $cardUri
695
+     * @param string $cardData
696
+     * @return string
697
+     */
698
+    public function updateCard($addressBookId, $cardUri, $cardData) {
699
+        $uid = $this->getUID($cardData);
700
+        $etag = md5($cardData);
701
+        $query = $this->db->getQueryBuilder();
702
+
703
+        // check for recently stored etag and stop if it is the same
704
+        $etagCacheKey = "$addressBookId#$cardUri";
705
+        if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
706
+            return '"' . $etag . '"';
707
+        }
708
+
709
+        $query->update($this->dbCardsTable)
710
+            ->set('carddata', $query->createNamedParameter($cardData, IQueryBuilder::PARAM_LOB))
711
+            ->set('lastmodified', $query->createNamedParameter(time()))
712
+            ->set('size', $query->createNamedParameter(strlen($cardData)))
713
+            ->set('etag', $query->createNamedParameter($etag))
714
+            ->set('uid', $query->createNamedParameter($uid))
715
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
716
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
717
+            ->execute();
718
+
719
+        $this->etagCache[$etagCacheKey] = $etag;
720
+
721
+        $this->addChange($addressBookId, $cardUri, 2);
722
+        $this->updateProperties($addressBookId, $cardUri, $cardData);
723
+
724
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::updateCard',
725
+            new GenericEvent(null, [
726
+                'addressBookId' => $addressBookId,
727
+                'cardUri' => $cardUri,
728
+                'cardData' => $cardData]));
729
+
730
+        return '"' . $etag . '"';
731
+    }
732
+
733
+    /**
734
+     * Deletes a card
735
+     *
736
+     * @param mixed $addressBookId
737
+     * @param string $cardUri
738
+     * @return bool
739
+     */
740
+    public function deleteCard($addressBookId, $cardUri) {
741
+        try {
742
+            $cardId = $this->getCardId($addressBookId, $cardUri);
743
+        } catch (\InvalidArgumentException $e) {
744
+            $cardId = null;
745
+        }
746
+        $query = $this->db->getQueryBuilder();
747
+        $ret = $query->delete($this->dbCardsTable)
748
+            ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
749
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri)))
750
+            ->execute();
751
+
752
+        $this->addChange($addressBookId, $cardUri, 3);
753
+
754
+        $this->dispatcher->dispatch('\OCA\DAV\CardDAV\CardDavBackend::deleteCard',
755
+            new GenericEvent(null, [
756
+                'addressBookId' => $addressBookId,
757
+                'cardUri' => $cardUri]));
758
+
759
+        if ($ret === 1) {
760
+            if ($cardId !== null) {
761
+                $this->purgeProperties($addressBookId, $cardId);
762
+            }
763
+            return true;
764
+        }
765
+
766
+        return false;
767
+    }
768
+
769
+    /**
770
+     * The getChanges method returns all the changes that have happened, since
771
+     * the specified syncToken in the specified address book.
772
+     *
773
+     * This function should return an array, such as the following:
774
+     *
775
+     * [
776
+     *   'syncToken' => 'The current synctoken',
777
+     *   'added'   => [
778
+     *      'new.txt',
779
+     *   ],
780
+     *   'modified'   => [
781
+     *      'modified.txt',
782
+     *   ],
783
+     *   'deleted' => [
784
+     *      'foo.php.bak',
785
+     *      'old.txt'
786
+     *   ]
787
+     * ];
788
+     *
789
+     * The returned syncToken property should reflect the *current* syncToken
790
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
791
+     * property. This is needed here too, to ensure the operation is atomic.
792
+     *
793
+     * If the $syncToken argument is specified as null, this is an initial
794
+     * sync, and all members should be reported.
795
+     *
796
+     * The modified property is an array of nodenames that have changed since
797
+     * the last token.
798
+     *
799
+     * The deleted property is an array with nodenames, that have been deleted
800
+     * from collection.
801
+     *
802
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
803
+     * 1, you only have to report changes that happened only directly in
804
+     * immediate descendants. If it's 2, it should also include changes from
805
+     * the nodes below the child collections. (grandchildren)
806
+     *
807
+     * The $limit argument allows a client to specify how many results should
808
+     * be returned at most. If the limit is not specified, it should be treated
809
+     * as infinite.
810
+     *
811
+     * If the limit (infinite or not) is higher than you're willing to return,
812
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
813
+     *
814
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
815
+     * return null.
816
+     *
817
+     * The limit is 'suggestive'. You are free to ignore it.
818
+     *
819
+     * @param string $addressBookId
820
+     * @param string $syncToken
821
+     * @param int $syncLevel
822
+     * @param int $limit
823
+     * @return array
824
+     */
825
+    public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
826
+        // Current synctoken
827
+        $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*addressbooks` WHERE `id` = ?');
828
+        $stmt->execute([$addressBookId]);
829
+        $currentToken = $stmt->fetchColumn(0);
830
+
831
+        if (is_null($currentToken)) {
832
+            return null;
833
+        }
834
+
835
+        $result = [
836
+            'syncToken' => $currentToken,
837
+            'added' => [],
838
+            'modified' => [],
839
+            'deleted' => [],
840
+        ];
841
+
842
+        if ($syncToken) {
843
+            $query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
844
+            if ($limit > 0) {
845
+                $query .= " LIMIT " . (int)$limit;
846
+            }
847
+
848
+            // Fetching all changes
849
+            $stmt = $this->db->prepare($query);
850
+            $stmt->execute([$syncToken, $currentToken, $addressBookId]);
851
+
852
+            $changes = [];
853
+
854
+            // This loop ensures that any duplicates are overwritten, only the
855
+            // last change on a node is relevant.
856
+            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
857
+                $changes[$row['uri']] = $row['operation'];
858
+            }
859
+
860
+            foreach ($changes as $uri => $operation) {
861
+                switch ($operation) {
862
+                    case 1:
863
+                        $result['added'][] = $uri;
864
+                        break;
865
+                    case 2:
866
+                        $result['modified'][] = $uri;
867
+                        break;
868
+                    case 3:
869
+                        $result['deleted'][] = $uri;
870
+                        break;
871
+                }
872
+            }
873
+        } else {
874
+            // No synctoken supplied, this is the initial sync.
875
+            $query = "SELECT `uri` FROM `*PREFIX*cards` WHERE `addressbookid` = ?";
876
+            $stmt = $this->db->prepare($query);
877
+            $stmt->execute([$addressBookId]);
878
+
879
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
880
+        }
881
+        return $result;
882
+    }
883
+
884
+    /**
885
+     * Adds a change record to the addressbookchanges table.
886
+     *
887
+     * @param mixed $addressBookId
888
+     * @param string $objectUri
889
+     * @param int $operation 1 = add, 2 = modify, 3 = delete
890
+     * @return void
891
+     */
892
+    protected function addChange($addressBookId, $objectUri, $operation) {
893
+        $sql = 'INSERT INTO `*PREFIX*addressbookchanges`(`uri`, `synctoken`, `addressbookid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*addressbooks` WHERE `id` = ?';
894
+        $stmt = $this->db->prepare($sql);
895
+        $stmt->execute([
896
+            $objectUri,
897
+            $addressBookId,
898
+            $operation,
899
+            $addressBookId
900
+        ]);
901
+        $stmt = $this->db->prepare('UPDATE `*PREFIX*addressbooks` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
902
+        $stmt->execute([
903
+            $addressBookId
904
+        ]);
905
+    }
906
+
907
+    /**
908
+     * @param resource|string $cardData
909
+     * @param bool $modified
910
+     * @return string
911
+     */
912
+    private function readBlob($cardData, &$modified = false) {
913
+        if (is_resource($cardData)) {
914
+            $cardData = stream_get_contents($cardData);
915
+        }
916
+
917
+        $cardDataArray = explode("\r\n", $cardData);
918
+
919
+        $cardDataFiltered = [];
920
+        $removingPhoto = false;
921
+        foreach ($cardDataArray as $line) {
922
+            if (strpos($line, 'PHOTO:data:') === 0
923
+                && strpos($line, 'PHOTO:data:image/') !== 0) {
924
+                // Filter out PHOTO data of non-images
925
+                $removingPhoto = true;
926
+                $modified = true;
927
+                continue;
928
+            }
929
+
930
+            if ($removingPhoto) {
931
+                if (strpos($line, ' ') === 0) {
932
+                    continue;
933
+                }
934
+                // No leading space means this is a new property
935
+                $removingPhoto = false;
936
+            }
937
+
938
+            $cardDataFiltered[] = $line;
939
+        }
940
+
941
+        return implode("\r\n", $cardDataFiltered);
942
+    }
943
+
944
+    /**
945
+     * @param IShareable $shareable
946
+     * @param string[] $add
947
+     * @param string[] $remove
948
+     */
949
+    public function updateShares(IShareable $shareable, $add, $remove) {
950
+        $this->sharingBackend->updateShares($shareable, $add, $remove);
951
+    }
952
+
953
+    /**
954
+     * Search contacts in a specific address-book
955
+     *
956
+     * @param int $addressBookId
957
+     * @param string $pattern which should match within the $searchProperties
958
+     * @param array $searchProperties defines the properties within the query pattern should match
959
+     * @param array $options = array() to define the search behavior
960
+     *    - 'escape_like_param' - If set to false wildcards _ and % are not escaped, otherwise they are
961
+     *    - 'limit' - Set a numeric limit for the search results
962
+     *    - 'offset' - Set the offset for the limited search results
963
+     * @return array an array of contacts which are arrays of key-value-pairs
964
+     */
965
+    public function search($addressBookId, $pattern, $searchProperties, $options = []): array {
966
+        return $this->searchByAddressBookIds([$addressBookId], $pattern, $searchProperties, $options);
967
+    }
968
+
969
+    /**
970
+     * Search contacts in all address-books accessible by a user
971
+     *
972
+     * @param string $principalUri
973
+     * @param string $pattern
974
+     * @param array $searchProperties
975
+     * @param array $options
976
+     * @return array
977
+     */
978
+    public function searchPrincipalUri(string $principalUri,
979
+                                        string $pattern,
980
+                                        array $searchProperties,
981
+                                        array $options = []): array {
982
+        $addressBookIds = array_map(static function ($row):int {
983
+            return (int) $row['id'];
984
+        }, $this->getAddressBooksForUser($principalUri));
985
+
986
+        return $this->searchByAddressBookIds($addressBookIds, $pattern, $searchProperties, $options);
987
+    }
988
+
989
+    /**
990
+     * @param array $addressBookIds
991
+     * @param string $pattern
992
+     * @param array $searchProperties
993
+     * @param array $options
994
+     * @return array
995
+     */
996
+    private function searchByAddressBookIds(array $addressBookIds,
997
+                                            string $pattern,
998
+                                            array $searchProperties,
999
+                                            array $options = []): array {
1000
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1001
+
1002
+        $query2 = $this->db->getQueryBuilder();
1003
+
1004
+        $addressBookOr =  $query2->expr()->orX();
1005
+        foreach ($addressBookIds as $addressBookId) {
1006
+            $addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1007
+        }
1008
+
1009
+        if ($addressBookOr->count() === 0) {
1010
+            return [];
1011
+        }
1012
+
1013
+        $propertyOr = $query2->expr()->orX();
1014
+        foreach ($searchProperties as $property) {
1015
+            if ($escapePattern) {
1016
+                if ($property === 'EMAIL' && strpos($pattern, ' ') !== false) {
1017
+                    // There can be no spaces in emails
1018
+                    continue;
1019
+                }
1020
+
1021
+                if ($property === 'CLOUD' && preg_match('/[^a-zA-Z0-9 :_.@\/\-\']/', $pattern) === 1) {
1022
+                    // There can be no chars in cloud ids which are not valid for user ids plus :/
1023
+                    // worst case: CA61590A-BBBC-423E-84AF-E6DF01455A53@https://my.nxt/srv/
1024
+                    continue;
1025
+                }
1026
+            }
1027
+
1028
+            $propertyOr->add($query2->expr()->eq('cp.name', $query2->createNamedParameter($property)));
1029
+        }
1030
+
1031
+        if ($propertyOr->count() === 0) {
1032
+            return [];
1033
+        }
1034
+
1035
+        $query2->selectDistinct('cp.cardid')
1036
+            ->from($this->dbCardsPropertiesTable, 'cp')
1037
+            ->andWhere($addressBookOr)
1038
+            ->andWhere($propertyOr);
1039
+
1040
+        // No need for like when the pattern is empty
1041
+        if ('' !== $pattern) {
1042
+            if (!$escapePattern) {
1043
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1044
+            } else {
1045
+                $query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1046
+            }
1047
+        }
1048
+
1049
+        if (isset($options['limit'])) {
1050
+            $query2->setMaxResults($options['limit']);
1051
+        }
1052
+        if (isset($options['offset'])) {
1053
+            $query2->setFirstResult($options['offset']);
1054
+        }
1055
+
1056
+        $result = $query2->execute();
1057
+        $matches = $result->fetchAll();
1058
+        $result->closeCursor();
1059
+        $matches = array_map(function ($match) {
1060
+            return (int)$match['cardid'];
1061
+        }, $matches);
1062
+
1063
+        $query = $this->db->getQueryBuilder();
1064
+        $query->select('c.addressbookid', 'c.carddata', 'c.uri')
1065
+            ->from($this->dbCardsTable, 'c')
1066
+            ->where($query->expr()->in('c.id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1067
+
1068
+        $result = $query->execute();
1069
+        $cards = $result->fetchAll();
1070
+
1071
+        $result->closeCursor();
1072
+
1073
+        return array_map(function ($array) {
1074
+            $array['addressbookid'] = (int) $array['addressbookid'];
1075
+            $modified = false;
1076
+            $array['carddata'] = $this->readBlob($array['carddata'], $modified);
1077
+            if ($modified) {
1078
+                $array['size'] = strlen($array['carddata']);
1079
+            }
1080
+            return $array;
1081
+        }, $cards);
1082
+    }
1083
+
1084
+    /**
1085
+     * @param int $bookId
1086
+     * @param string $name
1087
+     * @return array
1088
+     */
1089
+    public function collectCardProperties($bookId, $name) {
1090
+        $query = $this->db->getQueryBuilder();
1091
+        $result = $query->selectDistinct('value')
1092
+            ->from($this->dbCardsPropertiesTable)
1093
+            ->where($query->expr()->eq('name', $query->createNamedParameter($name)))
1094
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($bookId)))
1095
+            ->execute();
1096
+
1097
+        $all = $result->fetchAll(PDO::FETCH_COLUMN);
1098
+        $result->closeCursor();
1099
+
1100
+        return $all;
1101
+    }
1102
+
1103
+    /**
1104
+     * get URI from a given contact
1105
+     *
1106
+     * @param int $id
1107
+     * @return string
1108
+     */
1109
+    public function getCardUri($id) {
1110
+        $query = $this->db->getQueryBuilder();
1111
+        $query->select('uri')->from($this->dbCardsTable)
1112
+            ->where($query->expr()->eq('id', $query->createParameter('id')))
1113
+            ->setParameter('id', $id);
1114
+
1115
+        $result = $query->execute();
1116
+        $uri = $result->fetch();
1117
+        $result->closeCursor();
1118
+
1119
+        if (!isset($uri['uri'])) {
1120
+            throw new \InvalidArgumentException('Card does not exists: ' . $id);
1121
+        }
1122
+
1123
+        return $uri['uri'];
1124
+    }
1125
+
1126
+    /**
1127
+     * return contact with the given URI
1128
+     *
1129
+     * @param int $addressBookId
1130
+     * @param string $uri
1131
+     * @returns array
1132
+     */
1133
+    public function getContact($addressBookId, $uri) {
1134
+        $result = [];
1135
+        $query = $this->db->getQueryBuilder();
1136
+        $query->select('*')->from($this->dbCardsTable)
1137
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1138
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1139
+        $queryResult = $query->execute();
1140
+        $contact = $queryResult->fetch();
1141
+        $queryResult->closeCursor();
1142
+
1143
+        if (is_array($contact)) {
1144
+            $modified = false;
1145
+            $contact['etag'] = '"' . $contact['etag'] . '"';
1146
+            $contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1147
+            if ($modified) {
1148
+                $contact['size'] = strlen($contact['carddata']);
1149
+            }
1150
+
1151
+            $result = $contact;
1152
+        }
1153
+
1154
+        return $result;
1155
+    }
1156
+
1157
+    /**
1158
+     * Returns the list of people whom this address book is shared with.
1159
+     *
1160
+     * Every element in this array should have the following properties:
1161
+     *   * href - Often a mailto: address
1162
+     *   * commonName - Optional, for example a first + last name
1163
+     *   * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants.
1164
+     *   * readOnly - boolean
1165
+     *   * summary - Optional, a description for the share
1166
+     *
1167
+     * @return array
1168
+     */
1169
+    public function getShares($addressBookId) {
1170
+        return $this->sharingBackend->getShares($addressBookId);
1171
+    }
1172
+
1173
+    /**
1174
+     * update properties table
1175
+     *
1176
+     * @param int $addressBookId
1177
+     * @param string $cardUri
1178
+     * @param string $vCardSerialized
1179
+     */
1180
+    protected function updateProperties($addressBookId, $cardUri, $vCardSerialized) {
1181
+        $cardId = $this->getCardId($addressBookId, $cardUri);
1182
+        $vCard = $this->readCard($vCardSerialized);
1183
+
1184
+        $this->purgeProperties($addressBookId, $cardId);
1185
+
1186
+        $query = $this->db->getQueryBuilder();
1187
+        $query->insert($this->dbCardsPropertiesTable)
1188
+            ->values(
1189
+                [
1190
+                    'addressbookid' => $query->createNamedParameter($addressBookId),
1191
+                    'cardid' => $query->createNamedParameter($cardId),
1192
+                    'name' => $query->createParameter('name'),
1193
+                    'value' => $query->createParameter('value'),
1194
+                    'preferred' => $query->createParameter('preferred')
1195
+                ]
1196
+            );
1197
+
1198
+        foreach ($vCard->children() as $property) {
1199
+            if (!in_array($property->name, self::$indexProperties)) {
1200
+                continue;
1201
+            }
1202
+            $preferred = 0;
1203
+            foreach ($property->parameters as $parameter) {
1204
+                if ($parameter->name === 'TYPE' && strtoupper($parameter->getValue()) === 'PREF') {
1205
+                    $preferred = 1;
1206
+                    break;
1207
+                }
1208
+            }
1209
+            $query->setParameter('name', $property->name);
1210
+            $query->setParameter('value', mb_substr($property->getValue(), 0, 254));
1211
+            $query->setParameter('preferred', $preferred);
1212
+            $query->execute();
1213
+        }
1214
+    }
1215
+
1216
+    /**
1217
+     * read vCard data into a vCard object
1218
+     *
1219
+     * @param string $cardData
1220
+     * @return VCard
1221
+     */
1222
+    protected function readCard($cardData) {
1223
+        return Reader::read($cardData);
1224
+    }
1225
+
1226
+    /**
1227
+     * delete all properties from a given card
1228
+     *
1229
+     * @param int $addressBookId
1230
+     * @param int $cardId
1231
+     */
1232
+    protected function purgeProperties($addressBookId, $cardId) {
1233
+        $query = $this->db->getQueryBuilder();
1234
+        $query->delete($this->dbCardsPropertiesTable)
1235
+            ->where($query->expr()->eq('cardid', $query->createNamedParameter($cardId)))
1236
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1237
+        $query->execute();
1238
+    }
1239
+
1240
+    /**
1241
+     * get ID from a given contact
1242
+     *
1243
+     * @param int $addressBookId
1244
+     * @param string $uri
1245
+     * @return int
1246
+     */
1247
+    protected function getCardId($addressBookId, $uri) {
1248
+        $query = $this->db->getQueryBuilder();
1249
+        $query->select('id')->from($this->dbCardsTable)
1250
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
1251
+            ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)));
1252
+
1253
+        $result = $query->execute();
1254
+        $cardIds = $result->fetch();
1255
+        $result->closeCursor();
1256
+
1257
+        if (!isset($cardIds['id'])) {
1258
+            throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1259
+        }
1260
+
1261
+        return (int)$cardIds['id'];
1262
+    }
1263
+
1264
+    /**
1265
+     * For shared address books the sharee is set in the ACL of the address book
1266
+     *
1267
+     * @param $addressBookId
1268
+     * @param $acl
1269
+     * @return array
1270
+     */
1271
+    public function applyShareAcl($addressBookId, $acl) {
1272
+        return $this->sharingBackend->applyShareAcl($addressBookId, $acl);
1273
+    }
1274
+
1275
+    private function convertPrincipal($principalUri, $toV2) {
1276
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1277
+            list(, $name) = \Sabre\Uri\split($principalUri);
1278
+            if ($toV2 === true) {
1279
+                return "principals/users/$name";
1280
+            }
1281
+            return "principals/$name";
1282
+        }
1283
+        return $principalUri;
1284
+    }
1285
+
1286
+    private function addOwnerPrincipal(&$addressbookInfo) {
1287
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1288
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1289
+        if (isset($addressbookInfo[$ownerPrincipalKey])) {
1290
+            $uri = $addressbookInfo[$ownerPrincipalKey];
1291
+        } else {
1292
+            $uri = $addressbookInfo['principaluri'];
1293
+        }
1294
+
1295
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
1296
+        if (isset($principalInformation['{DAV:}displayname'])) {
1297
+            $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
1298
+        }
1299
+    }
1300
+
1301
+    /**
1302
+     * Extract UID from vcard
1303
+     *
1304
+     * @param string $cardData the vcard raw data
1305
+     * @return string the uid
1306
+     * @throws BadRequest if no UID is available
1307
+     */
1308
+    private function getUID($cardData) {
1309
+        if ($cardData != '') {
1310
+            $vCard = Reader::read($cardData);
1311
+            if ($vCard->UID) {
1312
+                $uid = $vCard->UID->getValue();
1313
+                return $uid;
1314
+            }
1315
+            // should already be handled, but just in case
1316
+            throw new BadRequest('vCards on CardDAV servers MUST have a UID property');
1317
+        }
1318
+        // should already be handled, but just in case
1319
+        throw new BadRequest('vCard can not be empty');
1320
+    }
1321 1321
 }
Please login to merge, or discard this patch.
Spacing   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -126,7 +126,7 @@  discard block
 block discarded – undo
126 126
 			->from('addressbooks')
127 127
 			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
128 128
 
129
-		return (int)$query->execute()->fetchColumn();
129
+		return (int) $query->execute()->fetchColumn();
130 130
 	}
131 131
 
132 132
 	/**
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
 				'uri' => $row['uri'],
164 164
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
165 165
 				'{DAV:}displayname' => $row['displayname'],
166
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
166
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
167 167
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
168 168
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
169 169
 			];
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
 		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
177 177
 		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
178 178
 
179
-		$principals = array_map(function ($principal) {
179
+		$principals = array_map(function($principal) {
180 180
 			return urldecode($principal);
181 181
 		}, $principals);
182 182
 		$principals[] = $principalUri;
@@ -191,13 +191,13 @@  discard block
 block discarded – undo
191 191
 			->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY)
192 192
 			->execute();
193 193
 
194
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
194
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
195 195
 		while ($row = $result->fetch()) {
196 196
 			if ($row['principaluri'] === $principalUri) {
197 197
 				continue;
198 198
 			}
199 199
 
200
-			$readOnly = (int)$row['access'] === Backend::ACCESS_READ;
200
+			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
201 201
 			if (isset($addressBooks[$row['id']])) {
202 202
 				if ($readOnly) {
203 203
 					// New share can not have more permissions then the old one.
@@ -211,18 +211,18 @@  discard block
 block discarded – undo
211 211
 			}
212 212
 
213 213
 			list(, $name) = \Sabre\Uri\split($row['principaluri']);
214
-			$uri = $row['uri'] . '_shared_by_' . $name;
215
-			$displayName = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
214
+			$uri = $row['uri'].'_shared_by_'.$name;
215
+			$displayName = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
216 216
 
217 217
 			$addressBooks[$row['id']] = [
218 218
 				'id' => $row['id'],
219 219
 				'uri' => $uri,
220 220
 				'principaluri' => $principalUriOriginal,
221 221
 				'{DAV:}displayname' => $displayName,
222
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
222
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
223 223
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
224 224
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
225
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'],
225
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $row['principaluri'],
226 226
 				$readOnlyPropertyName => $readOnly,
227 227
 			];
228 228
 
@@ -249,7 +249,7 @@  discard block
 block discarded – undo
249 249
 				'uri' => $row['uri'],
250 250
 				'principaluri' => $this->convertPrincipal($row['principaluri'], false),
251 251
 				'{DAV:}displayname' => $row['displayname'],
252
-				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
252
+				'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
253 253
 				'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
254 254
 				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
255 255
 			];
@@ -296,7 +296,7 @@  discard block
 block discarded – undo
296 296
 			'uri' => $row['uri'],
297 297
 			'principaluri' => $row['principaluri'],
298 298
 			'{DAV:}displayname' => $row['displayname'],
299
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
299
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
300 300
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
301 301
 			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
302 302
 		];
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
 			'uri' => $row['uri'],
331 331
 			'principaluri' => $row['principaluri'],
332 332
 			'{DAV:}displayname' => $row['displayname'],
333
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
333
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'],
334 334
 			'{http://calendarserver.org/ns/}getctag' => $row['synctoken'],
335 335
 			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0',
336 336
 		];
@@ -359,20 +359,20 @@  discard block
 block discarded – undo
359 359
 	public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {
360 360
 		$supportedProperties = [
361 361
 			'{DAV:}displayname',
362
-			'{' . Plugin::NS_CARDDAV . '}addressbook-description',
362
+			'{'.Plugin::NS_CARDDAV.'}addressbook-description',
363 363
 		];
364 364
 
365 365
 		/**
366 366
 		 * @suppress SqlInjectionChecker
367 367
 		 */
368
-		$propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) {
368
+		$propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {
369 369
 			$updates = [];
370 370
 			foreach ($mutations as $property => $newValue) {
371 371
 				switch ($property) {
372 372
 					case '{DAV:}displayname':
373 373
 						$updates['displayname'] = $newValue;
374 374
 						break;
375
-					case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
375
+					case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
376 376
 						$updates['description'] = $newValue;
377 377
 						break;
378 378
 				}
@@ -415,11 +415,11 @@  discard block
 block discarded – undo
415 415
 				case '{DAV:}displayname':
416 416
 					$values['displayname'] = $newValue;
417 417
 					break;
418
-				case '{' . Plugin::NS_CARDDAV . '}addressbook-description':
418
+				case '{'.Plugin::NS_CARDDAV.'}addressbook-description':
419 419
 					$values['description'] = $newValue;
420 420
 					break;
421 421
 				default:
422
-					throw new BadRequest('Unknown property: ' . $property);
422
+					throw new BadRequest('Unknown property: '.$property);
423 423
 			}
424 424
 		}
425 425
 
@@ -503,7 +503,7 @@  discard block
 block discarded – undo
503 503
 
504 504
 		$result = $query->execute();
505 505
 		while ($row = $result->fetch()) {
506
-			$row['etag'] = '"' . $row['etag'] . '"';
506
+			$row['etag'] = '"'.$row['etag'].'"';
507 507
 
508 508
 			$modified = false;
509 509
 			$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -543,7 +543,7 @@  discard block
 block discarded – undo
543 543
 		if (!$row) {
544 544
 			return false;
545 545
 		}
546
-		$row['etag'] = '"' . $row['etag'] . '"';
546
+		$row['etag'] = '"'.$row['etag'].'"';
547 547
 
548 548
 		$modified = false;
549 549
 		$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -585,7 +585,7 @@  discard block
 block discarded – undo
585 585
 			$result = $query->execute();
586 586
 
587 587
 			while ($row = $result->fetch()) {
588
-				$row['etag'] = '"' . $row['etag'] . '"';
588
+				$row['etag'] = '"'.$row['etag'].'"';
589 589
 
590 590
 				$modified = false;
591 591
 				$row['carddata'] = $this->readBlob($row['carddata'], $modified);
@@ -636,7 +636,7 @@  discard block
 block discarded – undo
636 636
 			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($uid)))
637 637
 			->setMaxResults(1);
638 638
 		$result = $q->execute();
639
-		$count = (bool)$result->fetchColumn();
639
+		$count = (bool) $result->fetchColumn();
640 640
 		$result->closeCursor();
641 641
 		if ($count) {
642 642
 			throw new \Sabre\DAV\Exception\BadRequest('VCard object with uid already exists in this addressbook collection.');
@@ -667,7 +667,7 @@  discard block
 block discarded – undo
667 667
 				'cardUri' => $cardUri,
668 668
 				'cardData' => $cardData]));
669 669
 
670
-		return '"' . $etag . '"';
670
+		return '"'.$etag.'"';
671 671
 	}
672 672
 
673 673
 	/**
@@ -703,7 +703,7 @@  discard block
 block discarded – undo
703 703
 		// check for recently stored etag and stop if it is the same
704 704
 		$etagCacheKey = "$addressBookId#$cardUri";
705 705
 		if (isset($this->etagCache[$etagCacheKey]) && $this->etagCache[$etagCacheKey] === $etag) {
706
-			return '"' . $etag . '"';
706
+			return '"'.$etag.'"';
707 707
 		}
708 708
 
709 709
 		$query->update($this->dbCardsTable)
@@ -727,7 +727,7 @@  discard block
 block discarded – undo
727 727
 				'cardUri' => $cardUri,
728 728
 				'cardData' => $cardData]));
729 729
 
730
-		return '"' . $etag . '"';
730
+		return '"'.$etag.'"';
731 731
 	}
732 732
 
733 733
 	/**
@@ -842,7 +842,7 @@  discard block
 block discarded – undo
842 842
 		if ($syncToken) {
843 843
 			$query = "SELECT `uri`, `operation` FROM `*PREFIX*addressbookchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `addressbookid` = ? ORDER BY `synctoken`";
844 844
 			if ($limit > 0) {
845
-				$query .= " LIMIT " . (int)$limit;
845
+				$query .= " LIMIT ".(int) $limit;
846 846
 			}
847 847
 
848 848
 			// Fetching all changes
@@ -979,7 +979,7 @@  discard block
 block discarded – undo
979 979
 									   string $pattern,
980 980
 									   array $searchProperties,
981 981
 									   array $options = []): array {
982
-		$addressBookIds = array_map(static function ($row):int {
982
+		$addressBookIds = array_map(static function($row):int {
983 983
 			return (int) $row['id'];
984 984
 		}, $this->getAddressBooksForUser($principalUri));
985 985
 
@@ -1001,7 +1001,7 @@  discard block
 block discarded – undo
1001 1001
 
1002 1002
 		$query2 = $this->db->getQueryBuilder();
1003 1003
 
1004
-		$addressBookOr =  $query2->expr()->orX();
1004
+		$addressBookOr = $query2->expr()->orX();
1005 1005
 		foreach ($addressBookIds as $addressBookId) {
1006 1006
 			$addressBookOr->add($query2->expr()->eq('cp.addressbookid', $query2->createNamedParameter($addressBookId)));
1007 1007
 		}
@@ -1042,7 +1042,7 @@  discard block
 block discarded – undo
1042 1042
 			if (!$escapePattern) {
1043 1043
 				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter($pattern)));
1044 1044
 			} else {
1045
-				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1045
+				$query2->andWhere($query2->expr()->ilike('cp.value', $query2->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
1046 1046
 			}
1047 1047
 		}
1048 1048
 
@@ -1056,8 +1056,8 @@  discard block
 block discarded – undo
1056 1056
 		$result = $query2->execute();
1057 1057
 		$matches = $result->fetchAll();
1058 1058
 		$result->closeCursor();
1059
-		$matches = array_map(function ($match) {
1060
-			return (int)$match['cardid'];
1059
+		$matches = array_map(function($match) {
1060
+			return (int) $match['cardid'];
1061 1061
 		}, $matches);
1062 1062
 
1063 1063
 		$query = $this->db->getQueryBuilder();
@@ -1070,7 +1070,7 @@  discard block
 block discarded – undo
1070 1070
 
1071 1071
 		$result->closeCursor();
1072 1072
 
1073
-		return array_map(function ($array) {
1073
+		return array_map(function($array) {
1074 1074
 			$array['addressbookid'] = (int) $array['addressbookid'];
1075 1075
 			$modified = false;
1076 1076
 			$array['carddata'] = $this->readBlob($array['carddata'], $modified);
@@ -1117,7 +1117,7 @@  discard block
 block discarded – undo
1117 1117
 		$result->closeCursor();
1118 1118
 
1119 1119
 		if (!isset($uri['uri'])) {
1120
-			throw new \InvalidArgumentException('Card does not exists: ' . $id);
1120
+			throw new \InvalidArgumentException('Card does not exists: '.$id);
1121 1121
 		}
1122 1122
 
1123 1123
 		return $uri['uri'];
@@ -1142,7 +1142,7 @@  discard block
 block discarded – undo
1142 1142
 
1143 1143
 		if (is_array($contact)) {
1144 1144
 			$modified = false;
1145
-			$contact['etag'] = '"' . $contact['etag'] . '"';
1145
+			$contact['etag'] = '"'.$contact['etag'].'"';
1146 1146
 			$contact['carddata'] = $this->readBlob($contact['carddata'], $modified);
1147 1147
 			if ($modified) {
1148 1148
 				$contact['size'] = strlen($contact['carddata']);
@@ -1255,10 +1255,10 @@  discard block
 block discarded – undo
1255 1255
 		$result->closeCursor();
1256 1256
 
1257 1257
 		if (!isset($cardIds['id'])) {
1258
-			throw new \InvalidArgumentException('Card does not exists: ' . $uri);
1258
+			throw new \InvalidArgumentException('Card does not exists: '.$uri);
1259 1259
 		}
1260 1260
 
1261
-		return (int)$cardIds['id'];
1261
+		return (int) $cardIds['id'];
1262 1262
 	}
1263 1263
 
1264 1264
 	/**
@@ -1284,8 +1284,8 @@  discard block
 block discarded – undo
1284 1284
 	}
1285 1285
 
1286 1286
 	private function addOwnerPrincipal(&$addressbookInfo) {
1287
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
1288
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
1287
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
1288
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
1289 1289
 		if (isset($addressbookInfo[$ownerPrincipalKey])) {
1290 1290
 			$uri = $addressbookInfo[$ownerPrincipalKey];
1291 1291
 		} else {
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php 2 patches
Indentation   +776 added lines, -776 removed lines patch added patch discarded remove patch
@@ -59,780 +59,780 @@
 block discarded – undo
59 59
 
60 60
 class CloudFederationProviderFiles implements ICloudFederationProvider {
61 61
 
62
-	/** @var IAppManager */
63
-	private $appManager;
64
-
65
-	/** @var FederatedShareProvider */
66
-	private $federatedShareProvider;
67
-
68
-	/** @var AddressHandler */
69
-	private $addressHandler;
70
-
71
-	/** @var ILogger */
72
-	private $logger;
73
-
74
-	/** @var IUserManager */
75
-	private $userManager;
76
-
77
-	/** @var IManager */
78
-	private $shareManager;
79
-
80
-	/** @var ICloudIdManager */
81
-	private $cloudIdManager;
82
-
83
-	/** @var IActivityManager */
84
-	private $activityManager;
85
-
86
-	/** @var INotificationManager */
87
-	private $notificationManager;
88
-
89
-	/** @var IURLGenerator */
90
-	private $urlGenerator;
91
-
92
-	/** @var ICloudFederationFactory */
93
-	private $cloudFederationFactory;
94
-
95
-	/** @var ICloudFederationProviderManager */
96
-	private $cloudFederationProviderManager;
97
-
98
-	/** @var IDBConnection */
99
-	private $connection;
100
-
101
-	/** @var IGroupManager */
102
-	private $groupManager;
103
-
104
-	/**
105
-	 * CloudFederationProvider constructor.
106
-	 *
107
-	 * @param IAppManager $appManager
108
-	 * @param FederatedShareProvider $federatedShareProvider
109
-	 * @param AddressHandler $addressHandler
110
-	 * @param ILogger $logger
111
-	 * @param IUserManager $userManager
112
-	 * @param IManager $shareManager
113
-	 * @param ICloudIdManager $cloudIdManager
114
-	 * @param IActivityManager $activityManager
115
-	 * @param INotificationManager $notificationManager
116
-	 * @param IURLGenerator $urlGenerator
117
-	 * @param ICloudFederationFactory $cloudFederationFactory
118
-	 * @param ICloudFederationProviderManager $cloudFederationProviderManager
119
-	 * @param IDBConnection $connection
120
-	 * @param IGroupManager $groupManager
121
-	 */
122
-	public function __construct(IAppManager $appManager,
123
-								FederatedShareProvider $federatedShareProvider,
124
-								AddressHandler $addressHandler,
125
-								ILogger $logger,
126
-								IUserManager $userManager,
127
-								IManager $shareManager,
128
-								ICloudIdManager $cloudIdManager,
129
-								IActivityManager $activityManager,
130
-								INotificationManager $notificationManager,
131
-								IURLGenerator $urlGenerator,
132
-								ICloudFederationFactory $cloudFederationFactory,
133
-								ICloudFederationProviderManager $cloudFederationProviderManager,
134
-								IDBConnection $connection,
135
-								IGroupManager $groupManager
136
-	) {
137
-		$this->appManager = $appManager;
138
-		$this->federatedShareProvider = $federatedShareProvider;
139
-		$this->addressHandler = $addressHandler;
140
-		$this->logger = $logger;
141
-		$this->userManager = $userManager;
142
-		$this->shareManager = $shareManager;
143
-		$this->cloudIdManager = $cloudIdManager;
144
-		$this->activityManager = $activityManager;
145
-		$this->notificationManager = $notificationManager;
146
-		$this->urlGenerator = $urlGenerator;
147
-		$this->cloudFederationFactory = $cloudFederationFactory;
148
-		$this->cloudFederationProviderManager = $cloudFederationProviderManager;
149
-		$this->connection = $connection;
150
-		$this->groupManager = $groupManager;
151
-	}
152
-
153
-
154
-
155
-	/**
156
-	 * @return string
157
-	 */
158
-	public function getShareType() {
159
-		return 'file';
160
-	}
161
-
162
-	/**
163
-	 * share received from another server
164
-	 *
165
-	 * @param ICloudFederationShare $share
166
-	 * @return string provider specific unique ID of the share
167
-	 *
168
-	 * @throws ProviderCouldNotAddShareException
169
-	 * @throws \OCP\AppFramework\QueryException
170
-	 * @throws \OC\HintException
171
-	 * @since 14.0.0
172
-	 */
173
-	public function shareReceived(ICloudFederationShare $share) {
174
-		if (!$this->isS2SEnabled(true)) {
175
-			throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
176
-		}
177
-
178
-		$protocol = $share->getProtocol();
179
-		if ($protocol['name'] !== 'webdav') {
180
-			throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
181
-		}
182
-
183
-		list($ownerUid, $remote) = $this->addressHandler->splitUserRemote($share->getOwner());
184
-		// for backward compatibility make sure that the remote url stored in the
185
-		// database ends with a trailing slash
186
-		if (substr($remote, -1) !== '/') {
187
-			$remote = $remote . '/';
188
-		}
189
-
190
-		$token = $share->getShareSecret();
191
-		$name = $share->getResourceName();
192
-		$owner = $share->getOwnerDisplayName();
193
-		$sharedBy = $share->getSharedByDisplayName();
194
-		$shareWith = $share->getShareWith();
195
-		$remoteId = $share->getProviderId();
196
-		$sharedByFederatedId = $share->getSharedBy();
197
-		$ownerFederatedId = $share->getOwner();
198
-		$shareType = $this->mapShareTypeToNextcloud($share->getShareType());
199
-
200
-		// if no explicit information about the person who created the share was send
201
-		// we assume that the share comes from the owner
202
-		if ($sharedByFederatedId === null) {
203
-			$sharedBy = $owner;
204
-			$sharedByFederatedId = $ownerFederatedId;
205
-		}
206
-
207
-		if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
208
-			if (!Util::isValidFileName($name)) {
209
-				throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
210
-			}
211
-
212
-			// FIXME this should be a method in the user management instead
213
-			if ($shareType === IShare::TYPE_USER) {
214
-				$this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
215
-				Util::emitHook(
216
-					'\OCA\Files_Sharing\API\Server2Server',
217
-					'preLoginNameUsedAsUserName',
218
-					['uid' => &$shareWith]
219
-				);
220
-				$this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
221
-
222
-				if (!$this->userManager->userExists($shareWith)) {
223
-					throw new ProviderCouldNotAddShareException('User does not exists', '',Http::STATUS_BAD_REQUEST);
224
-				}
225
-
226
-				\OC_Util::setupFS($shareWith);
227
-			}
228
-
229
-			if ($shareType === IShare::TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
230
-				throw new ProviderCouldNotAddShareException('Group does not exists', '',Http::STATUS_BAD_REQUEST);
231
-			}
232
-
233
-			$externalManager = new \OCA\Files_Sharing\External\Manager(
234
-				\OC::$server->getDatabaseConnection(),
235
-				Filesystem::getMountManager(),
236
-				Filesystem::getLoader(),
237
-				\OC::$server->getHTTPClientService(),
238
-				\OC::$server->getNotificationManager(),
239
-				\OC::$server->query(\OCP\OCS\IDiscoveryService::class),
240
-				\OC::$server->getCloudFederationProviderManager(),
241
-				\OC::$server->getCloudFederationFactory(),
242
-				\OC::$server->getGroupManager(),
243
-				\OC::$server->getUserManager(),
244
-				$shareWith,
245
-				\OC::$server->query(IEventDispatcher::class)
246
-			);
247
-
248
-			try {
249
-				$externalManager->addShare($remote, $token, '', $name, $owner, $shareType,false, $shareWith, $remoteId);
250
-				$shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
251
-
252
-				if ($shareType === IShare::TYPE_USER) {
253
-					$event = $this->activityManager->generateEvent();
254
-					$event->setApp('files_sharing')
255
-						->setType('remote_share')
256
-						->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
257
-						->setAffectedUser($shareWith)
258
-						->setObject('remote_share', (int)$shareId, $name);
259
-					\OC::$server->getActivityManager()->publish($event);
260
-					$this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name);
261
-				} else {
262
-					$groupMembers = $this->groupManager->get($shareWith)->getUsers();
263
-					foreach ($groupMembers as $user) {
264
-						$event = $this->activityManager->generateEvent();
265
-						$event->setApp('files_sharing')
266
-							->setType('remote_share')
267
-							->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
268
-							->setAffectedUser($user->getUID())
269
-							->setObject('remote_share', (int)$shareId, $name);
270
-						\OC::$server->getActivityManager()->publish($event);
271
-						$this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name);
272
-					}
273
-				}
274
-				return $shareId;
275
-			} catch (\Exception $e) {
276
-				$this->logger->logException($e, [
277
-					'message' => 'Server can not add remote share.',
278
-					'level' => ILogger::ERROR,
279
-					'app' => 'files_sharing'
280
-				]);
281
-				throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
282
-			}
283
-		}
284
-
285
-		throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
286
-	}
287
-
288
-	/**
289
-	 * notification received from another server
290
-	 *
291
-	 * @param string $notificationType (e.g. SHARE_ACCEPTED)
292
-	 * @param string $providerId id of the share
293
-	 * @param array $notification payload of the notification
294
-	 * @return array data send back to the sender
295
-	 *
296
-	 * @throws ActionNotSupportedException
297
-	 * @throws AuthenticationFailedException
298
-	 * @throws BadRequestException
299
-	 * @throws \OC\HintException
300
-	 * @since 14.0.0
301
-	 */
302
-	public function notificationReceived($notificationType, $providerId, array $notification) {
303
-		switch ($notificationType) {
304
-			case 'SHARE_ACCEPTED':
305
-				return $this->shareAccepted($providerId, $notification);
306
-			case 'SHARE_DECLINED':
307
-				return $this->shareDeclined($providerId, $notification);
308
-			case 'SHARE_UNSHARED':
309
-				return $this->unshare($providerId, $notification);
310
-			case 'REQUEST_RESHARE':
311
-				return $this->reshareRequested($providerId, $notification);
312
-			case 'RESHARE_UNDO':
313
-				return $this->undoReshare($providerId, $notification);
314
-			case 'RESHARE_CHANGE_PERMISSION':
315
-				return $this->updateResharePermissions($providerId, $notification);
316
-		}
317
-
318
-
319
-		throw new BadRequestException([$notificationType]);
320
-	}
321
-
322
-	/**
323
-	 * map OCM share type (strings) to Nextcloud internal share types (integer)
324
-	 *
325
-	 * @param string $shareType
326
-	 * @return int
327
-	 */
328
-	private function mapShareTypeToNextcloud($shareType) {
329
-		$result = IShare::TYPE_USER;
330
-		if ($shareType === 'group') {
331
-			$result = IShare::TYPE_GROUP;
332
-		}
333
-
334
-		return $result;
335
-	}
336
-
337
-	private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name): void {
338
-		$notification = $this->notificationManager->createNotification();
339
-		$notification->setApp('files_sharing')
340
-			->setUser($shareWith)
341
-			->setDateTime(new \DateTime())
342
-			->setObject('remote_share', $shareId)
343
-			->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/')]);
344
-
345
-		$declineAction = $notification->createAction();
346
-		$declineAction->setLabel('decline')
347
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
348
-		$notification->addAction($declineAction);
349
-
350
-		$acceptAction = $notification->createAction();
351
-		$acceptAction->setLabel('accept')
352
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
353
-		$notification->addAction($acceptAction);
354
-
355
-		$this->notificationManager->notify($notification);
356
-	}
357
-
358
-	/**
359
-	 * process notification that the recipient accepted a share
360
-	 *
361
-	 * @param string $id
362
-	 * @param array $notification
363
-	 * @return array
364
-	 * @throws ActionNotSupportedException
365
-	 * @throws AuthenticationFailedException
366
-	 * @throws BadRequestException
367
-	 * @throws \OC\HintException
368
-	 */
369
-	private function shareAccepted($id, array $notification) {
370
-		if (!$this->isS2SEnabled()) {
371
-			throw new ActionNotSupportedException('Server does not support federated cloud sharing');
372
-		}
373
-
374
-		if (!isset($notification['sharedSecret'])) {
375
-			throw new BadRequestException(['sharedSecret']);
376
-		}
377
-
378
-		$token = $notification['sharedSecret'];
379
-
380
-		$share = $this->federatedShareProvider->getShareById($id);
381
-
382
-		$this->verifyShare($share, $token);
383
-		$this->executeAcceptShare($share);
384
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
385
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
386
-			$remoteId = $this->federatedShareProvider->getRemoteId($share);
387
-			$notification = $this->cloudFederationFactory->getCloudFederationNotification();
388
-			$notification->setMessage(
389
-				'SHARE_ACCEPTED',
390
-				'file',
391
-				$remoteId,
392
-				[
393
-					'sharedSecret' => $token,
394
-					'message' => 'Recipient accepted the re-share'
395
-				]
396
-
397
-			);
398
-			$this->cloudFederationProviderManager->sendNotification($remote, $notification);
399
-		}
400
-
401
-		return [];
402
-	}
403
-
404
-	/**
405
-	 * @param IShare $share
406
-	 * @throws ShareNotFound
407
-	 */
408
-	protected function executeAcceptShare(IShare $share) {
409
-		try {
410
-			$fileId = (int)$share->getNode()->getId();
411
-			list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
412
-		} catch (\Exception $e) {
413
-			throw new ShareNotFound();
414
-		}
415
-
416
-		$event = $this->activityManager->generateEvent();
417
-		$event->setApp('files_sharing')
418
-			->setType('remote_share')
419
-			->setAffectedUser($this->getCorrectUid($share))
420
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
421
-			->setObject('files', $fileId, $file)
422
-			->setLink($link);
423
-		$this->activityManager->publish($event);
424
-	}
425
-
426
-	/**
427
-	 * process notification that the recipient declined a share
428
-	 *
429
-	 * @param string $id
430
-	 * @param array $notification
431
-	 * @return array
432
-	 * @throws ActionNotSupportedException
433
-	 * @throws AuthenticationFailedException
434
-	 * @throws BadRequestException
435
-	 * @throws ShareNotFound
436
-	 * @throws \OC\HintException
437
-	 *
438
-	 */
439
-	protected function shareDeclined($id, array $notification) {
440
-		if (!$this->isS2SEnabled()) {
441
-			throw new ActionNotSupportedException('Server does not support federated cloud sharing');
442
-		}
443
-
444
-		if (!isset($notification['sharedSecret'])) {
445
-			throw new BadRequestException(['sharedSecret']);
446
-		}
447
-
448
-		$token = $notification['sharedSecret'];
449
-
450
-		$share = $this->federatedShareProvider->getShareById($id);
451
-
452
-		$this->verifyShare($share, $token);
453
-
454
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
455
-			list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
456
-			$remoteId = $this->federatedShareProvider->getRemoteId($share);
457
-			$notification = $this->cloudFederationFactory->getCloudFederationNotification();
458
-			$notification->setMessage(
459
-				'SHARE_DECLINED',
460
-				'file',
461
-				$remoteId,
462
-				[
463
-					'sharedSecret' => $token,
464
-					'message' => 'Recipient declined the re-share'
465
-				]
466
-
467
-			);
468
-			$this->cloudFederationProviderManager->sendNotification($remote, $notification);
469
-		}
470
-
471
-		$this->executeDeclineShare($share);
472
-
473
-		return [];
474
-	}
475
-
476
-	/**
477
-	 * delete declined share and create a activity
478
-	 *
479
-	 * @param IShare $share
480
-	 * @throws ShareNotFound
481
-	 */
482
-	protected function executeDeclineShare(IShare $share) {
483
-		$this->federatedShareProvider->removeShareFromTable($share);
484
-
485
-		try {
486
-			$fileId = (int)$share->getNode()->getId();
487
-			list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
488
-		} catch (\Exception $e) {
489
-			throw new ShareNotFound();
490
-		}
491
-
492
-		$event = $this->activityManager->generateEvent();
493
-		$event->setApp('files_sharing')
494
-			->setType('remote_share')
495
-			->setAffectedUser($this->getCorrectUid($share))
496
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
497
-			->setObject('files', $fileId, $file)
498
-			->setLink($link);
499
-		$this->activityManager->publish($event);
500
-	}
501
-
502
-	/**
503
-	 * received the notification that the owner unshared a file from you
504
-	 *
505
-	 * @param string $id
506
-	 * @param array $notification
507
-	 * @return array
508
-	 * @throws AuthenticationFailedException
509
-	 * @throws BadRequestException
510
-	 */
511
-	private function undoReshare($id, array $notification) {
512
-		if (!isset($notification['sharedSecret'])) {
513
-			throw new BadRequestException(['sharedSecret']);
514
-		}
515
-		$token = $notification['sharedSecret'];
516
-
517
-		$share = $this->federatedShareProvider->getShareById($id);
518
-
519
-		$this->verifyShare($share, $token);
520
-		$this->federatedShareProvider->removeShareFromTable($share);
521
-		return [];
522
-	}
523
-
524
-	/**
525
-	 * unshare file from self
526
-	 *
527
-	 * @param string $id
528
-	 * @param array $notification
529
-	 * @return array
530
-	 * @throws ActionNotSupportedException
531
-	 * @throws BadRequestException
532
-	 */
533
-	private function unshare($id, array $notification) {
534
-		if (!$this->isS2SEnabled(true)) {
535
-			throw new ActionNotSupportedException("incoming shares disabled!");
536
-		}
537
-
538
-		if (!isset($notification['sharedSecret'])) {
539
-			throw new BadRequestException(['sharedSecret']);
540
-		}
541
-		$token = $notification['sharedSecret'];
542
-
543
-		$qb = $this->connection->getQueryBuilder();
544
-		$qb->select('*')
545
-			->from('share_external')
546
-			->where(
547
-				$qb->expr()->andX(
548
-					$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
549
-					$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
550
-				)
551
-			);
552
-
553
-		$result = $qb->execute();
554
-		$share = $result->fetch();
555
-		$result->closeCursor();
556
-
557
-		if ($token && $id && !empty($share)) {
558
-			$remote = $this->cleanupRemote($share['remote']);
559
-
560
-			$owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
561
-			$mountpoint = $share['mountpoint'];
562
-			$user = $share['user'];
563
-
564
-			$qb = $this->connection->getQueryBuilder();
565
-			$qb->delete('share_external')
566
-				->where(
567
-					$qb->expr()->andX(
568
-						$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
569
-						$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
570
-					)
571
-				);
572
-
573
-			$qb->execute();
574
-
575
-			// delete all child in case of a group share
576
-			$qb = $this->connection->getQueryBuilder();
577
-			$qb->delete('share_external')
578
-				->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
579
-			$qb->execute();
580
-
581
-			if ((int)$share['share_type'] === IShare::TYPE_USER) {
582
-				if ($share['accepted']) {
583
-					$path = trim($mountpoint, '/');
584
-				} else {
585
-					$path = trim($share['name'], '/');
586
-				}
587
-				$notification = $this->notificationManager->createNotification();
588
-				$notification->setApp('files_sharing')
589
-					->setUser($share['user'])
590
-					->setObject('remote_share', (int)$share['id']);
591
-				$this->notificationManager->markProcessed($notification);
592
-
593
-				$event = $this->activityManager->generateEvent();
594
-				$event->setApp('files_sharing')
595
-					->setType('remote_share')
596
-					->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
597
-					->setAffectedUser($user)
598
-					->setObject('remote_share', (int)$share['id'], $path);
599
-				\OC::$server->getActivityManager()->publish($event);
600
-			}
601
-		}
602
-
603
-		return [];
604
-	}
605
-
606
-	private function cleanupRemote($remote) {
607
-		$remote = substr($remote, strpos($remote, '://') + 3);
608
-
609
-		return rtrim($remote, '/');
610
-	}
611
-
612
-	/**
613
-	 * recipient of a share request to re-share the file with another user
614
-	 *
615
-	 * @param string $id
616
-	 * @param array $notification
617
-	 * @return array
618
-	 * @throws AuthenticationFailedException
619
-	 * @throws BadRequestException
620
-	 * @throws ProviderCouldNotAddShareException
621
-	 * @throws ShareNotFound
622
-	 */
623
-	protected function reshareRequested($id, array $notification) {
624
-		if (!isset($notification['sharedSecret'])) {
625
-			throw new BadRequestException(['sharedSecret']);
626
-		}
627
-		$token = $notification['sharedSecret'];
628
-
629
-		if (!isset($notification['shareWith'])) {
630
-			throw new BadRequestException(['shareWith']);
631
-		}
632
-		$shareWith = $notification['shareWith'];
633
-
634
-		if (!isset($notification['senderId'])) {
635
-			throw new BadRequestException(['senderId']);
636
-		}
637
-		$senderId = $notification['senderId'];
638
-
639
-		$share = $this->federatedShareProvider->getShareById($id);
640
-		// don't allow to share a file back to the owner
641
-		try {
642
-			list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
643
-			$owner = $share->getShareOwner();
644
-			$currentServer = $this->addressHandler->generateRemoteURL();
645
-			if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
646
-				throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
647
-			}
648
-		} catch (\Exception $e) {
649
-			throw new ProviderCouldNotAddShareException($e->getMessage());
650
-		}
651
-
652
-		$this->verifyShare($share, $token);
653
-
654
-		// check if re-sharing is allowed
655
-		if ($share->getPermissions() & Constants::PERMISSION_SHARE) {
656
-			// the recipient of the initial share is now the initiator for the re-share
657
-			$share->setSharedBy($share->getSharedWith());
658
-			$share->setSharedWith($shareWith);
659
-			$result = $this->federatedShareProvider->create($share);
660
-			$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
661
-			return ['token' => $result->getToken(), 'providerId' => $result->getId()];
662
-		} else {
663
-			throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
664
-		}
665
-	}
666
-
667
-	/**
668
-	 * update permission of a re-share so that the share dialog shows the right
669
-	 * permission if the owner or the sender changes the permission
670
-	 *
671
-	 * @param string $id
672
-	 * @param array $notification
673
-	 * @return array
674
-	 * @throws AuthenticationFailedException
675
-	 * @throws BadRequestException
676
-	 */
677
-	protected function updateResharePermissions($id, array $notification) {
678
-		if (!isset($notification['sharedSecret'])) {
679
-			throw new BadRequestException(['sharedSecret']);
680
-		}
681
-		$token = $notification['sharedSecret'];
682
-
683
-		if (!isset($notification['permission'])) {
684
-			throw new BadRequestException(['permission']);
685
-		}
686
-		$ocmPermissions = $notification['permission'];
687
-
688
-		$share = $this->federatedShareProvider->getShareById($id);
689
-
690
-		$ncPermission = $this->ocmPermissions2ncPermissions($ocmPermissions);
691
-
692
-		$this->verifyShare($share, $token);
693
-		$this->updatePermissionsInDatabase($share, $ncPermission);
694
-
695
-		return [];
696
-	}
697
-
698
-	/**
699
-	 * translate OCM Permissions to Nextcloud permissions
700
-	 *
701
-	 * @param array $ocmPermissions
702
-	 * @return int
703
-	 * @throws BadRequestException
704
-	 */
705
-	protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
706
-		$ncPermissions = 0;
707
-		foreach ($ocmPermissions as $permission) {
708
-			switch (strtolower($permission)) {
709
-				case 'read':
710
-					$ncPermissions += Constants::PERMISSION_READ;
711
-					break;
712
-				case 'write':
713
-					$ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
714
-					break;
715
-				case 'share':
716
-					$ncPermissions += Constants::PERMISSION_SHARE;
717
-					break;
718
-				default:
719
-					throw new BadRequestException(['permission']);
720
-			}
721
-		}
722
-
723
-		return $ncPermissions;
724
-	}
725
-
726
-	/**
727
-	 * update permissions in database
728
-	 *
729
-	 * @param IShare $share
730
-	 * @param int $permissions
731
-	 */
732
-	protected function updatePermissionsInDatabase(IShare $share, $permissions) {
733
-		$query = $this->connection->getQueryBuilder();
734
-		$query->update('share')
735
-			->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
736
-			->set('permissions', $query->createNamedParameter($permissions))
737
-			->execute();
738
-	}
739
-
740
-
741
-	/**
742
-	 * get file
743
-	 *
744
-	 * @param string $user
745
-	 * @param int $fileSource
746
-	 * @return array with internal path of the file and a absolute link to it
747
-	 */
748
-	private function getFile($user, $fileSource) {
749
-		\OC_Util::setupFS($user);
750
-
751
-		try {
752
-			$file = Filesystem::getPath($fileSource);
753
-		} catch (NotFoundException $e) {
754
-			$file = null;
755
-		}
756
-		$args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file];
757
-		$link = Util::linkToAbsolute('files', 'index.php', $args);
758
-
759
-		return [$file, $link];
760
-	}
761
-
762
-	/**
763
-	 * check if we are the initiator or the owner of a re-share and return the correct UID
764
-	 *
765
-	 * @param IShare $share
766
-	 * @return string
767
-	 */
768
-	protected function getCorrectUid(IShare $share) {
769
-		if ($this->userManager->userExists($share->getShareOwner())) {
770
-			return $share->getShareOwner();
771
-		}
772
-
773
-		return $share->getSharedBy();
774
-	}
775
-
776
-
777
-
778
-	/**
779
-	 * check if we got the right share
780
-	 *
781
-	 * @param IShare $share
782
-	 * @param string $token
783
-	 * @return bool
784
-	 * @throws AuthenticationFailedException
785
-	 */
786
-	protected function verifyShare(IShare $share, $token) {
787
-		if (
788
-			$share->getShareType() === IShare::TYPE_REMOTE &&
789
-			$share->getToken() === $token
790
-		) {
791
-			return true;
792
-		}
793
-
794
-		if ($share->getShareType() === IShare::TYPE_CIRCLE) {
795
-			try {
796
-				$knownShare = $this->shareManager->getShareByToken($token);
797
-				if ($knownShare->getId() === $share->getId()) {
798
-					return true;
799
-				}
800
-			} catch (ShareNotFound $e) {
801
-			}
802
-		}
803
-
804
-		throw new AuthenticationFailedException();
805
-	}
806
-
807
-
808
-
809
-	/**
810
-	 * check if server-to-server sharing is enabled
811
-	 *
812
-	 * @param bool $incoming
813
-	 * @return bool
814
-	 */
815
-	private function isS2SEnabled($incoming = false) {
816
-		$result = $this->appManager->isEnabledForUser('files_sharing');
817
-
818
-		if ($incoming) {
819
-			$result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
820
-		} else {
821
-			$result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
822
-		}
823
-
824
-		return $result;
825
-	}
826
-
827
-
828
-	/**
829
-	 * get the supported share types, e.g. "user", "group", etc.
830
-	 *
831
-	 * @return array
832
-	 *
833
-	 * @since 14.0.0
834
-	 */
835
-	public function getSupportedShareTypes() {
836
-		return ['user', 'group'];
837
-	}
62
+    /** @var IAppManager */
63
+    private $appManager;
64
+
65
+    /** @var FederatedShareProvider */
66
+    private $federatedShareProvider;
67
+
68
+    /** @var AddressHandler */
69
+    private $addressHandler;
70
+
71
+    /** @var ILogger */
72
+    private $logger;
73
+
74
+    /** @var IUserManager */
75
+    private $userManager;
76
+
77
+    /** @var IManager */
78
+    private $shareManager;
79
+
80
+    /** @var ICloudIdManager */
81
+    private $cloudIdManager;
82
+
83
+    /** @var IActivityManager */
84
+    private $activityManager;
85
+
86
+    /** @var INotificationManager */
87
+    private $notificationManager;
88
+
89
+    /** @var IURLGenerator */
90
+    private $urlGenerator;
91
+
92
+    /** @var ICloudFederationFactory */
93
+    private $cloudFederationFactory;
94
+
95
+    /** @var ICloudFederationProviderManager */
96
+    private $cloudFederationProviderManager;
97
+
98
+    /** @var IDBConnection */
99
+    private $connection;
100
+
101
+    /** @var IGroupManager */
102
+    private $groupManager;
103
+
104
+    /**
105
+     * CloudFederationProvider constructor.
106
+     *
107
+     * @param IAppManager $appManager
108
+     * @param FederatedShareProvider $federatedShareProvider
109
+     * @param AddressHandler $addressHandler
110
+     * @param ILogger $logger
111
+     * @param IUserManager $userManager
112
+     * @param IManager $shareManager
113
+     * @param ICloudIdManager $cloudIdManager
114
+     * @param IActivityManager $activityManager
115
+     * @param INotificationManager $notificationManager
116
+     * @param IURLGenerator $urlGenerator
117
+     * @param ICloudFederationFactory $cloudFederationFactory
118
+     * @param ICloudFederationProviderManager $cloudFederationProviderManager
119
+     * @param IDBConnection $connection
120
+     * @param IGroupManager $groupManager
121
+     */
122
+    public function __construct(IAppManager $appManager,
123
+                                FederatedShareProvider $federatedShareProvider,
124
+                                AddressHandler $addressHandler,
125
+                                ILogger $logger,
126
+                                IUserManager $userManager,
127
+                                IManager $shareManager,
128
+                                ICloudIdManager $cloudIdManager,
129
+                                IActivityManager $activityManager,
130
+                                INotificationManager $notificationManager,
131
+                                IURLGenerator $urlGenerator,
132
+                                ICloudFederationFactory $cloudFederationFactory,
133
+                                ICloudFederationProviderManager $cloudFederationProviderManager,
134
+                                IDBConnection $connection,
135
+                                IGroupManager $groupManager
136
+    ) {
137
+        $this->appManager = $appManager;
138
+        $this->federatedShareProvider = $federatedShareProvider;
139
+        $this->addressHandler = $addressHandler;
140
+        $this->logger = $logger;
141
+        $this->userManager = $userManager;
142
+        $this->shareManager = $shareManager;
143
+        $this->cloudIdManager = $cloudIdManager;
144
+        $this->activityManager = $activityManager;
145
+        $this->notificationManager = $notificationManager;
146
+        $this->urlGenerator = $urlGenerator;
147
+        $this->cloudFederationFactory = $cloudFederationFactory;
148
+        $this->cloudFederationProviderManager = $cloudFederationProviderManager;
149
+        $this->connection = $connection;
150
+        $this->groupManager = $groupManager;
151
+    }
152
+
153
+
154
+
155
+    /**
156
+     * @return string
157
+     */
158
+    public function getShareType() {
159
+        return 'file';
160
+    }
161
+
162
+    /**
163
+     * share received from another server
164
+     *
165
+     * @param ICloudFederationShare $share
166
+     * @return string provider specific unique ID of the share
167
+     *
168
+     * @throws ProviderCouldNotAddShareException
169
+     * @throws \OCP\AppFramework\QueryException
170
+     * @throws \OC\HintException
171
+     * @since 14.0.0
172
+     */
173
+    public function shareReceived(ICloudFederationShare $share) {
174
+        if (!$this->isS2SEnabled(true)) {
175
+            throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
176
+        }
177
+
178
+        $protocol = $share->getProtocol();
179
+        if ($protocol['name'] !== 'webdav') {
180
+            throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
181
+        }
182
+
183
+        list($ownerUid, $remote) = $this->addressHandler->splitUserRemote($share->getOwner());
184
+        // for backward compatibility make sure that the remote url stored in the
185
+        // database ends with a trailing slash
186
+        if (substr($remote, -1) !== '/') {
187
+            $remote = $remote . '/';
188
+        }
189
+
190
+        $token = $share->getShareSecret();
191
+        $name = $share->getResourceName();
192
+        $owner = $share->getOwnerDisplayName();
193
+        $sharedBy = $share->getSharedByDisplayName();
194
+        $shareWith = $share->getShareWith();
195
+        $remoteId = $share->getProviderId();
196
+        $sharedByFederatedId = $share->getSharedBy();
197
+        $ownerFederatedId = $share->getOwner();
198
+        $shareType = $this->mapShareTypeToNextcloud($share->getShareType());
199
+
200
+        // if no explicit information about the person who created the share was send
201
+        // we assume that the share comes from the owner
202
+        if ($sharedByFederatedId === null) {
203
+            $sharedBy = $owner;
204
+            $sharedByFederatedId = $ownerFederatedId;
205
+        }
206
+
207
+        if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
208
+            if (!Util::isValidFileName($name)) {
209
+                throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
210
+            }
211
+
212
+            // FIXME this should be a method in the user management instead
213
+            if ($shareType === IShare::TYPE_USER) {
214
+                $this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
215
+                Util::emitHook(
216
+                    '\OCA\Files_Sharing\API\Server2Server',
217
+                    'preLoginNameUsedAsUserName',
218
+                    ['uid' => &$shareWith]
219
+                );
220
+                $this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
221
+
222
+                if (!$this->userManager->userExists($shareWith)) {
223
+                    throw new ProviderCouldNotAddShareException('User does not exists', '',Http::STATUS_BAD_REQUEST);
224
+                }
225
+
226
+                \OC_Util::setupFS($shareWith);
227
+            }
228
+
229
+            if ($shareType === IShare::TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
230
+                throw new ProviderCouldNotAddShareException('Group does not exists', '',Http::STATUS_BAD_REQUEST);
231
+            }
232
+
233
+            $externalManager = new \OCA\Files_Sharing\External\Manager(
234
+                \OC::$server->getDatabaseConnection(),
235
+                Filesystem::getMountManager(),
236
+                Filesystem::getLoader(),
237
+                \OC::$server->getHTTPClientService(),
238
+                \OC::$server->getNotificationManager(),
239
+                \OC::$server->query(\OCP\OCS\IDiscoveryService::class),
240
+                \OC::$server->getCloudFederationProviderManager(),
241
+                \OC::$server->getCloudFederationFactory(),
242
+                \OC::$server->getGroupManager(),
243
+                \OC::$server->getUserManager(),
244
+                $shareWith,
245
+                \OC::$server->query(IEventDispatcher::class)
246
+            );
247
+
248
+            try {
249
+                $externalManager->addShare($remote, $token, '', $name, $owner, $shareType,false, $shareWith, $remoteId);
250
+                $shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
251
+
252
+                if ($shareType === IShare::TYPE_USER) {
253
+                    $event = $this->activityManager->generateEvent();
254
+                    $event->setApp('files_sharing')
255
+                        ->setType('remote_share')
256
+                        ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
257
+                        ->setAffectedUser($shareWith)
258
+                        ->setObject('remote_share', (int)$shareId, $name);
259
+                    \OC::$server->getActivityManager()->publish($event);
260
+                    $this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name);
261
+                } else {
262
+                    $groupMembers = $this->groupManager->get($shareWith)->getUsers();
263
+                    foreach ($groupMembers as $user) {
264
+                        $event = $this->activityManager->generateEvent();
265
+                        $event->setApp('files_sharing')
266
+                            ->setType('remote_share')
267
+                            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
268
+                            ->setAffectedUser($user->getUID())
269
+                            ->setObject('remote_share', (int)$shareId, $name);
270
+                        \OC::$server->getActivityManager()->publish($event);
271
+                        $this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name);
272
+                    }
273
+                }
274
+                return $shareId;
275
+            } catch (\Exception $e) {
276
+                $this->logger->logException($e, [
277
+                    'message' => 'Server can not add remote share.',
278
+                    'level' => ILogger::ERROR,
279
+                    'app' => 'files_sharing'
280
+                ]);
281
+                throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
282
+            }
283
+        }
284
+
285
+        throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
286
+    }
287
+
288
+    /**
289
+     * notification received from another server
290
+     *
291
+     * @param string $notificationType (e.g. SHARE_ACCEPTED)
292
+     * @param string $providerId id of the share
293
+     * @param array $notification payload of the notification
294
+     * @return array data send back to the sender
295
+     *
296
+     * @throws ActionNotSupportedException
297
+     * @throws AuthenticationFailedException
298
+     * @throws BadRequestException
299
+     * @throws \OC\HintException
300
+     * @since 14.0.0
301
+     */
302
+    public function notificationReceived($notificationType, $providerId, array $notification) {
303
+        switch ($notificationType) {
304
+            case 'SHARE_ACCEPTED':
305
+                return $this->shareAccepted($providerId, $notification);
306
+            case 'SHARE_DECLINED':
307
+                return $this->shareDeclined($providerId, $notification);
308
+            case 'SHARE_UNSHARED':
309
+                return $this->unshare($providerId, $notification);
310
+            case 'REQUEST_RESHARE':
311
+                return $this->reshareRequested($providerId, $notification);
312
+            case 'RESHARE_UNDO':
313
+                return $this->undoReshare($providerId, $notification);
314
+            case 'RESHARE_CHANGE_PERMISSION':
315
+                return $this->updateResharePermissions($providerId, $notification);
316
+        }
317
+
318
+
319
+        throw new BadRequestException([$notificationType]);
320
+    }
321
+
322
+    /**
323
+     * map OCM share type (strings) to Nextcloud internal share types (integer)
324
+     *
325
+     * @param string $shareType
326
+     * @return int
327
+     */
328
+    private function mapShareTypeToNextcloud($shareType) {
329
+        $result = IShare::TYPE_USER;
330
+        if ($shareType === 'group') {
331
+            $result = IShare::TYPE_GROUP;
332
+        }
333
+
334
+        return $result;
335
+    }
336
+
337
+    private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name): void {
338
+        $notification = $this->notificationManager->createNotification();
339
+        $notification->setApp('files_sharing')
340
+            ->setUser($shareWith)
341
+            ->setDateTime(new \DateTime())
342
+            ->setObject('remote_share', $shareId)
343
+            ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/')]);
344
+
345
+        $declineAction = $notification->createAction();
346
+        $declineAction->setLabel('decline')
347
+            ->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
348
+        $notification->addAction($declineAction);
349
+
350
+        $acceptAction = $notification->createAction();
351
+        $acceptAction->setLabel('accept')
352
+            ->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
353
+        $notification->addAction($acceptAction);
354
+
355
+        $this->notificationManager->notify($notification);
356
+    }
357
+
358
+    /**
359
+     * process notification that the recipient accepted a share
360
+     *
361
+     * @param string $id
362
+     * @param array $notification
363
+     * @return array
364
+     * @throws ActionNotSupportedException
365
+     * @throws AuthenticationFailedException
366
+     * @throws BadRequestException
367
+     * @throws \OC\HintException
368
+     */
369
+    private function shareAccepted($id, array $notification) {
370
+        if (!$this->isS2SEnabled()) {
371
+            throw new ActionNotSupportedException('Server does not support federated cloud sharing');
372
+        }
373
+
374
+        if (!isset($notification['sharedSecret'])) {
375
+            throw new BadRequestException(['sharedSecret']);
376
+        }
377
+
378
+        $token = $notification['sharedSecret'];
379
+
380
+        $share = $this->federatedShareProvider->getShareById($id);
381
+
382
+        $this->verifyShare($share, $token);
383
+        $this->executeAcceptShare($share);
384
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
385
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
386
+            $remoteId = $this->federatedShareProvider->getRemoteId($share);
387
+            $notification = $this->cloudFederationFactory->getCloudFederationNotification();
388
+            $notification->setMessage(
389
+                'SHARE_ACCEPTED',
390
+                'file',
391
+                $remoteId,
392
+                [
393
+                    'sharedSecret' => $token,
394
+                    'message' => 'Recipient accepted the re-share'
395
+                ]
396
+
397
+            );
398
+            $this->cloudFederationProviderManager->sendNotification($remote, $notification);
399
+        }
400
+
401
+        return [];
402
+    }
403
+
404
+    /**
405
+     * @param IShare $share
406
+     * @throws ShareNotFound
407
+     */
408
+    protected function executeAcceptShare(IShare $share) {
409
+        try {
410
+            $fileId = (int)$share->getNode()->getId();
411
+            list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
412
+        } catch (\Exception $e) {
413
+            throw new ShareNotFound();
414
+        }
415
+
416
+        $event = $this->activityManager->generateEvent();
417
+        $event->setApp('files_sharing')
418
+            ->setType('remote_share')
419
+            ->setAffectedUser($this->getCorrectUid($share))
420
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
421
+            ->setObject('files', $fileId, $file)
422
+            ->setLink($link);
423
+        $this->activityManager->publish($event);
424
+    }
425
+
426
+    /**
427
+     * process notification that the recipient declined a share
428
+     *
429
+     * @param string $id
430
+     * @param array $notification
431
+     * @return array
432
+     * @throws ActionNotSupportedException
433
+     * @throws AuthenticationFailedException
434
+     * @throws BadRequestException
435
+     * @throws ShareNotFound
436
+     * @throws \OC\HintException
437
+     *
438
+     */
439
+    protected function shareDeclined($id, array $notification) {
440
+        if (!$this->isS2SEnabled()) {
441
+            throw new ActionNotSupportedException('Server does not support federated cloud sharing');
442
+        }
443
+
444
+        if (!isset($notification['sharedSecret'])) {
445
+            throw new BadRequestException(['sharedSecret']);
446
+        }
447
+
448
+        $token = $notification['sharedSecret'];
449
+
450
+        $share = $this->federatedShareProvider->getShareById($id);
451
+
452
+        $this->verifyShare($share, $token);
453
+
454
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
455
+            list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
456
+            $remoteId = $this->federatedShareProvider->getRemoteId($share);
457
+            $notification = $this->cloudFederationFactory->getCloudFederationNotification();
458
+            $notification->setMessage(
459
+                'SHARE_DECLINED',
460
+                'file',
461
+                $remoteId,
462
+                [
463
+                    'sharedSecret' => $token,
464
+                    'message' => 'Recipient declined the re-share'
465
+                ]
466
+
467
+            );
468
+            $this->cloudFederationProviderManager->sendNotification($remote, $notification);
469
+        }
470
+
471
+        $this->executeDeclineShare($share);
472
+
473
+        return [];
474
+    }
475
+
476
+    /**
477
+     * delete declined share and create a activity
478
+     *
479
+     * @param IShare $share
480
+     * @throws ShareNotFound
481
+     */
482
+    protected function executeDeclineShare(IShare $share) {
483
+        $this->federatedShareProvider->removeShareFromTable($share);
484
+
485
+        try {
486
+            $fileId = (int)$share->getNode()->getId();
487
+            list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
488
+        } catch (\Exception $e) {
489
+            throw new ShareNotFound();
490
+        }
491
+
492
+        $event = $this->activityManager->generateEvent();
493
+        $event->setApp('files_sharing')
494
+            ->setType('remote_share')
495
+            ->setAffectedUser($this->getCorrectUid($share))
496
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
497
+            ->setObject('files', $fileId, $file)
498
+            ->setLink($link);
499
+        $this->activityManager->publish($event);
500
+    }
501
+
502
+    /**
503
+     * received the notification that the owner unshared a file from you
504
+     *
505
+     * @param string $id
506
+     * @param array $notification
507
+     * @return array
508
+     * @throws AuthenticationFailedException
509
+     * @throws BadRequestException
510
+     */
511
+    private function undoReshare($id, array $notification) {
512
+        if (!isset($notification['sharedSecret'])) {
513
+            throw new BadRequestException(['sharedSecret']);
514
+        }
515
+        $token = $notification['sharedSecret'];
516
+
517
+        $share = $this->federatedShareProvider->getShareById($id);
518
+
519
+        $this->verifyShare($share, $token);
520
+        $this->federatedShareProvider->removeShareFromTable($share);
521
+        return [];
522
+    }
523
+
524
+    /**
525
+     * unshare file from self
526
+     *
527
+     * @param string $id
528
+     * @param array $notification
529
+     * @return array
530
+     * @throws ActionNotSupportedException
531
+     * @throws BadRequestException
532
+     */
533
+    private function unshare($id, array $notification) {
534
+        if (!$this->isS2SEnabled(true)) {
535
+            throw new ActionNotSupportedException("incoming shares disabled!");
536
+        }
537
+
538
+        if (!isset($notification['sharedSecret'])) {
539
+            throw new BadRequestException(['sharedSecret']);
540
+        }
541
+        $token = $notification['sharedSecret'];
542
+
543
+        $qb = $this->connection->getQueryBuilder();
544
+        $qb->select('*')
545
+            ->from('share_external')
546
+            ->where(
547
+                $qb->expr()->andX(
548
+                    $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
549
+                    $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
550
+                )
551
+            );
552
+
553
+        $result = $qb->execute();
554
+        $share = $result->fetch();
555
+        $result->closeCursor();
556
+
557
+        if ($token && $id && !empty($share)) {
558
+            $remote = $this->cleanupRemote($share['remote']);
559
+
560
+            $owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
561
+            $mountpoint = $share['mountpoint'];
562
+            $user = $share['user'];
563
+
564
+            $qb = $this->connection->getQueryBuilder();
565
+            $qb->delete('share_external')
566
+                ->where(
567
+                    $qb->expr()->andX(
568
+                        $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
569
+                        $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
570
+                    )
571
+                );
572
+
573
+            $qb->execute();
574
+
575
+            // delete all child in case of a group share
576
+            $qb = $this->connection->getQueryBuilder();
577
+            $qb->delete('share_external')
578
+                ->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
579
+            $qb->execute();
580
+
581
+            if ((int)$share['share_type'] === IShare::TYPE_USER) {
582
+                if ($share['accepted']) {
583
+                    $path = trim($mountpoint, '/');
584
+                } else {
585
+                    $path = trim($share['name'], '/');
586
+                }
587
+                $notification = $this->notificationManager->createNotification();
588
+                $notification->setApp('files_sharing')
589
+                    ->setUser($share['user'])
590
+                    ->setObject('remote_share', (int)$share['id']);
591
+                $this->notificationManager->markProcessed($notification);
592
+
593
+                $event = $this->activityManager->generateEvent();
594
+                $event->setApp('files_sharing')
595
+                    ->setType('remote_share')
596
+                    ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
597
+                    ->setAffectedUser($user)
598
+                    ->setObject('remote_share', (int)$share['id'], $path);
599
+                \OC::$server->getActivityManager()->publish($event);
600
+            }
601
+        }
602
+
603
+        return [];
604
+    }
605
+
606
+    private function cleanupRemote($remote) {
607
+        $remote = substr($remote, strpos($remote, '://') + 3);
608
+
609
+        return rtrim($remote, '/');
610
+    }
611
+
612
+    /**
613
+     * recipient of a share request to re-share the file with another user
614
+     *
615
+     * @param string $id
616
+     * @param array $notification
617
+     * @return array
618
+     * @throws AuthenticationFailedException
619
+     * @throws BadRequestException
620
+     * @throws ProviderCouldNotAddShareException
621
+     * @throws ShareNotFound
622
+     */
623
+    protected function reshareRequested($id, array $notification) {
624
+        if (!isset($notification['sharedSecret'])) {
625
+            throw new BadRequestException(['sharedSecret']);
626
+        }
627
+        $token = $notification['sharedSecret'];
628
+
629
+        if (!isset($notification['shareWith'])) {
630
+            throw new BadRequestException(['shareWith']);
631
+        }
632
+        $shareWith = $notification['shareWith'];
633
+
634
+        if (!isset($notification['senderId'])) {
635
+            throw new BadRequestException(['senderId']);
636
+        }
637
+        $senderId = $notification['senderId'];
638
+
639
+        $share = $this->federatedShareProvider->getShareById($id);
640
+        // don't allow to share a file back to the owner
641
+        try {
642
+            list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
643
+            $owner = $share->getShareOwner();
644
+            $currentServer = $this->addressHandler->generateRemoteURL();
645
+            if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
646
+                throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
647
+            }
648
+        } catch (\Exception $e) {
649
+            throw new ProviderCouldNotAddShareException($e->getMessage());
650
+        }
651
+
652
+        $this->verifyShare($share, $token);
653
+
654
+        // check if re-sharing is allowed
655
+        if ($share->getPermissions() & Constants::PERMISSION_SHARE) {
656
+            // the recipient of the initial share is now the initiator for the re-share
657
+            $share->setSharedBy($share->getSharedWith());
658
+            $share->setSharedWith($shareWith);
659
+            $result = $this->federatedShareProvider->create($share);
660
+            $this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
661
+            return ['token' => $result->getToken(), 'providerId' => $result->getId()];
662
+        } else {
663
+            throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
664
+        }
665
+    }
666
+
667
+    /**
668
+     * update permission of a re-share so that the share dialog shows the right
669
+     * permission if the owner or the sender changes the permission
670
+     *
671
+     * @param string $id
672
+     * @param array $notification
673
+     * @return array
674
+     * @throws AuthenticationFailedException
675
+     * @throws BadRequestException
676
+     */
677
+    protected function updateResharePermissions($id, array $notification) {
678
+        if (!isset($notification['sharedSecret'])) {
679
+            throw new BadRequestException(['sharedSecret']);
680
+        }
681
+        $token = $notification['sharedSecret'];
682
+
683
+        if (!isset($notification['permission'])) {
684
+            throw new BadRequestException(['permission']);
685
+        }
686
+        $ocmPermissions = $notification['permission'];
687
+
688
+        $share = $this->federatedShareProvider->getShareById($id);
689
+
690
+        $ncPermission = $this->ocmPermissions2ncPermissions($ocmPermissions);
691
+
692
+        $this->verifyShare($share, $token);
693
+        $this->updatePermissionsInDatabase($share, $ncPermission);
694
+
695
+        return [];
696
+    }
697
+
698
+    /**
699
+     * translate OCM Permissions to Nextcloud permissions
700
+     *
701
+     * @param array $ocmPermissions
702
+     * @return int
703
+     * @throws BadRequestException
704
+     */
705
+    protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
706
+        $ncPermissions = 0;
707
+        foreach ($ocmPermissions as $permission) {
708
+            switch (strtolower($permission)) {
709
+                case 'read':
710
+                    $ncPermissions += Constants::PERMISSION_READ;
711
+                    break;
712
+                case 'write':
713
+                    $ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
714
+                    break;
715
+                case 'share':
716
+                    $ncPermissions += Constants::PERMISSION_SHARE;
717
+                    break;
718
+                default:
719
+                    throw new BadRequestException(['permission']);
720
+            }
721
+        }
722
+
723
+        return $ncPermissions;
724
+    }
725
+
726
+    /**
727
+     * update permissions in database
728
+     *
729
+     * @param IShare $share
730
+     * @param int $permissions
731
+     */
732
+    protected function updatePermissionsInDatabase(IShare $share, $permissions) {
733
+        $query = $this->connection->getQueryBuilder();
734
+        $query->update('share')
735
+            ->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
736
+            ->set('permissions', $query->createNamedParameter($permissions))
737
+            ->execute();
738
+    }
739
+
740
+
741
+    /**
742
+     * get file
743
+     *
744
+     * @param string $user
745
+     * @param int $fileSource
746
+     * @return array with internal path of the file and a absolute link to it
747
+     */
748
+    private function getFile($user, $fileSource) {
749
+        \OC_Util::setupFS($user);
750
+
751
+        try {
752
+            $file = Filesystem::getPath($fileSource);
753
+        } catch (NotFoundException $e) {
754
+            $file = null;
755
+        }
756
+        $args = Filesystem::is_dir($file) ? ['dir' => $file] : ['dir' => dirname($file), 'scrollto' => $file];
757
+        $link = Util::linkToAbsolute('files', 'index.php', $args);
758
+
759
+        return [$file, $link];
760
+    }
761
+
762
+    /**
763
+     * check if we are the initiator or the owner of a re-share and return the correct UID
764
+     *
765
+     * @param IShare $share
766
+     * @return string
767
+     */
768
+    protected function getCorrectUid(IShare $share) {
769
+        if ($this->userManager->userExists($share->getShareOwner())) {
770
+            return $share->getShareOwner();
771
+        }
772
+
773
+        return $share->getSharedBy();
774
+    }
775
+
776
+
777
+
778
+    /**
779
+     * check if we got the right share
780
+     *
781
+     * @param IShare $share
782
+     * @param string $token
783
+     * @return bool
784
+     * @throws AuthenticationFailedException
785
+     */
786
+    protected function verifyShare(IShare $share, $token) {
787
+        if (
788
+            $share->getShareType() === IShare::TYPE_REMOTE &&
789
+            $share->getToken() === $token
790
+        ) {
791
+            return true;
792
+        }
793
+
794
+        if ($share->getShareType() === IShare::TYPE_CIRCLE) {
795
+            try {
796
+                $knownShare = $this->shareManager->getShareByToken($token);
797
+                if ($knownShare->getId() === $share->getId()) {
798
+                    return true;
799
+                }
800
+            } catch (ShareNotFound $e) {
801
+            }
802
+        }
803
+
804
+        throw new AuthenticationFailedException();
805
+    }
806
+
807
+
808
+
809
+    /**
810
+     * check if server-to-server sharing is enabled
811
+     *
812
+     * @param bool $incoming
813
+     * @return bool
814
+     */
815
+    private function isS2SEnabled($incoming = false) {
816
+        $result = $this->appManager->isEnabledForUser('files_sharing');
817
+
818
+        if ($incoming) {
819
+            $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
820
+        } else {
821
+            $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
822
+        }
823
+
824
+        return $result;
825
+    }
826
+
827
+
828
+    /**
829
+     * get the supported share types, e.g. "user", "group", etc.
830
+     *
831
+     * @return array
832
+     *
833
+     * @since 14.0.0
834
+     */
835
+    public function getSupportedShareTypes() {
836
+        return ['user', 'group'];
837
+    }
838 838
 }
Please login to merge, or discard this patch.
Spacing   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
 		// for backward compatibility make sure that the remote url stored in the
185 185
 		// database ends with a trailing slash
186 186
 		if (substr($remote, -1) !== '/') {
187
-			$remote = $remote . '/';
187
+			$remote = $remote.'/';
188 188
 		}
189 189
 
190 190
 		$token = $share->getShareSecret();
@@ -211,23 +211,23 @@  discard block
 block discarded – undo
211 211
 
212 212
 			// FIXME this should be a method in the user management instead
213 213
 			if ($shareType === IShare::TYPE_USER) {
214
-				$this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
214
+				$this->logger->debug('shareWith before, '.$shareWith, ['app' => 'files_sharing']);
215 215
 				Util::emitHook(
216 216
 					'\OCA\Files_Sharing\API\Server2Server',
217 217
 					'preLoginNameUsedAsUserName',
218 218
 					['uid' => &$shareWith]
219 219
 				);
220
-				$this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
220
+				$this->logger->debug('shareWith after, '.$shareWith, ['app' => 'files_sharing']);
221 221
 
222 222
 				if (!$this->userManager->userExists($shareWith)) {
223
-					throw new ProviderCouldNotAddShareException('User does not exists', '',Http::STATUS_BAD_REQUEST);
223
+					throw new ProviderCouldNotAddShareException('User does not exists', '', Http::STATUS_BAD_REQUEST);
224 224
 				}
225 225
 
226 226
 				\OC_Util::setupFS($shareWith);
227 227
 			}
228 228
 
229 229
 			if ($shareType === IShare::TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
230
-				throw new ProviderCouldNotAddShareException('Group does not exists', '',Http::STATUS_BAD_REQUEST);
230
+				throw new ProviderCouldNotAddShareException('Group does not exists', '', Http::STATUS_BAD_REQUEST);
231 231
 			}
232 232
 
233 233
 			$externalManager = new \OCA\Files_Sharing\External\Manager(
@@ -246,7 +246,7 @@  discard block
 block discarded – undo
246 246
 			);
247 247
 
248 248
 			try {
249
-				$externalManager->addShare($remote, $token, '', $name, $owner, $shareType,false, $shareWith, $remoteId);
249
+				$externalManager->addShare($remote, $token, '', $name, $owner, $shareType, false, $shareWith, $remoteId);
250 250
 				$shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
251 251
 
252 252
 				if ($shareType === IShare::TYPE_USER) {
@@ -255,7 +255,7 @@  discard block
 block discarded – undo
255 255
 						->setType('remote_share')
256 256
 						->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
257 257
 						->setAffectedUser($shareWith)
258
-						->setObject('remote_share', (int)$shareId, $name);
258
+						->setObject('remote_share', (int) $shareId, $name);
259 259
 					\OC::$server->getActivityManager()->publish($event);
260 260
 					$this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name);
261 261
 				} else {
@@ -266,7 +266,7 @@  discard block
 block discarded – undo
266 266
 							->setType('remote_share')
267 267
 							->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
268 268
 							->setAffectedUser($user->getUID())
269
-							->setObject('remote_share', (int)$shareId, $name);
269
+							->setObject('remote_share', (int) $shareId, $name);
270 270
 						\OC::$server->getActivityManager()->publish($event);
271 271
 						$this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name);
272 272
 					}
@@ -278,7 +278,7 @@  discard block
 block discarded – undo
278 278
 					'level' => ILogger::ERROR,
279 279
 					'app' => 'files_sharing'
280 280
 				]);
281
-				throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from ' . $remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
281
+				throw new ProviderCouldNotAddShareException('internal server error, was not able to add share from '.$remote, '', HTTP::STATUS_INTERNAL_SERVER_ERROR);
282 282
 			}
283 283
 		}
284 284
 
@@ -344,12 +344,12 @@  discard block
 block discarded – undo
344 344
 
345 345
 		$declineAction = $notification->createAction();
346 346
 		$declineAction->setLabel('decline')
347
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
347
+			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/'.$shareId)), 'DELETE');
348 348
 		$notification->addAction($declineAction);
349 349
 
350 350
 		$acceptAction = $notification->createAction();
351 351
 		$acceptAction->setLabel('accept')
352
-			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
352
+			->setLink($this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/'.$shareId)), 'POST');
353 353
 		$notification->addAction($acceptAction);
354 354
 
355 355
 		$this->notificationManager->notify($notification);
@@ -407,7 +407,7 @@  discard block
 block discarded – undo
407 407
 	 */
408 408
 	protected function executeAcceptShare(IShare $share) {
409 409
 		try {
410
-			$fileId = (int)$share->getNode()->getId();
410
+			$fileId = (int) $share->getNode()->getId();
411 411
 			list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
412 412
 		} catch (\Exception $e) {
413 413
 			throw new ShareNotFound();
@@ -483,7 +483,7 @@  discard block
 block discarded – undo
483 483
 		$this->federatedShareProvider->removeShareFromTable($share);
484 484
 
485 485
 		try {
486
-			$fileId = (int)$share->getNode()->getId();
486
+			$fileId = (int) $share->getNode()->getId();
487 487
 			list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
488 488
 		} catch (\Exception $e) {
489 489
 			throw new ShareNotFound();
@@ -575,10 +575,10 @@  discard block
 block discarded – undo
575 575
 			// delete all child in case of a group share
576 576
 			$qb = $this->connection->getQueryBuilder();
577 577
 			$qb->delete('share_external')
578
-				->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
578
+				->where($qb->expr()->eq('parent', $qb->createNamedParameter((int) $share['id'])));
579 579
 			$qb->execute();
580 580
 
581
-			if ((int)$share['share_type'] === IShare::TYPE_USER) {
581
+			if ((int) $share['share_type'] === IShare::TYPE_USER) {
582 582
 				if ($share['accepted']) {
583 583
 					$path = trim($mountpoint, '/');
584 584
 				} else {
@@ -587,7 +587,7 @@  discard block
 block discarded – undo
587 587
 				$notification = $this->notificationManager->createNotification();
588 588
 				$notification->setApp('files_sharing')
589 589
 					->setUser($share['user'])
590
-					->setObject('remote_share', (int)$share['id']);
590
+					->setObject('remote_share', (int) $share['id']);
591 591
 				$this->notificationManager->markProcessed($notification);
592 592
 
593 593
 				$event = $this->activityManager->generateEvent();
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
 					->setType('remote_share')
596 596
 					->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
597 597
 					->setAffectedUser($user)
598
-					->setObject('remote_share', (int)$share['id'], $path);
598
+					->setObject('remote_share', (int) $share['id'], $path);
599 599
 				\OC::$server->getActivityManager()->publish($event);
600 600
 			}
601 601
 		}
@@ -643,7 +643,7 @@  discard block
 block discarded – undo
643 643
 			$owner = $share->getShareOwner();
644 644
 			$currentServer = $this->addressHandler->generateRemoteURL();
645 645
 			if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
646
-				throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: ' . $id);
646
+				throw new ProviderCouldNotAddShareException('Resharing back to the owner is not allowed: '.$id);
647 647
 			}
648 648
 		} catch (\Exception $e) {
649 649
 			throw new ProviderCouldNotAddShareException($e->getMessage());
@@ -657,10 +657,10 @@  discard block
 block discarded – undo
657 657
 			$share->setSharedBy($share->getSharedWith());
658 658
 			$share->setSharedWith($shareWith);
659 659
 			$result = $this->federatedShareProvider->create($share);
660
-			$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
660
+			$this->federatedShareProvider->storeRemoteId((int) $result->getId(), $senderId);
661 661
 			return ['token' => $result->getToken(), 'providerId' => $result->getId()];
662 662
 		} else {
663
-			throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
663
+			throw new ProviderCouldNotAddShareException('resharing not allowed for share: '.$id);
664 664
 		}
665 665
 	}
666 666
 
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/Notifier.php 2 patches
Indentation   +206 added lines, -206 removed lines patch added patch discarded remove patch
@@ -36,237 +36,237 @@
 block discarded – undo
36 36
 use OCP\Notification\INotifier;
37 37
 
38 38
 class Notifier implements INotifier {
39
-	/** @var IFactory */
40
-	protected $factory;
41
-	/** @var IManager */
42
-	protected $contactsManager;
43
-	/** @var IURLGenerator */
44
-	protected $url;
45
-	/** @var array */
46
-	protected $federatedContacts;
47
-	/** @var ICloudIdManager */
48
-	protected $cloudIdManager;
39
+    /** @var IFactory */
40
+    protected $factory;
41
+    /** @var IManager */
42
+    protected $contactsManager;
43
+    /** @var IURLGenerator */
44
+    protected $url;
45
+    /** @var array */
46
+    protected $federatedContacts;
47
+    /** @var ICloudIdManager */
48
+    protected $cloudIdManager;
49 49
 
50
-	/**
51
-	 * @param IFactory $factory
52
-	 * @param IManager $contactsManager
53
-	 * @param IURLGenerator $url
54
-	 * @param ICloudIdManager $cloudIdManager
55
-	 */
56
-	public function __construct(IFactory $factory, IManager $contactsManager, IURLGenerator $url, ICloudIdManager $cloudIdManager) {
57
-		$this->factory = $factory;
58
-		$this->contactsManager = $contactsManager;
59
-		$this->url = $url;
60
-		$this->cloudIdManager = $cloudIdManager;
61
-	}
50
+    /**
51
+     * @param IFactory $factory
52
+     * @param IManager $contactsManager
53
+     * @param IURLGenerator $url
54
+     * @param ICloudIdManager $cloudIdManager
55
+     */
56
+    public function __construct(IFactory $factory, IManager $contactsManager, IURLGenerator $url, ICloudIdManager $cloudIdManager) {
57
+        $this->factory = $factory;
58
+        $this->contactsManager = $contactsManager;
59
+        $this->url = $url;
60
+        $this->cloudIdManager = $cloudIdManager;
61
+    }
62 62
 
63
-	/**
64
-	 * Identifier of the notifier, only use [a-z0-9_]
65
-	 *
66
-	 * @return string
67
-	 * @since 17.0.0
68
-	 */
69
-	public function getID(): string {
70
-		return 'federatedfilesharing';
71
-	}
63
+    /**
64
+     * Identifier of the notifier, only use [a-z0-9_]
65
+     *
66
+     * @return string
67
+     * @since 17.0.0
68
+     */
69
+    public function getID(): string {
70
+        return 'federatedfilesharing';
71
+    }
72 72
 
73
-	/**
74
-	 * Human readable name describing the notifier
75
-	 *
76
-	 * @return string
77
-	 * @since 17.0.0
78
-	 */
79
-	public function getName(): string {
80
-		return $this->factory->get('federatedfilesharing')->t('Federated sharing');
81
-	}
73
+    /**
74
+     * Human readable name describing the notifier
75
+     *
76
+     * @return string
77
+     * @since 17.0.0
78
+     */
79
+    public function getName(): string {
80
+        return $this->factory->get('federatedfilesharing')->t('Federated sharing');
81
+    }
82 82
 
83
-	/**
84
-	 * @param INotification $notification
85
-	 * @param string $languageCode The code of the language that should be used to prepare the notification
86
-	 * @return INotification
87
-	 * @throws \InvalidArgumentException
88
-	 */
89
-	public function prepare(INotification $notification, string $languageCode): INotification {
90
-		if ($notification->getApp() !== 'files_sharing' || $notification->getObjectType() !== 'remote_share') {
91
-			// Not my app => throw
92
-			throw new \InvalidArgumentException();
93
-		}
83
+    /**
84
+     * @param INotification $notification
85
+     * @param string $languageCode The code of the language that should be used to prepare the notification
86
+     * @return INotification
87
+     * @throws \InvalidArgumentException
88
+     */
89
+    public function prepare(INotification $notification, string $languageCode): INotification {
90
+        if ($notification->getApp() !== 'files_sharing' || $notification->getObjectType() !== 'remote_share') {
91
+            // Not my app => throw
92
+            throw new \InvalidArgumentException();
93
+        }
94 94
 
95
-		// Read the language from the notification
96
-		$l = $this->factory->get('files_sharing', $languageCode);
95
+        // Read the language from the notification
96
+        $l = $this->factory->get('files_sharing', $languageCode);
97 97
 
98
-		switch ($notification->getSubject()) {
99
-			// Deal with known subjects
100
-			case 'remote_share':
101
-				$notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
98
+        switch ($notification->getSubject()) {
99
+            // Deal with known subjects
100
+            case 'remote_share':
101
+                $notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/share.svg')));
102 102
 
103
-				$params = $notification->getSubjectParameters();
104
-				if ($params[0] !== $params[1] && $params[1] !== null) {
105
-					$remoteInitiator = $this->createRemoteUser($params[0]);
106
-					$remoteOwner = $this->createRemoteUser($params[1]);
107
-					$params[3] = $remoteInitiator['name'] . '@' . $remoteInitiator['server'];
108
-					$params[4] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
103
+                $params = $notification->getSubjectParameters();
104
+                if ($params[0] !== $params[1] && $params[1] !== null) {
105
+                    $remoteInitiator = $this->createRemoteUser($params[0]);
106
+                    $remoteOwner = $this->createRemoteUser($params[1]);
107
+                    $params[3] = $remoteInitiator['name'] . '@' . $remoteInitiator['server'];
108
+                    $params[4] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
109 109
 
110
-					$notification->setParsedSubject(
111
-						$l->t('You received "%3$s" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))', $params)
112
-					);
110
+                    $notification->setParsedSubject(
111
+                        $l->t('You received "%3$s" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))', $params)
112
+                    );
113 113
 
114
-					$notification->setRichSubject(
115
-						$l->t('You received {share} as a remote share from {user} (on behalf of {behalf})'),
116
-						[
117
-							'share' => [
118
-								'type' => 'pending-federated-share',
119
-								'id' => $notification->getObjectId(),
120
-								'name' => $params[2],
121
-							],
122
-							'user' => $remoteInitiator,
123
-							'behalf' => $remoteOwner,
124
-						]
125
-					);
126
-				} else {
127
-					$remoteOwner = $this->createRemoteUser($params[0]);
128
-					$params[3] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
114
+                    $notification->setRichSubject(
115
+                        $l->t('You received {share} as a remote share from {user} (on behalf of {behalf})'),
116
+                        [
117
+                            'share' => [
118
+                                'type' => 'pending-federated-share',
119
+                                'id' => $notification->getObjectId(),
120
+                                'name' => $params[2],
121
+                            ],
122
+                            'user' => $remoteInitiator,
123
+                            'behalf' => $remoteOwner,
124
+                        ]
125
+                    );
126
+                } else {
127
+                    $remoteOwner = $this->createRemoteUser($params[0]);
128
+                    $params[3] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
129 129
 
130
-					$notification->setParsedSubject(
131
-						$l->t('You received "%3$s" as a remote share from %4$s (%1$s)', $params)
132
-					);
130
+                    $notification->setParsedSubject(
131
+                        $l->t('You received "%3$s" as a remote share from %4$s (%1$s)', $params)
132
+                    );
133 133
 
134 134
 
135
-					$notification->setRichSubject(
136
-						$l->t('You received {share} as a remote share from {user}'),
137
-						[
138
-							'share' => [
139
-								'type' => 'pending-federated-share',
140
-								'id' => $notification->getObjectId(),
141
-								'name' => $params[2],
142
-							],
143
-							'user' => $remoteOwner,
144
-						]
145
-					);
146
-				}
135
+                    $notification->setRichSubject(
136
+                        $l->t('You received {share} as a remote share from {user}'),
137
+                        [
138
+                            'share' => [
139
+                                'type' => 'pending-federated-share',
140
+                                'id' => $notification->getObjectId(),
141
+                                'name' => $params[2],
142
+                            ],
143
+                            'user' => $remoteOwner,
144
+                        ]
145
+                    );
146
+                }
147 147
 
148
-				// Deal with the actions for a known subject
149
-				foreach ($notification->getActions() as $action) {
150
-					switch ($action->getLabel()) {
151
-						case 'accept':
152
-							$action->setParsedLabel(
153
-								(string)$l->t('Accept')
154
-							)
155
-								->setPrimary(true);
156
-							break;
148
+                // Deal with the actions for a known subject
149
+                foreach ($notification->getActions() as $action) {
150
+                    switch ($action->getLabel()) {
151
+                        case 'accept':
152
+                            $action->setParsedLabel(
153
+                                (string)$l->t('Accept')
154
+                            )
155
+                                ->setPrimary(true);
156
+                            break;
157 157
 
158
-						case 'decline':
159
-							$action->setParsedLabel(
160
-								(string)$l->t('Decline')
161
-							);
162
-							break;
163
-					}
158
+                        case 'decline':
159
+                            $action->setParsedLabel(
160
+                                (string)$l->t('Decline')
161
+                            );
162
+                            break;
163
+                    }
164 164
 
165
-					$notification->addParsedAction($action);
166
-				}
167
-				return $notification;
165
+                    $notification->addParsedAction($action);
166
+                }
167
+                return $notification;
168 168
 
169
-			default:
170
-				// Unknown subject => Unknown notification => throw
171
-				throw new \InvalidArgumentException();
172
-		}
173
-	}
169
+            default:
170
+                // Unknown subject => Unknown notification => throw
171
+                throw new \InvalidArgumentException();
172
+        }
173
+    }
174 174
 
175
-	/**
176
-	 * @param string $cloudId
177
-	 * @return array
178
-	 */
179
-	protected function createRemoteUser($cloudId, $displayName = null) {
180
-		try {
181
-			$resolvedId = $this->cloudIdManager->resolveCloudId($cloudId);
182
-			if ($displayName === null) {
183
-				$displayName = $this->getDisplayName($resolvedId);
184
-			}
185
-			$user = $resolvedId->getUser();
186
-			$server = $resolvedId->getRemote();
187
-		} catch (HintException $e) {
188
-			$user = $cloudId;
189
-			$displayName = $cloudId;
190
-			$server = '';
191
-		}
175
+    /**
176
+     * @param string $cloudId
177
+     * @return array
178
+     */
179
+    protected function createRemoteUser($cloudId, $displayName = null) {
180
+        try {
181
+            $resolvedId = $this->cloudIdManager->resolveCloudId($cloudId);
182
+            if ($displayName === null) {
183
+                $displayName = $this->getDisplayName($resolvedId);
184
+            }
185
+            $user = $resolvedId->getUser();
186
+            $server = $resolvedId->getRemote();
187
+        } catch (HintException $e) {
188
+            $user = $cloudId;
189
+            $displayName = $cloudId;
190
+            $server = '';
191
+        }
192 192
 
193
-		return [
194
-			'type' => 'user',
195
-			'id' => $user,
196
-			'name' => $displayName,
197
-			'server' => $server,
198
-		];
199
-	}
193
+        return [
194
+            'type' => 'user',
195
+            'id' => $user,
196
+            'name' => $displayName,
197
+            'server' => $server,
198
+        ];
199
+    }
200 200
 
201
-	/**
202
-	 * Try to find the user in the contacts
203
-	 *
204
-	 * @param ICloudId $cloudId
205
-	 * @return string
206
-	 */
207
-	protected function getDisplayName(ICloudId $cloudId): string {
208
-		$server = $cloudId->getRemote();
209
-		$user = $cloudId->getUser();
210
-		if (strpos($server, 'http://') === 0) {
211
-			$server = substr($server, strlen('http://'));
212
-		} elseif (strpos($server, 'https://') === 0) {
213
-			$server = substr($server, strlen('https://'));
214
-		}
201
+    /**
202
+     * Try to find the user in the contacts
203
+     *
204
+     * @param ICloudId $cloudId
205
+     * @return string
206
+     */
207
+    protected function getDisplayName(ICloudId $cloudId): string {
208
+        $server = $cloudId->getRemote();
209
+        $user = $cloudId->getUser();
210
+        if (strpos($server, 'http://') === 0) {
211
+            $server = substr($server, strlen('http://'));
212
+        } elseif (strpos($server, 'https://') === 0) {
213
+            $server = substr($server, strlen('https://'));
214
+        }
215 215
 
216
-		try {
217
-			// contains protocol in the  ID
218
-			return $this->getDisplayNameFromContact($cloudId->getId());
219
-		} catch (\OutOfBoundsException $e) {
220
-		}
216
+        try {
217
+            // contains protocol in the  ID
218
+            return $this->getDisplayNameFromContact($cloudId->getId());
219
+        } catch (\OutOfBoundsException $e) {
220
+        }
221 221
 
222
-		try {
223
-			// does not include protocol, as stored in addressbooks
224
-			return $this->getDisplayNameFromContact($cloudId->getDisplayId());
225
-		} catch (\OutOfBoundsException $e) {
226
-		}
222
+        try {
223
+            // does not include protocol, as stored in addressbooks
224
+            return $this->getDisplayNameFromContact($cloudId->getDisplayId());
225
+        } catch (\OutOfBoundsException $e) {
226
+        }
227 227
 
228
-		try {
229
-			return $this->getDisplayNameFromContact($user . '@http://' . $server);
230
-		} catch (\OutOfBoundsException $e) {
231
-		}
228
+        try {
229
+            return $this->getDisplayNameFromContact($user . '@http://' . $server);
230
+        } catch (\OutOfBoundsException $e) {
231
+        }
232 232
 
233
-		try {
234
-			return $this->getDisplayNameFromContact($user . '@https://' . $server);
235
-		} catch (\OutOfBoundsException $e) {
236
-		}
233
+        try {
234
+            return $this->getDisplayNameFromContact($user . '@https://' . $server);
235
+        } catch (\OutOfBoundsException $e) {
236
+        }
237 237
 
238
-		return $cloudId->getId();
239
-	}
238
+        return $cloudId->getId();
239
+    }
240 240
 
241
-	/**
242
-	 * Try to find the user in the contacts
243
-	 *
244
-	 * @param string $federatedCloudId
245
-	 * @return string
246
-	 * @throws \OutOfBoundsException when there is no contact for the id
247
-	 */
248
-	protected function getDisplayNameFromContact($federatedCloudId) {
249
-		if (isset($this->federatedContacts[$federatedCloudId])) {
250
-			if ($this->federatedContacts[$federatedCloudId] !== '') {
251
-				return $this->federatedContacts[$federatedCloudId];
252
-			} else {
253
-				throw new \OutOfBoundsException('No contact found for federated cloud id');
254
-			}
255
-		}
241
+    /**
242
+     * Try to find the user in the contacts
243
+     *
244
+     * @param string $federatedCloudId
245
+     * @return string
246
+     * @throws \OutOfBoundsException when there is no contact for the id
247
+     */
248
+    protected function getDisplayNameFromContact($federatedCloudId) {
249
+        if (isset($this->federatedContacts[$federatedCloudId])) {
250
+            if ($this->federatedContacts[$federatedCloudId] !== '') {
251
+                return $this->federatedContacts[$federatedCloudId];
252
+            } else {
253
+                throw new \OutOfBoundsException('No contact found for federated cloud id');
254
+            }
255
+        }
256 256
 
257
-		$addressBookEntries = $this->contactsManager->search($federatedCloudId, ['CLOUD']);
258
-		foreach ($addressBookEntries as $entry) {
259
-			if (isset($entry['CLOUD'])) {
260
-				foreach ($entry['CLOUD'] as $cloudID) {
261
-					if ($cloudID === $federatedCloudId) {
262
-						$this->federatedContacts[$federatedCloudId] = $entry['FN'];
263
-						return $entry['FN'];
264
-					}
265
-				}
266
-			}
267
-		}
257
+        $addressBookEntries = $this->contactsManager->search($federatedCloudId, ['CLOUD']);
258
+        foreach ($addressBookEntries as $entry) {
259
+            if (isset($entry['CLOUD'])) {
260
+                foreach ($entry['CLOUD'] as $cloudID) {
261
+                    if ($cloudID === $federatedCloudId) {
262
+                        $this->federatedContacts[$federatedCloudId] = $entry['FN'];
263
+                        return $entry['FN'];
264
+                    }
265
+                }
266
+            }
267
+        }
268 268
 
269
-		$this->federatedContacts[$federatedCloudId] = '';
270
-		throw new \OutOfBoundsException('No contact found for federated cloud id');
271
-	}
269
+        $this->federatedContacts[$federatedCloudId] = '';
270
+        throw new \OutOfBoundsException('No contact found for federated cloud id');
271
+    }
272 272
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -104,8 +104,8 @@  discard block
 block discarded – undo
104 104
 				if ($params[0] !== $params[1] && $params[1] !== null) {
105 105
 					$remoteInitiator = $this->createRemoteUser($params[0]);
106 106
 					$remoteOwner = $this->createRemoteUser($params[1]);
107
-					$params[3] = $remoteInitiator['name'] . '@' . $remoteInitiator['server'];
108
-					$params[4] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
107
+					$params[3] = $remoteInitiator['name'].'@'.$remoteInitiator['server'];
108
+					$params[4] = $remoteOwner['name'].'@'.$remoteOwner['server'];
109 109
 
110 110
 					$notification->setParsedSubject(
111 111
 						$l->t('You received "%3$s" as a remote share from %4$s (%1$s) (on behalf of %5$s (%2$s))', $params)
@@ -125,7 +125,7 @@  discard block
 block discarded – undo
125 125
 					);
126 126
 				} else {
127 127
 					$remoteOwner = $this->createRemoteUser($params[0]);
128
-					$params[3] = $remoteOwner['name'] . '@' . $remoteOwner['server'];
128
+					$params[3] = $remoteOwner['name'].'@'.$remoteOwner['server'];
129 129
 
130 130
 					$notification->setParsedSubject(
131 131
 						$l->t('You received "%3$s" as a remote share from %4$s (%1$s)', $params)
@@ -150,14 +150,14 @@  discard block
 block discarded – undo
150 150
 					switch ($action->getLabel()) {
151 151
 						case 'accept':
152 152
 							$action->setParsedLabel(
153
-								(string)$l->t('Accept')
153
+								(string) $l->t('Accept')
154 154
 							)
155 155
 								->setPrimary(true);
156 156
 							break;
157 157
 
158 158
 						case 'decline':
159 159
 							$action->setParsedLabel(
160
-								(string)$l->t('Decline')
160
+								(string) $l->t('Decline')
161 161
 							);
162 162
 							break;
163 163
 					}
@@ -226,12 +226,12 @@  discard block
 block discarded – undo
226 226
 		}
227 227
 
228 228
 		try {
229
-			return $this->getDisplayNameFromContact($user . '@http://' . $server);
229
+			return $this->getDisplayNameFromContact($user.'@http://'.$server);
230 230
 		} catch (\OutOfBoundsException $e) {
231 231
 		}
232 232
 
233 233
 		try {
234
-			return $this->getDisplayNameFromContact($user . '@https://' . $server);
234
+			return $this->getDisplayNameFromContact($user.'@https://'.$server);
235 235
 		} catch (\OutOfBoundsException $e) {
236 236
 		}
237 237
 
Please login to merge, or discard this patch.