Passed
Push — master ( f8c519...0ac456 )
by Blizzz
13:02 queued 11s
created
apps/dav/lib/CalDAV/CalDavBackend.php 2 patches
Indentation   +2722 added lines, -2722 removed lines patch added patch discarded remove patch
@@ -96,2726 +96,2726 @@
 block discarded – undo
96 96
  * @package OCA\DAV\CalDAV
97 97
  */
98 98
 class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
99
-	public const CALENDAR_TYPE_CALENDAR = 0;
100
-	public const CALENDAR_TYPE_SUBSCRIPTION = 1;
101
-
102
-	public const PERSONAL_CALENDAR_URI = 'personal';
103
-	public const PERSONAL_CALENDAR_NAME = 'Personal';
104
-
105
-	public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
106
-	public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
107
-
108
-	/**
109
-	 * We need to specify a max date, because we need to stop *somewhere*
110
-	 *
111
-	 * On 32 bit system the maximum for a signed integer is 2147483647, so
112
-	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
113
-	 * in 2038-01-19 to avoid problems when the date is converted
114
-	 * to a unix timestamp.
115
-	 */
116
-	public const MAX_DATE = '2038-01-01';
117
-
118
-	public const ACCESS_PUBLIC = 4;
119
-	public const CLASSIFICATION_PUBLIC = 0;
120
-	public const CLASSIFICATION_PRIVATE = 1;
121
-	public const CLASSIFICATION_CONFIDENTIAL = 2;
122
-
123
-	/**
124
-	 * List of CalDAV properties, and how they map to database field names
125
-	 * Add your own properties by simply adding on to this array.
126
-	 *
127
-	 * Note that only string-based properties are supported here.
128
-	 *
129
-	 * @var array
130
-	 */
131
-	public $propertyMap = [
132
-		'{DAV:}displayname' => 'displayname',
133
-		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
134
-		'{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
135
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
136
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
137
-	];
138
-
139
-	/**
140
-	 * List of subscription properties, and how they map to database field names.
141
-	 *
142
-	 * @var array
143
-	 */
144
-	public $subscriptionPropertyMap = [
145
-		'{DAV:}displayname' => 'displayname',
146
-		'{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
147
-		'{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
148
-		'{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
149
-		'{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
150
-		'{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
151
-		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
152
-	];
153
-
154
-	/** @var array properties to index */
155
-	public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
156
-		'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
157
-		'ORGANIZER'];
158
-
159
-	/** @var array parameters to index */
160
-	public static $indexParameters = [
161
-		'ATTENDEE' => ['CN'],
162
-		'ORGANIZER' => ['CN'],
163
-	];
164
-
165
-	/**
166
-	 * @var string[] Map of uid => display name
167
-	 */
168
-	protected $userDisplayNames;
169
-
170
-	/** @var IDBConnection */
171
-	private $db;
172
-
173
-	/** @var Backend */
174
-	private $calendarSharingBackend;
175
-
176
-	/** @var Principal */
177
-	private $principalBackend;
178
-
179
-	/** @var IUserManager */
180
-	private $userManager;
181
-
182
-	/** @var ISecureRandom */
183
-	private $random;
184
-
185
-	/** @var ILogger */
186
-	private $logger;
187
-
188
-	/** @var IEventDispatcher */
189
-	private $dispatcher;
190
-
191
-	/** @var EventDispatcherInterface */
192
-	private $legacyDispatcher;
193
-
194
-	/** @var bool */
195
-	private $legacyEndpoint;
196
-
197
-	/** @var string */
198
-	private $dbObjectPropertiesTable = 'calendarobjects_props';
199
-
200
-	/**
201
-	 * CalDavBackend constructor.
202
-	 *
203
-	 * @param IDBConnection $db
204
-	 * @param Principal $principalBackend
205
-	 * @param IUserManager $userManager
206
-	 * @param IGroupManager $groupManager
207
-	 * @param ISecureRandom $random
208
-	 * @param ILogger $logger
209
-	 * @param IEventDispatcher $dispatcher
210
-	 * @param EventDispatcherInterface $legacyDispatcher
211
-	 * @param bool $legacyEndpoint
212
-	 */
213
-	public function __construct(IDBConnection $db,
214
-								Principal $principalBackend,
215
-								IUserManager $userManager,
216
-								IGroupManager $groupManager,
217
-								ISecureRandom $random,
218
-								ILogger $logger,
219
-								IEventDispatcher $dispatcher,
220
-								EventDispatcherInterface $legacyDispatcher,
221
-								bool $legacyEndpoint = false) {
222
-		$this->db = $db;
223
-		$this->principalBackend = $principalBackend;
224
-		$this->userManager = $userManager;
225
-		$this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
226
-		$this->random = $random;
227
-		$this->logger = $logger;
228
-		$this->dispatcher = $dispatcher;
229
-		$this->legacyDispatcher = $legacyDispatcher;
230
-		$this->legacyEndpoint = $legacyEndpoint;
231
-	}
232
-
233
-	/**
234
-	 * Return the number of calendars for a principal
235
-	 *
236
-	 * By default this excludes the automatically generated birthday calendar
237
-	 *
238
-	 * @param $principalUri
239
-	 * @param bool $excludeBirthday
240
-	 * @return int
241
-	 */
242
-	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
243
-		$principalUri = $this->convertPrincipal($principalUri, true);
244
-		$query = $this->db->getQueryBuilder();
245
-		$query->select($query->func()->count('*'))
246
-			->from('calendars');
247
-
248
-		if ($principalUri === '') {
249
-			$query->where($query->expr()->emptyString('principaluri'));
250
-		} else {
251
-			$query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
252
-		}
253
-
254
-		if ($excludeBirthday) {
255
-			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
256
-		}
257
-
258
-		$result = $query->executeQuery();
259
-		$column = (int)$result->fetchOne();
260
-		$result->closeCursor();
261
-		return $column;
262
-	}
263
-
264
-	/**
265
-	 * Returns a list of calendars for a principal.
266
-	 *
267
-	 * Every project is an array with the following keys:
268
-	 *  * id, a unique id that will be used by other functions to modify the
269
-	 *    calendar. This can be the same as the uri or a database key.
270
-	 *  * uri, which the basename of the uri with which the calendar is
271
-	 *    accessed.
272
-	 *  * principaluri. The owner of the calendar. Almost always the same as
273
-	 *    principalUri passed to this method.
274
-	 *
275
-	 * Furthermore it can contain webdav properties in clark notation. A very
276
-	 * common one is '{DAV:}displayname'.
277
-	 *
278
-	 * Many clients also require:
279
-	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
280
-	 * For this property, you can just return an instance of
281
-	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
282
-	 *
283
-	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
284
-	 * ACL will automatically be put in read-only mode.
285
-	 *
286
-	 * @param string $principalUri
287
-	 * @return array
288
-	 */
289
-	public function getCalendarsForUser($principalUri) {
290
-		$principalUriOriginal = $principalUri;
291
-		$principalUri = $this->convertPrincipal($principalUri, true);
292
-		$fields = array_values($this->propertyMap);
293
-		$fields[] = 'id';
294
-		$fields[] = 'uri';
295
-		$fields[] = 'synctoken';
296
-		$fields[] = 'components';
297
-		$fields[] = 'principaluri';
298
-		$fields[] = 'transparent';
299
-
300
-		// Making fields a comma-delimited list
301
-		$query = $this->db->getQueryBuilder();
302
-		$query->select($fields)
303
-			->from('calendars')
304
-			->orderBy('calendarorder', 'ASC');
305
-
306
-		if ($principalUri === '') {
307
-			$query->where($query->expr()->emptyString('principaluri'));
308
-		} else {
309
-			$query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
310
-		}
311
-
312
-		$result = $query->executeQuery();
313
-
314
-		$calendars = [];
315
-		while ($row = $result->fetch()) {
316
-			$row['principaluri'] = (string) $row['principaluri'];
317
-			$components = [];
318
-			if ($row['components']) {
319
-				$components = explode(',',$row['components']);
320
-			}
321
-
322
-			$calendar = [
323
-				'id' => $row['id'],
324
-				'uri' => $row['uri'],
325
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
326
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
327
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
328
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
330
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
331
-			];
332
-
333
-			foreach ($this->propertyMap as $xmlName => $dbName) {
334
-				$calendar[$xmlName] = $row[$dbName];
335
-			}
336
-
337
-			$this->addOwnerPrincipal($calendar);
338
-
339
-			if (!isset($calendars[$calendar['id']])) {
340
-				$calendars[$calendar['id']] = $calendar;
341
-			}
342
-		}
343
-		$result->closeCursor();
344
-
345
-		// query for shared calendars
346
-		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
347
-		$principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
348
-
349
-		$principals[] = $principalUri;
350
-
351
-		$fields = array_values($this->propertyMap);
352
-		$fields[] = 'a.id';
353
-		$fields[] = 'a.uri';
354
-		$fields[] = 'a.synctoken';
355
-		$fields[] = 'a.components';
356
-		$fields[] = 'a.principaluri';
357
-		$fields[] = 'a.transparent';
358
-		$fields[] = 's.access';
359
-		$query = $this->db->getQueryBuilder();
360
-		$query->select($fields)
361
-			->from('dav_shares', 's')
362
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
363
-			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
364
-			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
365
-			->setParameter('type', 'calendar')
366
-			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
367
-
368
-		$result = $query->executeQuery();
369
-
370
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
371
-		while ($row = $result->fetch()) {
372
-			$row['principaluri'] = (string) $row['principaluri'];
373
-			if ($row['principaluri'] === $principalUri) {
374
-				continue;
375
-			}
376
-
377
-			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
378
-			if (isset($calendars[$row['id']])) {
379
-				if ($readOnly) {
380
-					// New share can not have more permissions then the old one.
381
-					continue;
382
-				}
383
-				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
384
-					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
385
-					// Old share is already read-write, no more permissions can be gained
386
-					continue;
387
-				}
388
-			}
389
-
390
-			[, $name] = Uri\split($row['principaluri']);
391
-			$uri = $row['uri'] . '_shared_by_' . $name;
392
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
393
-			$components = [];
394
-			if ($row['components']) {
395
-				$components = explode(',',$row['components']);
396
-			}
397
-			$calendar = [
398
-				'id' => $row['id'],
399
-				'uri' => $uri,
400
-				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
401
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
402
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
403
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
406
-				$readOnlyPropertyName => $readOnly,
407
-			];
408
-
409
-			foreach ($this->propertyMap as $xmlName => $dbName) {
410
-				$calendar[$xmlName] = $row[$dbName];
411
-			}
412
-
413
-			$this->addOwnerPrincipal($calendar);
414
-
415
-			$calendars[$calendar['id']] = $calendar;
416
-		}
417
-		$result->closeCursor();
418
-
419
-		return array_values($calendars);
420
-	}
421
-
422
-	/**
423
-	 * @param $principalUri
424
-	 * @return array
425
-	 */
426
-	public function getUsersOwnCalendars($principalUri) {
427
-		$principalUri = $this->convertPrincipal($principalUri, true);
428
-		$fields = array_values($this->propertyMap);
429
-		$fields[] = 'id';
430
-		$fields[] = 'uri';
431
-		$fields[] = 'synctoken';
432
-		$fields[] = 'components';
433
-		$fields[] = 'principaluri';
434
-		$fields[] = 'transparent';
435
-		// Making fields a comma-delimited list
436
-		$query = $this->db->getQueryBuilder();
437
-		$query->select($fields)->from('calendars')
438
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
439
-			->orderBy('calendarorder', 'ASC');
440
-		$stmt = $query->executeQuery();
441
-		$calendars = [];
442
-		while ($row = $stmt->fetch()) {
443
-			$row['principaluri'] = (string) $row['principaluri'];
444
-			$components = [];
445
-			if ($row['components']) {
446
-				$components = explode(',',$row['components']);
447
-			}
448
-			$calendar = [
449
-				'id' => $row['id'],
450
-				'uri' => $row['uri'],
451
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
452
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
453
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
454
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
456
-			];
457
-			foreach ($this->propertyMap as $xmlName => $dbName) {
458
-				$calendar[$xmlName] = $row[$dbName];
459
-			}
460
-
461
-			$this->addOwnerPrincipal($calendar);
462
-
463
-			if (!isset($calendars[$calendar['id']])) {
464
-				$calendars[$calendar['id']] = $calendar;
465
-			}
466
-		}
467
-		$stmt->closeCursor();
468
-		return array_values($calendars);
469
-	}
470
-
471
-
472
-	/**
473
-	 * @param $uid
474
-	 * @return string
475
-	 */
476
-	private function getUserDisplayName($uid) {
477
-		if (!isset($this->userDisplayNames[$uid])) {
478
-			$user = $this->userManager->get($uid);
479
-
480
-			if ($user instanceof IUser) {
481
-				$this->userDisplayNames[$uid] = $user->getDisplayName();
482
-			} else {
483
-				$this->userDisplayNames[$uid] = $uid;
484
-			}
485
-		}
486
-
487
-		return $this->userDisplayNames[$uid];
488
-	}
489
-
490
-	/**
491
-	 * @return array
492
-	 */
493
-	public function getPublicCalendars() {
494
-		$fields = array_values($this->propertyMap);
495
-		$fields[] = 'a.id';
496
-		$fields[] = 'a.uri';
497
-		$fields[] = 'a.synctoken';
498
-		$fields[] = 'a.components';
499
-		$fields[] = 'a.principaluri';
500
-		$fields[] = 'a.transparent';
501
-		$fields[] = 's.access';
502
-		$fields[] = 's.publicuri';
503
-		$calendars = [];
504
-		$query = $this->db->getQueryBuilder();
505
-		$result = $query->select($fields)
506
-			->from('dav_shares', 's')
507
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
508
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
509
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
510
-			->executeQuery();
511
-
512
-		while ($row = $result->fetch()) {
513
-			$row['principaluri'] = (string) $row['principaluri'];
514
-			[, $name] = Uri\split($row['principaluri']);
515
-			$row['displayname'] = $row['displayname'] . "($name)";
516
-			$components = [];
517
-			if ($row['components']) {
518
-				$components = explode(',',$row['components']);
519
-			}
520
-			$calendar = [
521
-				'id' => $row['id'],
522
-				'uri' => $row['publicuri'],
523
-				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
525
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
526
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
528
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
530
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
531
-			];
532
-
533
-			foreach ($this->propertyMap as $xmlName => $dbName) {
534
-				$calendar[$xmlName] = $row[$dbName];
535
-			}
536
-
537
-			$this->addOwnerPrincipal($calendar);
538
-
539
-			if (!isset($calendars[$calendar['id']])) {
540
-				$calendars[$calendar['id']] = $calendar;
541
-			}
542
-		}
543
-		$result->closeCursor();
544
-
545
-		return array_values($calendars);
546
-	}
547
-
548
-	/**
549
-	 * @param string $uri
550
-	 * @return array
551
-	 * @throws NotFound
552
-	 */
553
-	public function getPublicCalendar($uri) {
554
-		$fields = array_values($this->propertyMap);
555
-		$fields[] = 'a.id';
556
-		$fields[] = 'a.uri';
557
-		$fields[] = 'a.synctoken';
558
-		$fields[] = 'a.components';
559
-		$fields[] = 'a.principaluri';
560
-		$fields[] = 'a.transparent';
561
-		$fields[] = 's.access';
562
-		$fields[] = 's.publicuri';
563
-		$query = $this->db->getQueryBuilder();
564
-		$result = $query->select($fields)
565
-			->from('dav_shares', 's')
566
-			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
567
-			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
568
-			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
569
-			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
570
-			->executeQuery();
571
-
572
-		$row = $result->fetch();
573
-
574
-		$result->closeCursor();
575
-
576
-		if ($row === false) {
577
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
578
-		}
579
-
580
-		$row['principaluri'] = (string) $row['principaluri'];
581
-		[, $name] = Uri\split($row['principaluri']);
582
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
583
-		$components = [];
584
-		if ($row['components']) {
585
-			$components = explode(',',$row['components']);
586
-		}
587
-		$calendar = [
588
-			'id' => $row['id'],
589
-			'uri' => $row['publicuri'],
590
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
591
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
592
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
593
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
595
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
597
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
598
-		];
599
-
600
-		foreach ($this->propertyMap as $xmlName => $dbName) {
601
-			$calendar[$xmlName] = $row[$dbName];
602
-		}
603
-
604
-		$this->addOwnerPrincipal($calendar);
605
-
606
-		return $calendar;
607
-	}
608
-
609
-	/**
610
-	 * @param string $principal
611
-	 * @param string $uri
612
-	 * @return array|null
613
-	 */
614
-	public function getCalendarByUri($principal, $uri) {
615
-		$fields = array_values($this->propertyMap);
616
-		$fields[] = 'id';
617
-		$fields[] = 'uri';
618
-		$fields[] = 'synctoken';
619
-		$fields[] = 'components';
620
-		$fields[] = 'principaluri';
621
-		$fields[] = 'transparent';
622
-
623
-		// Making fields a comma-delimited list
624
-		$query = $this->db->getQueryBuilder();
625
-		$query->select($fields)->from('calendars')
626
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
627
-			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
628
-			->setMaxResults(1);
629
-		$stmt = $query->executeQuery();
630
-
631
-		$row = $stmt->fetch();
632
-		$stmt->closeCursor();
633
-		if ($row === false) {
634
-			return null;
635
-		}
636
-
637
-		$row['principaluri'] = (string) $row['principaluri'];
638
-		$components = [];
639
-		if ($row['components']) {
640
-			$components = explode(',',$row['components']);
641
-		}
642
-
643
-		$calendar = [
644
-			'id' => $row['id'],
645
-			'uri' => $row['uri'],
646
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
647
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
648
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
649
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
651
-		];
652
-
653
-		foreach ($this->propertyMap as $xmlName => $dbName) {
654
-			$calendar[$xmlName] = $row[$dbName];
655
-		}
656
-
657
-		$this->addOwnerPrincipal($calendar);
658
-
659
-		return $calendar;
660
-	}
661
-
662
-	/**
663
-	 * @param $calendarId
664
-	 * @return array|null
665
-	 */
666
-	public function getCalendarById($calendarId) {
667
-		$fields = array_values($this->propertyMap);
668
-		$fields[] = 'id';
669
-		$fields[] = 'uri';
670
-		$fields[] = 'synctoken';
671
-		$fields[] = 'components';
672
-		$fields[] = 'principaluri';
673
-		$fields[] = 'transparent';
674
-
675
-		// Making fields a comma-delimited list
676
-		$query = $this->db->getQueryBuilder();
677
-		$query->select($fields)->from('calendars')
678
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
679
-			->setMaxResults(1);
680
-		$stmt = $query->executeQuery();
681
-
682
-		$row = $stmt->fetch();
683
-		$stmt->closeCursor();
684
-		if ($row === false) {
685
-			return null;
686
-		}
687
-
688
-		$row['principaluri'] = (string) $row['principaluri'];
689
-		$components = [];
690
-		if ($row['components']) {
691
-			$components = explode(',',$row['components']);
692
-		}
693
-
694
-		$calendar = [
695
-			'id' => $row['id'],
696
-			'uri' => $row['uri'],
697
-			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
698
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
699
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
700
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
702
-		];
703
-
704
-		foreach ($this->propertyMap as $xmlName => $dbName) {
705
-			$calendar[$xmlName] = $row[$dbName];
706
-		}
707
-
708
-		$this->addOwnerPrincipal($calendar);
709
-
710
-		return $calendar;
711
-	}
712
-
713
-	/**
714
-	 * @param $subscriptionId
715
-	 */
716
-	public function getSubscriptionById($subscriptionId) {
717
-		$fields = array_values($this->subscriptionPropertyMap);
718
-		$fields[] = 'id';
719
-		$fields[] = 'uri';
720
-		$fields[] = 'source';
721
-		$fields[] = 'synctoken';
722
-		$fields[] = 'principaluri';
723
-		$fields[] = 'lastmodified';
724
-
725
-		$query = $this->db->getQueryBuilder();
726
-		$query->select($fields)
727
-			->from('calendarsubscriptions')
728
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
729
-			->orderBy('calendarorder', 'asc');
730
-		$stmt = $query->executeQuery();
731
-
732
-		$row = $stmt->fetch();
733
-		$stmt->closeCursor();
734
-		if ($row === false) {
735
-			return null;
736
-		}
737
-
738
-		$row['principaluri'] = (string) $row['principaluri'];
739
-		$subscription = [
740
-			'id' => $row['id'],
741
-			'uri' => $row['uri'],
742
-			'principaluri' => $row['principaluri'],
743
-			'source' => $row['source'],
744
-			'lastmodified' => $row['lastmodified'],
745
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
747
-		];
748
-
749
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
750
-			if (!is_null($row[$dbName])) {
751
-				$subscription[$xmlName] = $row[$dbName];
752
-			}
753
-		}
754
-
755
-		return $subscription;
756
-	}
757
-
758
-	/**
759
-	 * Creates a new calendar for a principal.
760
-	 *
761
-	 * If the creation was a success, an id must be returned that can be used to reference
762
-	 * this calendar in other methods, such as updateCalendar.
763
-	 *
764
-	 * @param string $principalUri
765
-	 * @param string $calendarUri
766
-	 * @param array $properties
767
-	 * @return int
768
-	 */
769
-	public function createCalendar($principalUri, $calendarUri, array $properties) {
770
-		$values = [
771
-			'principaluri' => $this->convertPrincipal($principalUri, true),
772
-			'uri' => $calendarUri,
773
-			'synctoken' => 1,
774
-			'transparent' => 0,
775
-			'components' => 'VEVENT,VTODO',
776
-			'displayname' => $calendarUri
777
-		];
778
-
779
-		// Default value
780
-		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
781
-		if (isset($properties[$sccs])) {
782
-			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
783
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
784
-			}
785
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
786
-		} elseif (isset($properties['components'])) {
787
-			// Allow to provide components internally without having
788
-			// to create a SupportedCalendarComponentSet object
789
-			$values['components'] = $properties['components'];
790
-		}
791
-
792
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
793
-		if (isset($properties[$transp])) {
794
-			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
795
-		}
796
-
797
-		foreach ($this->propertyMap as $xmlName => $dbName) {
798
-			if (isset($properties[$xmlName])) {
799
-				$values[$dbName] = $properties[$xmlName];
800
-			}
801
-		}
802
-
803
-		$query = $this->db->getQueryBuilder();
804
-		$query->insert('calendars');
805
-		foreach ($values as $column => $value) {
806
-			$query->setValue($column, $query->createNamedParameter($value));
807
-		}
808
-		$query->executeStatement();
809
-		$calendarId = $query->getLastInsertId();
810
-
811
-		$calendarData = $this->getCalendarById($calendarId);
812
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
813
-
814
-		return $calendarId;
815
-	}
816
-
817
-	/**
818
-	 * Updates properties for a calendar.
819
-	 *
820
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
821
-	 * To do the actual updates, you must tell this object which properties
822
-	 * you're going to process with the handle() method.
823
-	 *
824
-	 * Calling the handle method is like telling the PropPatch object "I
825
-	 * promise I can handle updating this property".
826
-	 *
827
-	 * Read the PropPatch documentation for more info and examples.
828
-	 *
829
-	 * @param mixed $calendarId
830
-	 * @param PropPatch $propPatch
831
-	 * @return void
832
-	 */
833
-	public function updateCalendar($calendarId, PropPatch $propPatch) {
834
-		$supportedProperties = array_keys($this->propertyMap);
835
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
836
-
837
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
838
-			$newValues = [];
839
-			foreach ($mutations as $propertyName => $propertyValue) {
840
-				switch ($propertyName) {
841
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
842
-						$fieldName = 'transparent';
843
-						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
844
-						break;
845
-					default:
846
-						$fieldName = $this->propertyMap[$propertyName];
847
-						$newValues[$fieldName] = $propertyValue;
848
-						break;
849
-				}
850
-			}
851
-			$query = $this->db->getQueryBuilder();
852
-			$query->update('calendars');
853
-			foreach ($newValues as $fieldName => $value) {
854
-				$query->set($fieldName, $query->createNamedParameter($value));
855
-			}
856
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
857
-			$query->executeStatement();
858
-
859
-			$this->addChange($calendarId, "", 2);
860
-
861
-			$calendarData = $this->getCalendarById($calendarId);
862
-			$shares = $this->getShares($calendarId);
863
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
864
-
865
-			return true;
866
-		});
867
-	}
868
-
869
-	/**
870
-	 * Delete a calendar and all it's objects
871
-	 *
872
-	 * @param mixed $calendarId
873
-	 * @return void
874
-	 */
875
-	public function deleteCalendar($calendarId) {
876
-		$calendarData = $this->getCalendarById($calendarId);
877
-		$shares = $this->getShares($calendarId);
878
-
879
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
880
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
881
-
882
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
883
-		$stmt->execute([$calendarId]);
884
-
885
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
886
-		$stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
887
-
888
-		$this->calendarSharingBackend->deleteAllShares($calendarId);
889
-
890
-		$query = $this->db->getQueryBuilder();
891
-		$query->delete($this->dbObjectPropertiesTable)
892
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
893
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
894
-			->executeStatement();
895
-
896
-		// Only dispatch if we actually deleted anything
897
-		if ($calendarData) {
898
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
899
-		}
900
-	}
901
-
902
-	/**
903
-	 * Delete all of an user's shares
904
-	 *
905
-	 * @param string $principaluri
906
-	 * @return void
907
-	 */
908
-	public function deleteAllSharesByUser($principaluri) {
909
-		$this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
910
-	}
911
-
912
-	/**
913
-	 * Returns all calendar objects within a calendar.
914
-	 *
915
-	 * Every item contains an array with the following keys:
916
-	 *   * calendardata - The iCalendar-compatible calendar data
917
-	 *   * uri - a unique key which will be used to construct the uri. This can
918
-	 *     be any arbitrary string, but making sure it ends with '.ics' is a
919
-	 *     good idea. This is only the basename, or filename, not the full
920
-	 *     path.
921
-	 *   * lastmodified - a timestamp of the last modification time
922
-	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
923
-	 *   '"abcdef"')
924
-	 *   * size - The size of the calendar objects, in bytes.
925
-	 *   * component - optional, a string containing the type of object, such
926
-	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
927
-	 *     the Content-Type header.
928
-	 *
929
-	 * Note that the etag is optional, but it's highly encouraged to return for
930
-	 * speed reasons.
931
-	 *
932
-	 * The calendardata is also optional. If it's not returned
933
-	 * 'getCalendarObject' will be called later, which *is* expected to return
934
-	 * calendardata.
935
-	 *
936
-	 * If neither etag or size are specified, the calendardata will be
937
-	 * used/fetched to determine these numbers. If both are specified the
938
-	 * amount of times this is needed is reduced by a great degree.
939
-	 *
940
-	 * @param mixed $calendarId
941
-	 * @param int $calendarType
942
-	 * @return array
943
-	 */
944
-	public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
945
-		$query = $this->db->getQueryBuilder();
946
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
947
-			->from('calendarobjects')
948
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
949
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
950
-		$stmt = $query->executeQuery();
951
-
952
-		$result = [];
953
-		foreach ($stmt->fetchAll() as $row) {
954
-			$result[] = [
955
-				'id' => $row['id'],
956
-				'uri' => $row['uri'],
957
-				'lastmodified' => $row['lastmodified'],
958
-				'etag' => '"' . $row['etag'] . '"',
959
-				'calendarid' => $row['calendarid'],
960
-				'size' => (int)$row['size'],
961
-				'component' => strtolower($row['componenttype']),
962
-				'classification' => (int)$row['classification']
963
-			];
964
-		}
965
-		$stmt->closeCursor();
966
-
967
-		return $result;
968
-	}
969
-
970
-	/**
971
-	 * Returns information from a single calendar object, based on it's object
972
-	 * uri.
973
-	 *
974
-	 * The object uri is only the basename, or filename and not a full path.
975
-	 *
976
-	 * The returned array must have the same keys as getCalendarObjects. The
977
-	 * 'calendardata' object is required here though, while it's not required
978
-	 * for getCalendarObjects.
979
-	 *
980
-	 * This method must return null if the object did not exist.
981
-	 *
982
-	 * @param mixed $calendarId
983
-	 * @param string $objectUri
984
-	 * @param int $calendarType
985
-	 * @return array|null
986
-	 */
987
-	public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
988
-		$query = $this->db->getQueryBuilder();
989
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
990
-			->from('calendarobjects')
991
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
992
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
993
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
994
-		$stmt = $query->executeQuery();
995
-		$row = $stmt->fetch();
996
-		$stmt->closeCursor();
997
-
998
-		if (!$row) {
999
-			return null;
1000
-		}
1001
-
1002
-		return [
1003
-			'id' => $row['id'],
1004
-			'uri' => $row['uri'],
1005
-			'lastmodified' => $row['lastmodified'],
1006
-			'etag' => '"' . $row['etag'] . '"',
1007
-			'calendarid' => $row['calendarid'],
1008
-			'size' => (int)$row['size'],
1009
-			'calendardata' => $this->readBlob($row['calendardata']),
1010
-			'component' => strtolower($row['componenttype']),
1011
-			'classification' => (int)$row['classification']
1012
-		];
1013
-	}
1014
-
1015
-	/**
1016
-	 * Returns a list of calendar objects.
1017
-	 *
1018
-	 * This method should work identical to getCalendarObject, but instead
1019
-	 * return all the calendar objects in the list as an array.
1020
-	 *
1021
-	 * If the backend supports this, it may allow for some speed-ups.
1022
-	 *
1023
-	 * @param mixed $calendarId
1024
-	 * @param string[] $uris
1025
-	 * @param int $calendarType
1026
-	 * @return array
1027
-	 */
1028
-	public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1029
-		if (empty($uris)) {
1030
-			return [];
1031
-		}
1032
-
1033
-		$chunks = array_chunk($uris, 100);
1034
-		$objects = [];
1035
-
1036
-		$query = $this->db->getQueryBuilder();
1037
-		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1038
-			->from('calendarobjects')
1039
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1040
-			->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1041
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1042
-
1043
-		foreach ($chunks as $uris) {
1044
-			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1045
-			$result = $query->executeQuery();
1046
-
1047
-			while ($row = $result->fetch()) {
1048
-				$objects[] = [
1049
-					'id' => $row['id'],
1050
-					'uri' => $row['uri'],
1051
-					'lastmodified' => $row['lastmodified'],
1052
-					'etag' => '"' . $row['etag'] . '"',
1053
-					'calendarid' => $row['calendarid'],
1054
-					'size' => (int)$row['size'],
1055
-					'calendardata' => $this->readBlob($row['calendardata']),
1056
-					'component' => strtolower($row['componenttype']),
1057
-					'classification' => (int)$row['classification']
1058
-				];
1059
-			}
1060
-			$result->closeCursor();
1061
-		}
1062
-
1063
-		return $objects;
1064
-	}
1065
-
1066
-	/**
1067
-	 * Creates a new calendar object.
1068
-	 *
1069
-	 * The object uri is only the basename, or filename and not a full path.
1070
-	 *
1071
-	 * It is possible return an etag from this function, which will be used in
1072
-	 * the response to this PUT request. Note that the ETag must be surrounded
1073
-	 * by double-quotes.
1074
-	 *
1075
-	 * However, you should only really return this ETag if you don't mangle the
1076
-	 * calendar-data. If the result of a subsequent GET to this object is not
1077
-	 * the exact same as this request body, you should omit the ETag.
1078
-	 *
1079
-	 * @param mixed $calendarId
1080
-	 * @param string $objectUri
1081
-	 * @param string $calendarData
1082
-	 * @param int $calendarType
1083
-	 * @return string
1084
-	 */
1085
-	public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1086
-		$extraData = $this->getDenormalizedData($calendarData);
1087
-
1088
-		$q = $this->db->getQueryBuilder();
1089
-		$q->select($q->func()->count('*'))
1090
-			->from('calendarobjects')
1091
-			->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1092
-			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1093
-			->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1094
-
1095
-		$result = $q->executeQuery();
1096
-		$count = (int) $result->fetchOne();
1097
-		$result->closeCursor();
1098
-
1099
-		if ($count !== 0) {
1100
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1101
-		}
1102
-
1103
-		$query = $this->db->getQueryBuilder();
1104
-		$query->insert('calendarobjects')
1105
-			->values([
1106
-				'calendarid' => $query->createNamedParameter($calendarId),
1107
-				'uri' => $query->createNamedParameter($objectUri),
1108
-				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1109
-				'lastmodified' => $query->createNamedParameter(time()),
1110
-				'etag' => $query->createNamedParameter($extraData['etag']),
1111
-				'size' => $query->createNamedParameter($extraData['size']),
1112
-				'componenttype' => $query->createNamedParameter($extraData['componentType']),
1113
-				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1114
-				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1115
-				'classification' => $query->createNamedParameter($extraData['classification']),
1116
-				'uid' => $query->createNamedParameter($extraData['uid']),
1117
-				'calendartype' => $query->createNamedParameter($calendarType),
1118
-			])
1119
-			->executeStatement();
1120
-
1121
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1122
-		$this->addChange($calendarId, $objectUri, 1, $calendarType);
1123
-
1124
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1125
-		if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1126
-			$calendarRow = $this->getCalendarById($calendarId);
1127
-			$shares = $this->getShares($calendarId);
1128
-
1129
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1130
-		} else {
1131
-			$subscriptionRow = $this->getSubscriptionById($calendarId);
1132
-
1133
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1134
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1135
-				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1136
-				[
1137
-					'subscriptionId' => $calendarId,
1138
-					'calendarData' => $subscriptionRow,
1139
-					'shares' => [],
1140
-					'objectData' => $objectRow,
1141
-				]
1142
-			));
1143
-		}
1144
-
1145
-		return '"' . $extraData['etag'] . '"';
1146
-	}
1147
-
1148
-	/**
1149
-	 * Updates an existing calendarobject, based on it's uri.
1150
-	 *
1151
-	 * The object uri is only the basename, or filename and not a full path.
1152
-	 *
1153
-	 * It is possible return an etag from this function, which will be used in
1154
-	 * the response to this PUT request. Note that the ETag must be surrounded
1155
-	 * by double-quotes.
1156
-	 *
1157
-	 * However, you should only really return this ETag if you don't mangle the
1158
-	 * calendar-data. If the result of a subsequent GET to this object is not
1159
-	 * the exact same as this request body, you should omit the ETag.
1160
-	 *
1161
-	 * @param mixed $calendarId
1162
-	 * @param string $objectUri
1163
-	 * @param string $calendarData
1164
-	 * @param int $calendarType
1165
-	 * @return string
1166
-	 */
1167
-	public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1168
-		$extraData = $this->getDenormalizedData($calendarData);
1169
-		$query = $this->db->getQueryBuilder();
1170
-		$query->update('calendarobjects')
1171
-				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1172
-				->set('lastmodified', $query->createNamedParameter(time()))
1173
-				->set('etag', $query->createNamedParameter($extraData['etag']))
1174
-				->set('size', $query->createNamedParameter($extraData['size']))
1175
-				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1176
-				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1177
-				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1178
-				->set('classification', $query->createNamedParameter($extraData['classification']))
1179
-				->set('uid', $query->createNamedParameter($extraData['uid']))
1180
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1181
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1182
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1183
-			->executeStatement();
1184
-
1185
-		$this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1186
-		$this->addChange($calendarId, $objectUri, 2, $calendarType);
1187
-
1188
-		$objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1189
-		if (is_array($objectRow)) {
1190
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1191
-				$calendarRow = $this->getCalendarById($calendarId);
1192
-				$shares = $this->getShares($calendarId);
1193
-
1194
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1195
-			} else {
1196
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1197
-
1198
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1199
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1200
-					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1201
-					[
1202
-						'subscriptionId' => $calendarId,
1203
-						'calendarData' => $subscriptionRow,
1204
-						'shares' => [],
1205
-						'objectData' => $objectRow,
1206
-					]
1207
-				));
1208
-			}
1209
-		}
1210
-
1211
-		return '"' . $extraData['etag'] . '"';
1212
-	}
1213
-
1214
-	/**
1215
-	 * @param int $calendarObjectId
1216
-	 * @param int $classification
1217
-	 */
1218
-	public function setClassification($calendarObjectId, $classification) {
1219
-		if (!in_array($classification, [
1220
-			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1221
-		])) {
1222
-			throw new \InvalidArgumentException();
1223
-		}
1224
-		$query = $this->db->getQueryBuilder();
1225
-		$query->update('calendarobjects')
1226
-			->set('classification', $query->createNamedParameter($classification))
1227
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1228
-			->executeStatement();
1229
-	}
1230
-
1231
-	/**
1232
-	 * Deletes an existing calendar object.
1233
-	 *
1234
-	 * The object uri is only the basename, or filename and not a full path.
1235
-	 *
1236
-	 * @param mixed $calendarId
1237
-	 * @param string $objectUri
1238
-	 * @param int $calendarType
1239
-	 * @return void
1240
-	 */
1241
-	public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1242
-		$data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1243
-		if (is_array($data)) {
1244
-			if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1245
-				$calendarRow = $this->getCalendarById($calendarId);
1246
-				$shares = $this->getShares($calendarId);
1247
-
1248
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1249
-			} else {
1250
-				$subscriptionRow = $this->getSubscriptionById($calendarId);
1251
-
1252
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1253
-				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1254
-					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1255
-					[
1256
-						'subscriptionId' => $calendarId,
1257
-						'calendarData' => $subscriptionRow,
1258
-						'shares' => [],
1259
-						'objectData' => $data,
1260
-					]
1261
-				));
1262
-			}
1263
-		}
1264
-
1265
-		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1266
-		$stmt->execute([$calendarId, $objectUri, $calendarType]);
1267
-
1268
-		if (is_array($data)) {
1269
-			$this->purgeProperties($calendarId, $data['id'], $calendarType);
1270
-		}
1271
-
1272
-		$this->addChange($calendarId, $objectUri, 3, $calendarType);
1273
-	}
1274
-
1275
-	/**
1276
-	 * Performs a calendar-query on the contents of this calendar.
1277
-	 *
1278
-	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1279
-	 * calendar-query it is possible for a client to request a specific set of
1280
-	 * object, based on contents of iCalendar properties, date-ranges and
1281
-	 * iCalendar component types (VTODO, VEVENT).
1282
-	 *
1283
-	 * This method should just return a list of (relative) urls that match this
1284
-	 * query.
1285
-	 *
1286
-	 * The list of filters are specified as an array. The exact array is
1287
-	 * documented by Sabre\CalDAV\CalendarQueryParser.
1288
-	 *
1289
-	 * Note that it is extremely likely that getCalendarObject for every path
1290
-	 * returned from this method will be called almost immediately after. You
1291
-	 * may want to anticipate this to speed up these requests.
1292
-	 *
1293
-	 * This method provides a default implementation, which parses *all* the
1294
-	 * iCalendar objects in the specified calendar.
1295
-	 *
1296
-	 * This default may well be good enough for personal use, and calendars
1297
-	 * that aren't very large. But if you anticipate high usage, big calendars
1298
-	 * or high loads, you are strongly advised to optimize certain paths.
1299
-	 *
1300
-	 * The best way to do so is override this method and to optimize
1301
-	 * specifically for 'common filters'.
1302
-	 *
1303
-	 * Requests that are extremely common are:
1304
-	 *   * requests for just VEVENTS
1305
-	 *   * requests for just VTODO
1306
-	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1307
-	 *
1308
-	 * ..and combinations of these requests. It may not be worth it to try to
1309
-	 * handle every possible situation and just rely on the (relatively
1310
-	 * easy to use) CalendarQueryValidator to handle the rest.
1311
-	 *
1312
-	 * Note that especially time-range-filters may be difficult to parse. A
1313
-	 * time-range filter specified on a VEVENT must for instance also handle
1314
-	 * recurrence rules correctly.
1315
-	 * A good example of how to interprete all these filters can also simply
1316
-	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1317
-	 * as possible, so it gives you a good idea on what type of stuff you need
1318
-	 * to think of.
1319
-	 *
1320
-	 * @param mixed $calendarId
1321
-	 * @param array $filters
1322
-	 * @param int $calendarType
1323
-	 * @return array
1324
-	 */
1325
-	public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1326
-		$componentType = null;
1327
-		$requirePostFilter = true;
1328
-		$timeRange = null;
1329
-
1330
-		// if no filters were specified, we don't need to filter after a query
1331
-		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1332
-			$requirePostFilter = false;
1333
-		}
1334
-
1335
-		// Figuring out if there's a component filter
1336
-		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1337
-			$componentType = $filters['comp-filters'][0]['name'];
1338
-
1339
-			// Checking if we need post-filters
1340
-			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1341
-				$requirePostFilter = false;
1342
-			}
1343
-			// There was a time-range filter
1344
-			if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1345
-				$timeRange = $filters['comp-filters'][0]['time-range'];
1346
-
1347
-				// If start time OR the end time is not specified, we can do a
1348
-				// 100% accurate mysql query.
1349
-				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1350
-					$requirePostFilter = false;
1351
-				}
1352
-			}
1353
-		}
1354
-		$columns = ['uri'];
1355
-		if ($requirePostFilter) {
1356
-			$columns = ['uri', 'calendardata'];
1357
-		}
1358
-		$query = $this->db->getQueryBuilder();
1359
-		$query->select($columns)
1360
-			->from('calendarobjects')
1361
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1362
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1363
-
1364
-		if ($componentType) {
1365
-			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1366
-		}
1367
-
1368
-		if ($timeRange && $timeRange['start']) {
1369
-			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1370
-		}
1371
-		if ($timeRange && $timeRange['end']) {
1372
-			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1373
-		}
1374
-
1375
-		$stmt = $query->executeQuery();
1376
-
1377
-		$result = [];
1378
-		while ($row = $stmt->fetch()) {
1379
-			if ($requirePostFilter) {
1380
-				// validateFilterForObject will parse the calendar data
1381
-				// catch parsing errors
1382
-				try {
1383
-					$matches = $this->validateFilterForObject($row, $filters);
1384
-				} catch (ParseException $ex) {
1385
-					$this->logger->logException($ex, [
1386
-						'app' => 'dav',
1387
-						'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1388
-					]);
1389
-					continue;
1390
-				} catch (InvalidDataException $ex) {
1391
-					$this->logger->logException($ex, [
1392
-						'app' => 'dav',
1393
-						'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1394
-					]);
1395
-					continue;
1396
-				}
1397
-
1398
-				if (!$matches) {
1399
-					continue;
1400
-				}
1401
-			}
1402
-			$result[] = $row['uri'];
1403
-		}
1404
-
1405
-		return $result;
1406
-	}
1407
-
1408
-	/**
1409
-	 * custom Nextcloud search extension for CalDAV
1410
-	 *
1411
-	 * TODO - this should optionally cover cached calendar objects as well
1412
-	 *
1413
-	 * @param string $principalUri
1414
-	 * @param array $filters
1415
-	 * @param integer|null $limit
1416
-	 * @param integer|null $offset
1417
-	 * @return array
1418
-	 */
1419
-	public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1420
-		$calendars = $this->getCalendarsForUser($principalUri);
1421
-		$ownCalendars = [];
1422
-		$sharedCalendars = [];
1423
-
1424
-		$uriMapper = [];
1425
-
1426
-		foreach ($calendars as $calendar) {
1427
-			if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1428
-				$ownCalendars[] = $calendar['id'];
1429
-			} else {
1430
-				$sharedCalendars[] = $calendar['id'];
1431
-			}
1432
-			$uriMapper[$calendar['id']] = $calendar['uri'];
1433
-		}
1434
-		if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1435
-			return [];
1436
-		}
1437
-
1438
-		$query = $this->db->getQueryBuilder();
1439
-		// Calendar id expressions
1440
-		$calendarExpressions = [];
1441
-		foreach ($ownCalendars as $id) {
1442
-			$calendarExpressions[] = $query->expr()->andX(
1443
-				$query->expr()->eq('c.calendarid',
1444
-					$query->createNamedParameter($id)),
1445
-				$query->expr()->eq('c.calendartype',
1446
-						$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1447
-		}
1448
-		foreach ($sharedCalendars as $id) {
1449
-			$calendarExpressions[] = $query->expr()->andX(
1450
-				$query->expr()->eq('c.calendarid',
1451
-					$query->createNamedParameter($id)),
1452
-				$query->expr()->eq('c.classification',
1453
-					$query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1454
-				$query->expr()->eq('c.calendartype',
1455
-					$query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1456
-		}
1457
-
1458
-		if (count($calendarExpressions) === 1) {
1459
-			$calExpr = $calendarExpressions[0];
1460
-		} else {
1461
-			$calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1462
-		}
1463
-
1464
-		// Component expressions
1465
-		$compExpressions = [];
1466
-		foreach ($filters['comps'] as $comp) {
1467
-			$compExpressions[] = $query->expr()
1468
-				->eq('c.componenttype', $query->createNamedParameter($comp));
1469
-		}
1470
-
1471
-		if (count($compExpressions) === 1) {
1472
-			$compExpr = $compExpressions[0];
1473
-		} else {
1474
-			$compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1475
-		}
1476
-
1477
-		if (!isset($filters['props'])) {
1478
-			$filters['props'] = [];
1479
-		}
1480
-		if (!isset($filters['params'])) {
1481
-			$filters['params'] = [];
1482
-		}
1483
-
1484
-		$propParamExpressions = [];
1485
-		foreach ($filters['props'] as $prop) {
1486
-			$propParamExpressions[] = $query->expr()->andX(
1487
-				$query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1488
-				$query->expr()->isNull('i.parameter')
1489
-			);
1490
-		}
1491
-		foreach ($filters['params'] as $param) {
1492
-			$propParamExpressions[] = $query->expr()->andX(
1493
-				$query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1494
-				$query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1495
-			);
1496
-		}
1497
-
1498
-		if (count($propParamExpressions) === 1) {
1499
-			$propParamExpr = $propParamExpressions[0];
1500
-		} else {
1501
-			$propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1502
-		}
1503
-
1504
-		$query->select(['c.calendarid', 'c.uri'])
1505
-			->from($this->dbObjectPropertiesTable, 'i')
1506
-			->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1507
-			->where($calExpr)
1508
-			->andWhere($compExpr)
1509
-			->andWhere($propParamExpr)
1510
-			->andWhere($query->expr()->iLike('i.value',
1511
-				$query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1512
-
1513
-		if ($offset) {
1514
-			$query->setFirstResult($offset);
1515
-		}
1516
-		if ($limit) {
1517
-			$query->setMaxResults($limit);
1518
-		}
1519
-
1520
-		$stmt = $query->executeQuery();
1521
-
1522
-		$result = [];
1523
-		while ($row = $stmt->fetch()) {
1524
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1525
-			if (!in_array($path, $result)) {
1526
-				$result[] = $path;
1527
-			}
1528
-		}
1529
-
1530
-		return $result;
1531
-	}
1532
-
1533
-	/**
1534
-	 * used for Nextcloud's calendar API
1535
-	 *
1536
-	 * @param array $calendarInfo
1537
-	 * @param string $pattern
1538
-	 * @param array $searchProperties
1539
-	 * @param array $options
1540
-	 * @param integer|null $limit
1541
-	 * @param integer|null $offset
1542
-	 *
1543
-	 * @return array
1544
-	 */
1545
-	public function search(array $calendarInfo, $pattern, array $searchProperties,
1546
-						   array $options, $limit, $offset) {
1547
-		$outerQuery = $this->db->getQueryBuilder();
1548
-		$innerQuery = $this->db->getQueryBuilder();
1549
-
1550
-		$innerQuery->selectDistinct('op.objectid')
1551
-			->from($this->dbObjectPropertiesTable, 'op')
1552
-			->andWhere($innerQuery->expr()->eq('op.calendarid',
1553
-				$outerQuery->createNamedParameter($calendarInfo['id'])))
1554
-			->andWhere($innerQuery->expr()->eq('op.calendartype',
1555
-				$outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1556
-
1557
-		// only return public items for shared calendars for now
1558
-		if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1559
-			$innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1560
-				$outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1561
-		}
1562
-
1563
-		$or = $innerQuery->expr()->orX();
1564
-		foreach ($searchProperties as $searchProperty) {
1565
-			$or->add($innerQuery->expr()->eq('op.name',
1566
-				$outerQuery->createNamedParameter($searchProperty)));
1567
-		}
1568
-		$innerQuery->andWhere($or);
1569
-
1570
-		if ($pattern !== '') {
1571
-			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1572
-				$outerQuery->createNamedParameter('%' .
1573
-					$this->db->escapeLikeParameter($pattern) . '%')));
1574
-		}
1575
-
1576
-		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1577
-			->from('calendarobjects', 'c');
1578
-
1579
-		if (isset($options['timerange'])) {
1580
-			if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1581
-				$outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1582
-					$outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1583
-			}
1584
-			if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1585
-				$outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1586
-					$outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1587
-			}
1588
-		}
1589
-
1590
-		if (isset($options['types'])) {
1591
-			$or = $outerQuery->expr()->orX();
1592
-			foreach ($options['types'] as $type) {
1593
-				$or->add($outerQuery->expr()->eq('componenttype',
1594
-					$outerQuery->createNamedParameter($type)));
1595
-			}
1596
-			$outerQuery->andWhere($or);
1597
-		}
1598
-
1599
-		$outerQuery->andWhere($outerQuery->expr()->in('c.id',
1600
-			$outerQuery->createFunction($innerQuery->getSQL())));
1601
-
1602
-		if ($offset) {
1603
-			$outerQuery->setFirstResult($offset);
1604
-		}
1605
-		if ($limit) {
1606
-			$outerQuery->setMaxResults($limit);
1607
-		}
1608
-
1609
-		$result = $outerQuery->executeQuery();
1610
-		$calendarObjects = $result->fetchAll();
1611
-
1612
-		return array_map(function ($o) {
1613
-			$calendarData = Reader::read($o['calendardata']);
1614
-			$comps = $calendarData->getComponents();
1615
-			$objects = [];
1616
-			$timezones = [];
1617
-			foreach ($comps as $comp) {
1618
-				if ($comp instanceof VTimeZone) {
1619
-					$timezones[] = $comp;
1620
-				} else {
1621
-					$objects[] = $comp;
1622
-				}
1623
-			}
1624
-
1625
-			return [
1626
-				'id' => $o['id'],
1627
-				'type' => $o['componenttype'],
1628
-				'uid' => $o['uid'],
1629
-				'uri' => $o['uri'],
1630
-				'objects' => array_map(function ($c) {
1631
-					return $this->transformSearchData($c);
1632
-				}, $objects),
1633
-				'timezones' => array_map(function ($c) {
1634
-					return $this->transformSearchData($c);
1635
-				}, $timezones),
1636
-			];
1637
-		}, $calendarObjects);
1638
-	}
1639
-
1640
-	/**
1641
-	 * @param Component $comp
1642
-	 * @return array
1643
-	 */
1644
-	private function transformSearchData(Component $comp) {
1645
-		$data = [];
1646
-		/** @var Component[] $subComponents */
1647
-		$subComponents = $comp->getComponents();
1648
-		/** @var Property[] $properties */
1649
-		$properties = array_filter($comp->children(), function ($c) {
1650
-			return $c instanceof Property;
1651
-		});
1652
-		$validationRules = $comp->getValidationRules();
1653
-
1654
-		foreach ($subComponents as $subComponent) {
1655
-			$name = $subComponent->name;
1656
-			if (!isset($data[$name])) {
1657
-				$data[$name] = [];
1658
-			}
1659
-			$data[$name][] = $this->transformSearchData($subComponent);
1660
-		}
1661
-
1662
-		foreach ($properties as $property) {
1663
-			$name = $property->name;
1664
-			if (!isset($validationRules[$name])) {
1665
-				$validationRules[$name] = '*';
1666
-			}
1667
-
1668
-			$rule = $validationRules[$property->name];
1669
-			if ($rule === '+' || $rule === '*') { // multiple
1670
-				if (!isset($data[$name])) {
1671
-					$data[$name] = [];
1672
-				}
1673
-
1674
-				$data[$name][] = $this->transformSearchProperty($property);
1675
-			} else { // once
1676
-				$data[$name] = $this->transformSearchProperty($property);
1677
-			}
1678
-		}
1679
-
1680
-		return $data;
1681
-	}
1682
-
1683
-	/**
1684
-	 * @param Property $prop
1685
-	 * @return array
1686
-	 */
1687
-	private function transformSearchProperty(Property $prop) {
1688
-		// No need to check Date, as it extends DateTime
1689
-		if ($prop instanceof Property\ICalendar\DateTime) {
1690
-			$value = $prop->getDateTime();
1691
-		} else {
1692
-			$value = $prop->getValue();
1693
-		}
1694
-
1695
-		return [
1696
-			$value,
1697
-			$prop->parameters()
1698
-		];
1699
-	}
1700
-
1701
-	/**
1702
-	 * @param string $principalUri
1703
-	 * @param string $pattern
1704
-	 * @param array $componentTypes
1705
-	 * @param array $searchProperties
1706
-	 * @param array $searchParameters
1707
-	 * @param array $options
1708
-	 * @return array
1709
-	 */
1710
-	public function searchPrincipalUri(string $principalUri,
1711
-									   string $pattern,
1712
-									   array $componentTypes,
1713
-									   array $searchProperties,
1714
-									   array $searchParameters,
1715
-									   array $options = []): array {
1716
-		$escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1717
-
1718
-		$calendarObjectIdQuery = $this->db->getQueryBuilder();
1719
-		$calendarOr = $calendarObjectIdQuery->expr()->orX();
1720
-		$searchOr = $calendarObjectIdQuery->expr()->orX();
1721
-
1722
-		// Fetch calendars and subscription
1723
-		$calendars = $this->getCalendarsForUser($principalUri);
1724
-		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1725
-		foreach ($calendars as $calendar) {
1726
-			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1727
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1728
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1729
-
1730
-			// If it's shared, limit search to public events
1731
-			if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
1732
-				&& $calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1733
-				$calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1734
-			}
1735
-
1736
-			$calendarOr->add($calendarAnd);
1737
-		}
1738
-		foreach ($subscriptions as $subscription) {
1739
-			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1740
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1741
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1742
-
1743
-			// If it's shared, limit search to public events
1744
-			if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
1745
-				&& $subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1746
-				$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1747
-			}
1748
-
1749
-			$calendarOr->add($subscriptionAnd);
1750
-		}
1751
-
1752
-		foreach ($searchProperties as $property) {
1753
-			$propertyAnd = $calendarObjectIdQuery->expr()->andX();
1754
-			$propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1755
-			$propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1756
-
1757
-			$searchOr->add($propertyAnd);
1758
-		}
1759
-		foreach ($searchParameters as $property => $parameter) {
1760
-			$parameterAnd = $calendarObjectIdQuery->expr()->andX();
1761
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1762
-			$parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1763
-
1764
-			$searchOr->add($parameterAnd);
1765
-		}
1766
-
1767
-		if ($calendarOr->count() === 0) {
1768
-			return [];
1769
-		}
1770
-		if ($searchOr->count() === 0) {
1771
-			return [];
1772
-		}
1773
-
1774
-		$calendarObjectIdQuery->selectDistinct('cob.objectid')
1775
-			->from($this->dbObjectPropertiesTable, 'cob')
1776
-			->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1777
-			->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1778
-			->andWhere($calendarOr)
1779
-			->andWhere($searchOr);
1780
-
1781
-		if ('' !== $pattern) {
1782
-			if (!$escapePattern) {
1783
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1784
-			} else {
1785
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1786
-			}
1787
-		}
1788
-
1789
-		if (isset($options['limit'])) {
1790
-			$calendarObjectIdQuery->setMaxResults($options['limit']);
1791
-		}
1792
-		if (isset($options['offset'])) {
1793
-			$calendarObjectIdQuery->setFirstResult($options['offset']);
1794
-		}
1795
-
1796
-		$result = $calendarObjectIdQuery->executeQuery();
1797
-		$matches = $result->fetchAll();
1798
-		$result->closeCursor();
1799
-		$matches = array_map(static function (array $match):int {
1800
-			return (int) $match['objectid'];
1801
-		}, $matches);
1802
-
1803
-		$query = $this->db->getQueryBuilder();
1804
-		$query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1805
-			->from('calendarobjects')
1806
-			->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1807
-
1808
-		$result = $query->executeQuery();
1809
-		$calendarObjects = $result->fetchAll();
1810
-		$result->closeCursor();
1811
-
1812
-		return array_map(function (array $array): array {
1813
-			$array['calendarid'] = (int)$array['calendarid'];
1814
-			$array['calendartype'] = (int)$array['calendartype'];
1815
-			$array['calendardata'] = $this->readBlob($array['calendardata']);
1816
-
1817
-			return $array;
1818
-		}, $calendarObjects);
1819
-	}
1820
-
1821
-	/**
1822
-	 * Searches through all of a users calendars and calendar objects to find
1823
-	 * an object with a specific UID.
1824
-	 *
1825
-	 * This method should return the path to this object, relative to the
1826
-	 * calendar home, so this path usually only contains two parts:
1827
-	 *
1828
-	 * calendarpath/objectpath.ics
1829
-	 *
1830
-	 * If the uid is not found, return null.
1831
-	 *
1832
-	 * This method should only consider * objects that the principal owns, so
1833
-	 * any calendars owned by other principals that also appear in this
1834
-	 * collection should be ignored.
1835
-	 *
1836
-	 * @param string $principalUri
1837
-	 * @param string $uid
1838
-	 * @return string|null
1839
-	 */
1840
-	public function getCalendarObjectByUID($principalUri, $uid) {
1841
-		$query = $this->db->getQueryBuilder();
1842
-		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1843
-			->from('calendarobjects', 'co')
1844
-			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1845
-			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1846
-			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1847
-
1848
-		$stmt = $query->executeQuery();
1849
-		$row = $stmt->fetch();
1850
-		$stmt->closeCursor();
1851
-		if ($row) {
1852
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1853
-		}
1854
-
1855
-		return null;
1856
-	}
1857
-
1858
-	/**
1859
-	 * The getChanges method returns all the changes that have happened, since
1860
-	 * the specified syncToken in the specified calendar.
1861
-	 *
1862
-	 * This function should return an array, such as the following:
1863
-	 *
1864
-	 * [
1865
-	 *   'syncToken' => 'The current synctoken',
1866
-	 *   'added'   => [
1867
-	 *      'new.txt',
1868
-	 *   ],
1869
-	 *   'modified'   => [
1870
-	 *      'modified.txt',
1871
-	 *   ],
1872
-	 *   'deleted' => [
1873
-	 *      'foo.php.bak',
1874
-	 *      'old.txt'
1875
-	 *   ]
1876
-	 * );
1877
-	 *
1878
-	 * The returned syncToken property should reflect the *current* syncToken
1879
-	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1880
-	 * property This is * needed here too, to ensure the operation is atomic.
1881
-	 *
1882
-	 * If the $syncToken argument is specified as null, this is an initial
1883
-	 * sync, and all members should be reported.
1884
-	 *
1885
-	 * The modified property is an array of nodenames that have changed since
1886
-	 * the last token.
1887
-	 *
1888
-	 * The deleted property is an array with nodenames, that have been deleted
1889
-	 * from collection.
1890
-	 *
1891
-	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1892
-	 * 1, you only have to report changes that happened only directly in
1893
-	 * immediate descendants. If it's 2, it should also include changes from
1894
-	 * the nodes below the child collections. (grandchildren)
1895
-	 *
1896
-	 * The $limit argument allows a client to specify how many results should
1897
-	 * be returned at most. If the limit is not specified, it should be treated
1898
-	 * as infinite.
1899
-	 *
1900
-	 * If the limit (infinite or not) is higher than you're willing to return,
1901
-	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1902
-	 *
1903
-	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1904
-	 * return null.
1905
-	 *
1906
-	 * The limit is 'suggestive'. You are free to ignore it.
1907
-	 *
1908
-	 * @param string $calendarId
1909
-	 * @param string $syncToken
1910
-	 * @param int $syncLevel
1911
-	 * @param int|null $limit
1912
-	 * @param int $calendarType
1913
-	 * @return array
1914
-	 */
1915
-	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1916
-		// Current synctoken
1917
-		$qb = $this->db->getQueryBuilder();
1918
-		$qb->select('synctoken')
1919
-			->from('calendars')
1920
-			->where(
1921
-				$qb->expr()->eq('id', $qb->createNamedParameter($calendarId))
1922
-			);
1923
-		$stmt = $qb->executeQuery();
1924
-		$currentToken = $stmt->fetchOne();
1925
-
1926
-		if ($currentToken === false) {
1927
-			return null;
1928
-		}
1929
-
1930
-		$result = [
1931
-			'syncToken' => $currentToken,
1932
-			'added' => [],
1933
-			'modified' => [],
1934
-			'deleted' => [],
1935
-		];
1936
-
1937
-		if ($syncToken) {
1938
-			$qb = $this->db->getQueryBuilder();
1939
-
1940
-			$qb->select('uri', 'operation')
1941
-				->from('calendarchanges')
1942
-				->where(
1943
-					$qb->expr()->andX(
1944
-						$qb->expr()->gte('synctoken', $qb->createNamedParameter($syncToken)),
1945
-						$qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken)),
1946
-						$qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
1947
-						$qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType))
1948
-					)
1949
-				)->orderBy('synctoken');
1950
-			if (is_int($limit) && $limit > 0) {
1951
-				$qb->setMaxResults($limit);
1952
-			}
1953
-
1954
-			// Fetching all changes
1955
-			$stmt = $qb->executeQuery();
1956
-			$changes = [];
1957
-
1958
-			// This loop ensures that any duplicates are overwritten, only the
1959
-			// last change on a node is relevant.
1960
-			while ($row = $stmt->fetch()) {
1961
-				$changes[$row['uri']] = $row['operation'];
1962
-			}
1963
-			$stmt->closeCursor();
1964
-
1965
-			foreach ($changes as $uri => $operation) {
1966
-				switch ($operation) {
1967
-					case 1:
1968
-						$result['added'][] = $uri;
1969
-						break;
1970
-					case 2:
1971
-						$result['modified'][] = $uri;
1972
-						break;
1973
-					case 3:
1974
-						$result['deleted'][] = $uri;
1975
-						break;
1976
-				}
1977
-			}
1978
-		} else {
1979
-			// No synctoken supplied, this is the initial sync.
1980
-			$qb = $this->db->getQueryBuilder();
1981
-			$qb->select('uri')
1982
-				->from('calendarobjects')
1983
-				->where(
1984
-					$qb->expr()->andX(
1985
-						$qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
1986
-						$qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType))
1987
-					)
1988
-				);
1989
-			$stmt = $qb->executeQuery();
1990
-			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1991
-			$stmt->closeCursor();
1992
-		}
1993
-		return $result;
1994
-	}
1995
-
1996
-	/**
1997
-	 * Returns a list of subscriptions for a principal.
1998
-	 *
1999
-	 * Every subscription is an array with the following keys:
2000
-	 *  * id, a unique id that will be used by other functions to modify the
2001
-	 *    subscription. This can be the same as the uri or a database key.
2002
-	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2003
-	 *  * principaluri. The owner of the subscription. Almost always the same as
2004
-	 *    principalUri passed to this method.
2005
-	 *
2006
-	 * Furthermore, all the subscription info must be returned too:
2007
-	 *
2008
-	 * 1. {DAV:}displayname
2009
-	 * 2. {http://apple.com/ns/ical/}refreshrate
2010
-	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2011
-	 *    should not be stripped).
2012
-	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2013
-	 *    should not be stripped).
2014
-	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2015
-	 *    attachments should not be stripped).
2016
-	 * 6. {http://calendarserver.org/ns/}source (Must be a
2017
-	 *     Sabre\DAV\Property\Href).
2018
-	 * 7. {http://apple.com/ns/ical/}calendar-color
2019
-	 * 8. {http://apple.com/ns/ical/}calendar-order
2020
-	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2021
-	 *    (should just be an instance of
2022
-	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2023
-	 *    default components).
2024
-	 *
2025
-	 * @param string $principalUri
2026
-	 * @return array
2027
-	 */
2028
-	public function getSubscriptionsForUser($principalUri) {
2029
-		$fields = array_values($this->subscriptionPropertyMap);
2030
-		$fields[] = 'id';
2031
-		$fields[] = 'uri';
2032
-		$fields[] = 'source';
2033
-		$fields[] = 'principaluri';
2034
-		$fields[] = 'lastmodified';
2035
-		$fields[] = 'synctoken';
2036
-
2037
-		$query = $this->db->getQueryBuilder();
2038
-		$query->select($fields)
2039
-			->from('calendarsubscriptions')
2040
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2041
-			->orderBy('calendarorder', 'asc');
2042
-		$stmt = $query->executeQuery();
2043
-
2044
-		$subscriptions = [];
2045
-		while ($row = $stmt->fetch()) {
2046
-			$subscription = [
2047
-				'id' => $row['id'],
2048
-				'uri' => $row['uri'],
2049
-				'principaluri' => $row['principaluri'],
2050
-				'source' => $row['source'],
2051
-				'lastmodified' => $row['lastmodified'],
2052
-
2053
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2054
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2055
-			];
2056
-
2057
-			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2058
-				if (!is_null($row[$dbName])) {
2059
-					$subscription[$xmlName] = $row[$dbName];
2060
-				}
2061
-			}
2062
-
2063
-			$subscriptions[] = $subscription;
2064
-		}
2065
-
2066
-		return $subscriptions;
2067
-	}
2068
-
2069
-	/**
2070
-	 * Creates a new subscription for a principal.
2071
-	 *
2072
-	 * If the creation was a success, an id must be returned that can be used to reference
2073
-	 * this subscription in other methods, such as updateSubscription.
2074
-	 *
2075
-	 * @param string $principalUri
2076
-	 * @param string $uri
2077
-	 * @param array $properties
2078
-	 * @return mixed
2079
-	 */
2080
-	public function createSubscription($principalUri, $uri, array $properties) {
2081
-		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2082
-			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2083
-		}
2084
-
2085
-		$values = [
2086
-			'principaluri' => $principalUri,
2087
-			'uri' => $uri,
2088
-			'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2089
-			'lastmodified' => time(),
2090
-		];
2091
-
2092
-		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2093
-
2094
-		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2095
-			if (array_key_exists($xmlName, $properties)) {
2096
-				$values[$dbName] = $properties[$xmlName];
2097
-				if (in_array($dbName, $propertiesBoolean)) {
2098
-					$values[$dbName] = true;
2099
-				}
2100
-			}
2101
-		}
2102
-
2103
-		$valuesToInsert = [];
2104
-
2105
-		$query = $this->db->getQueryBuilder();
2106
-
2107
-		foreach (array_keys($values) as $name) {
2108
-			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2109
-		}
2110
-
2111
-		$query->insert('calendarsubscriptions')
2112
-			->values($valuesToInsert)
2113
-			->executeStatement();
2114
-
2115
-		$subscriptionId = $query->getLastInsertId();
2116
-
2117
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2118
-		$this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent($subscriptionId, $subscriptionRow));
2119
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2120
-			'\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2121
-			[
2122
-				'subscriptionId' => $subscriptionId,
2123
-				'subscriptionData' => $subscriptionRow,
2124
-			]));
2125
-
2126
-		return $subscriptionId;
2127
-	}
2128
-
2129
-	/**
2130
-	 * Updates a subscription
2131
-	 *
2132
-	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2133
-	 * To do the actual updates, you must tell this object which properties
2134
-	 * you're going to process with the handle() method.
2135
-	 *
2136
-	 * Calling the handle method is like telling the PropPatch object "I
2137
-	 * promise I can handle updating this property".
2138
-	 *
2139
-	 * Read the PropPatch documentation for more info and examples.
2140
-	 *
2141
-	 * @param mixed $subscriptionId
2142
-	 * @param PropPatch $propPatch
2143
-	 * @return void
2144
-	 */
2145
-	public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2146
-		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2147
-		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2148
-
2149
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2150
-			$newValues = [];
2151
-
2152
-			foreach ($mutations as $propertyName => $propertyValue) {
2153
-				if ($propertyName === '{http://calendarserver.org/ns/}source') {
2154
-					$newValues['source'] = $propertyValue->getHref();
2155
-				} else {
2156
-					$fieldName = $this->subscriptionPropertyMap[$propertyName];
2157
-					$newValues[$fieldName] = $propertyValue;
2158
-				}
2159
-			}
2160
-
2161
-			$query = $this->db->getQueryBuilder();
2162
-			$query->update('calendarsubscriptions')
2163
-				->set('lastmodified', $query->createNamedParameter(time()));
2164
-			foreach ($newValues as $fieldName => $value) {
2165
-				$query->set($fieldName, $query->createNamedParameter($value));
2166
-			}
2167
-			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2168
-				->executeStatement();
2169
-
2170
-			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2171
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2172
-			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2173
-				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2174
-				[
2175
-					'subscriptionId' => $subscriptionId,
2176
-					'subscriptionData' => $subscriptionRow,
2177
-					'propertyMutations' => $mutations,
2178
-				]));
2179
-
2180
-			return true;
2181
-		});
2182
-	}
2183
-
2184
-	/**
2185
-	 * Deletes a subscription.
2186
-	 *
2187
-	 * @param mixed $subscriptionId
2188
-	 * @return void
2189
-	 */
2190
-	public function deleteSubscription($subscriptionId) {
2191
-		$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2192
-
2193
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2194
-			'\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2195
-			[
2196
-				'subscriptionId' => $subscriptionId,
2197
-				'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2198
-			]));
2199
-
2200
-		$query = $this->db->getQueryBuilder();
2201
-		$query->delete('calendarsubscriptions')
2202
-			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2203
-			->executeStatement();
2204
-
2205
-		$query = $this->db->getQueryBuilder();
2206
-		$query->delete('calendarobjects')
2207
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2208
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2209
-			->executeStatement();
2210
-
2211
-		$query->delete('calendarchanges')
2212
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2213
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2214
-			->executeStatement();
2215
-
2216
-		$query->delete($this->dbObjectPropertiesTable)
2217
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2218
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2219
-			->executeStatement();
2220
-
2221
-		if ($subscriptionRow) {
2222
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2223
-		}
2224
-	}
2225
-
2226
-	/**
2227
-	 * Returns a single scheduling object for the inbox collection.
2228
-	 *
2229
-	 * The returned array should contain the following elements:
2230
-	 *   * uri - A unique basename for the object. This will be used to
2231
-	 *           construct a full uri.
2232
-	 *   * calendardata - The iCalendar object
2233
-	 *   * lastmodified - The last modification date. Can be an int for a unix
2234
-	 *                    timestamp, or a PHP DateTime object.
2235
-	 *   * etag - A unique token that must change if the object changed.
2236
-	 *   * size - The size of the object, in bytes.
2237
-	 *
2238
-	 * @param string $principalUri
2239
-	 * @param string $objectUri
2240
-	 * @return array
2241
-	 */
2242
-	public function getSchedulingObject($principalUri, $objectUri) {
2243
-		$query = $this->db->getQueryBuilder();
2244
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2245
-			->from('schedulingobjects')
2246
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2247
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2248
-			->executeQuery();
2249
-
2250
-		$row = $stmt->fetch();
2251
-
2252
-		if (!$row) {
2253
-			return null;
2254
-		}
2255
-
2256
-		return [
2257
-			'uri' => $row['uri'],
2258
-			'calendardata' => $row['calendardata'],
2259
-			'lastmodified' => $row['lastmodified'],
2260
-			'etag' => '"' . $row['etag'] . '"',
2261
-			'size' => (int)$row['size'],
2262
-		];
2263
-	}
2264
-
2265
-	/**
2266
-	 * Returns all scheduling objects for the inbox collection.
2267
-	 *
2268
-	 * These objects should be returned as an array. Every item in the array
2269
-	 * should follow the same structure as returned from getSchedulingObject.
2270
-	 *
2271
-	 * The main difference is that 'calendardata' is optional.
2272
-	 *
2273
-	 * @param string $principalUri
2274
-	 * @return array
2275
-	 */
2276
-	public function getSchedulingObjects($principalUri) {
2277
-		$query = $this->db->getQueryBuilder();
2278
-		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2279
-				->from('schedulingobjects')
2280
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2281
-				->executeQuery();
2282
-
2283
-		$result = [];
2284
-		foreach ($stmt->fetchAll() as $row) {
2285
-			$result[] = [
2286
-				'calendardata' => $row['calendardata'],
2287
-				'uri' => $row['uri'],
2288
-				'lastmodified' => $row['lastmodified'],
2289
-				'etag' => '"' . $row['etag'] . '"',
2290
-				'size' => (int)$row['size'],
2291
-			];
2292
-		}
2293
-
2294
-		return $result;
2295
-	}
2296
-
2297
-	/**
2298
-	 * Deletes a scheduling object from the inbox collection.
2299
-	 *
2300
-	 * @param string $principalUri
2301
-	 * @param string $objectUri
2302
-	 * @return void
2303
-	 */
2304
-	public function deleteSchedulingObject($principalUri, $objectUri) {
2305
-		$query = $this->db->getQueryBuilder();
2306
-		$query->delete('schedulingobjects')
2307
-				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2308
-				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2309
-				->executeStatement();
2310
-	}
2311
-
2312
-	/**
2313
-	 * Creates a new scheduling object. This should land in a users' inbox.
2314
-	 *
2315
-	 * @param string $principalUri
2316
-	 * @param string $objectUri
2317
-	 * @param string $objectData
2318
-	 * @return void
2319
-	 */
2320
-	public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2321
-		$query = $this->db->getQueryBuilder();
2322
-		$query->insert('schedulingobjects')
2323
-			->values([
2324
-				'principaluri' => $query->createNamedParameter($principalUri),
2325
-				'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2326
-				'uri' => $query->createNamedParameter($objectUri),
2327
-				'lastmodified' => $query->createNamedParameter(time()),
2328
-				'etag' => $query->createNamedParameter(md5($objectData)),
2329
-				'size' => $query->createNamedParameter(strlen($objectData))
2330
-			])
2331
-			->executeStatement();
2332
-	}
2333
-
2334
-	/**
2335
-	 * Adds a change record to the calendarchanges table.
2336
-	 *
2337
-	 * @param mixed $calendarId
2338
-	 * @param string $objectUri
2339
-	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
2340
-	 * @param int $calendarType
2341
-	 * @return void
2342
-	 */
2343
-	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2344
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2345
-
2346
-		$query = $this->db->getQueryBuilder();
2347
-		$query->select('synctoken')
2348
-			->from($table)
2349
-			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2350
-		$result = $query->executeQuery();
2351
-		$syncToken = (int)$result->fetchOne();
2352
-		$result->closeCursor();
2353
-
2354
-		$query = $this->db->getQueryBuilder();
2355
-		$query->insert('calendarchanges')
2356
-			->values([
2357
-				'uri' => $query->createNamedParameter($objectUri),
2358
-				'synctoken' => $query->createNamedParameter($syncToken),
2359
-				'calendarid' => $query->createNamedParameter($calendarId),
2360
-				'operation' => $query->createNamedParameter($operation),
2361
-				'calendartype' => $query->createNamedParameter($calendarType),
2362
-			])
2363
-			->executeStatement();
2364
-
2365
-		$stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2366
-		$stmt->execute([
2367
-			$calendarId
2368
-		]);
2369
-	}
2370
-
2371
-	/**
2372
-	 * Parses some information from calendar objects, used for optimized
2373
-	 * calendar-queries.
2374
-	 *
2375
-	 * Returns an array with the following keys:
2376
-	 *   * etag - An md5 checksum of the object without the quotes.
2377
-	 *   * size - Size of the object in bytes
2378
-	 *   * componentType - VEVENT, VTODO or VJOURNAL
2379
-	 *   * firstOccurence
2380
-	 *   * lastOccurence
2381
-	 *   * uid - value of the UID property
2382
-	 *
2383
-	 * @param string $calendarData
2384
-	 * @return array
2385
-	 */
2386
-	public function getDenormalizedData($calendarData) {
2387
-		$vObject = Reader::read($calendarData);
2388
-		$vEvents = [];
2389
-		$componentType = null;
2390
-		$component = null;
2391
-		$firstOccurrence = null;
2392
-		$lastOccurrence = null;
2393
-		$uid = null;
2394
-		$classification = self::CLASSIFICATION_PUBLIC;
2395
-		$hasDTSTART = false;
2396
-		foreach ($vObject->getComponents() as $component) {
2397
-			if ($component->name !== 'VTIMEZONE') {
2398
-				// Finding all VEVENTs, and track them
2399
-				if ($component->name === 'VEVENT') {
2400
-					array_push($vEvents, $component);
2401
-					if ($component->DTSTART) {
2402
-						$hasDTSTART = true;
2403
-					}
2404
-				}
2405
-				// Track first component type and uid
2406
-				if ($uid === null) {
2407
-					$componentType = $component->name;
2408
-					$uid = (string)$component->UID;
2409
-				}
2410
-			}
2411
-		}
2412
-		if (!$componentType) {
2413
-			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2414
-		}
2415
-
2416
-		if ($hasDTSTART) {
2417
-			$component = $vEvents[0];
2418
-
2419
-			// Finding the last occurrence is a bit harder
2420
-			if (!isset($component->RRULE) && count($vEvents) === 1) {
2421
-				$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2422
-				if (isset($component->DTEND)) {
2423
-					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2424
-				} elseif (isset($component->DURATION)) {
2425
-					$endDate = clone $component->DTSTART->getDateTime();
2426
-					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2427
-					$lastOccurrence = $endDate->getTimeStamp();
2428
-				} elseif (!$component->DTSTART->hasTime()) {
2429
-					$endDate = clone $component->DTSTART->getDateTime();
2430
-					$endDate->modify('+1 day');
2431
-					$lastOccurrence = $endDate->getTimeStamp();
2432
-				} else {
2433
-					$lastOccurrence = $firstOccurrence;
2434
-				}
2435
-			} else {
2436
-				$it = new EventIterator($vEvents);
2437
-				$maxDate = new DateTime(self::MAX_DATE);
2438
-				$firstOccurrence = $it->getDtStart()->getTimestamp();
2439
-				if ($it->isInfinite()) {
2440
-					$lastOccurrence = $maxDate->getTimestamp();
2441
-				} else {
2442
-					$end = $it->getDtEnd();
2443
-					while ($it->valid() && $end < $maxDate) {
2444
-						$end = $it->getDtEnd();
2445
-						$it->next();
2446
-					}
2447
-					$lastOccurrence = $end->getTimestamp();
2448
-				}
2449
-			}
2450
-		}
2451
-
2452
-		if ($component->CLASS) {
2453
-			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2454
-			switch ($component->CLASS->getValue()) {
2455
-				case 'PUBLIC':
2456
-					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2457
-					break;
2458
-				case 'CONFIDENTIAL':
2459
-					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2460
-					break;
2461
-			}
2462
-		}
2463
-		return [
2464
-			'etag' => md5($calendarData),
2465
-			'size' => strlen($calendarData),
2466
-			'componentType' => $componentType,
2467
-			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2468
-			'lastOccurence' => $lastOccurrence,
2469
-			'uid' => $uid,
2470
-			'classification' => $classification
2471
-		];
2472
-	}
2473
-
2474
-	/**
2475
-	 * @param $cardData
2476
-	 * @return bool|string
2477
-	 */
2478
-	private function readBlob($cardData) {
2479
-		if (is_resource($cardData)) {
2480
-			return stream_get_contents($cardData);
2481
-		}
2482
-
2483
-		return $cardData;
2484
-	}
2485
-
2486
-	/**
2487
-	 * @param IShareable $shareable
2488
-	 * @param array $add
2489
-	 * @param array $remove
2490
-	 */
2491
-	public function updateShares($shareable, $add, $remove) {
2492
-		$calendarId = $shareable->getResourceId();
2493
-		$calendarRow = $this->getCalendarById($calendarId);
2494
-		$oldShares = $this->getShares($calendarId);
2495
-
2496
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2497
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2498
-			[
2499
-				'calendarId' => $calendarId,
2500
-				'calendarData' => $calendarRow,
2501
-				'shares' => $oldShares,
2502
-				'add' => $add,
2503
-				'remove' => $remove,
2504
-			]));
2505
-		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2506
-
2507
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2508
-	}
2509
-
2510
-	/**
2511
-	 * @param int $resourceId
2512
-	 * @return array
2513
-	 */
2514
-	public function getShares($resourceId) {
2515
-		return $this->calendarSharingBackend->getShares($resourceId);
2516
-	}
2517
-
2518
-	/**
2519
-	 * @param boolean $value
2520
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2521
-	 * @return string|null
2522
-	 */
2523
-	public function setPublishStatus($value, $calendar) {
2524
-		$calendarId = $calendar->getResourceId();
2525
-		$calendarData = $this->getCalendarById($calendarId);
2526
-		$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2527
-			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2528
-			[
2529
-				'calendarId' => $calendarId,
2530
-				'calendarData' => $calendarData,
2531
-				'public' => $value,
2532
-			]));
2533
-
2534
-		$query = $this->db->getQueryBuilder();
2535
-		if ($value) {
2536
-			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2537
-			$query->insert('dav_shares')
2538
-				->values([
2539
-					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2540
-					'type' => $query->createNamedParameter('calendar'),
2541
-					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2542
-					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2543
-					'publicuri' => $query->createNamedParameter($publicUri)
2544
-				]);
2545
-			$query->executeStatement();
2546
-
2547
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2548
-			return $publicUri;
2549
-		}
2550
-		$query->delete('dav_shares')
2551
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2552
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2553
-		$query->executeStatement();
2554
-
2555
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2556
-		return null;
2557
-	}
2558
-
2559
-	/**
2560
-	 * @param \OCA\DAV\CalDAV\Calendar $calendar
2561
-	 * @return mixed
2562
-	 */
2563
-	public function getPublishStatus($calendar) {
2564
-		$query = $this->db->getQueryBuilder();
2565
-		$result = $query->select('publicuri')
2566
-			->from('dav_shares')
2567
-			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2568
-			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2569
-			->executeQuery();
2570
-
2571
-		$row = $result->fetch();
2572
-		$result->closeCursor();
2573
-		return $row ? reset($row) : false;
2574
-	}
2575
-
2576
-	/**
2577
-	 * @param int $resourceId
2578
-	 * @param array $acl
2579
-	 * @return array
2580
-	 */
2581
-	public function applyShareAcl($resourceId, $acl) {
2582
-		return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2583
-	}
2584
-
2585
-
2586
-
2587
-	/**
2588
-	 * update properties table
2589
-	 *
2590
-	 * @param int $calendarId
2591
-	 * @param string $objectUri
2592
-	 * @param string $calendarData
2593
-	 * @param int $calendarType
2594
-	 */
2595
-	public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2596
-		$objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2597
-
2598
-		try {
2599
-			$vCalendar = $this->readCalendarData($calendarData);
2600
-		} catch (\Exception $ex) {
2601
-			return;
2602
-		}
2603
-
2604
-		$this->purgeProperties($calendarId, $objectId);
2605
-
2606
-		$query = $this->db->getQueryBuilder();
2607
-		$query->insert($this->dbObjectPropertiesTable)
2608
-			->values(
2609
-				[
2610
-					'calendarid' => $query->createNamedParameter($calendarId),
2611
-					'calendartype' => $query->createNamedParameter($calendarType),
2612
-					'objectid' => $query->createNamedParameter($objectId),
2613
-					'name' => $query->createParameter('name'),
2614
-					'parameter' => $query->createParameter('parameter'),
2615
-					'value' => $query->createParameter('value'),
2616
-				]
2617
-			);
2618
-
2619
-		$indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2620
-		foreach ($vCalendar->getComponents() as $component) {
2621
-			if (!in_array($component->name, $indexComponents)) {
2622
-				continue;
2623
-			}
2624
-
2625
-			foreach ($component->children() as $property) {
2626
-				if (in_array($property->name, self::$indexProperties)) {
2627
-					$value = $property->getValue();
2628
-					// is this a shitty db?
2629
-					if (!$this->db->supports4ByteText()) {
2630
-						$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2631
-					}
2632
-					$value = mb_strcut($value, 0, 254);
2633
-
2634
-					$query->setParameter('name', $property->name);
2635
-					$query->setParameter('parameter', null);
2636
-					$query->setParameter('value', $value);
2637
-					$query->executeStatement();
2638
-				}
2639
-
2640
-				if (array_key_exists($property->name, self::$indexParameters)) {
2641
-					$parameters = $property->parameters();
2642
-					$indexedParametersForProperty = self::$indexParameters[$property->name];
2643
-
2644
-					foreach ($parameters as $key => $value) {
2645
-						if (in_array($key, $indexedParametersForProperty)) {
2646
-							// is this a shitty db?
2647
-							if ($this->db->supports4ByteText()) {
2648
-								$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2649
-							}
2650
-
2651
-							$query->setParameter('name', $property->name);
2652
-							$query->setParameter('parameter', mb_strcut($key, 0, 254));
2653
-							$query->setParameter('value', mb_strcut($value, 0, 254));
2654
-							$query->executeStatement();
2655
-						}
2656
-					}
2657
-				}
2658
-			}
2659
-		}
2660
-	}
2661
-
2662
-	/**
2663
-	 * deletes all birthday calendars
2664
-	 */
2665
-	public function deleteAllBirthdayCalendars() {
2666
-		$query = $this->db->getQueryBuilder();
2667
-		$result = $query->select(['id'])->from('calendars')
2668
-			->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2669
-			->executeQuery();
2670
-
2671
-		$ids = $result->fetchAll();
2672
-		foreach ($ids as $id) {
2673
-			$this->deleteCalendar($id['id']);
2674
-		}
2675
-	}
2676
-
2677
-	/**
2678
-	 * @param $subscriptionId
2679
-	 */
2680
-	public function purgeAllCachedEventsForSubscription($subscriptionId) {
2681
-		$query = $this->db->getQueryBuilder();
2682
-		$query->select('uri')
2683
-			->from('calendarobjects')
2684
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2685
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2686
-		$stmt = $query->executeQuery();
2687
-
2688
-		$uris = [];
2689
-		foreach ($stmt->fetchAll() as $row) {
2690
-			$uris[] = $row['uri'];
2691
-		}
2692
-		$stmt->closeCursor();
2693
-
2694
-		$query = $this->db->getQueryBuilder();
2695
-		$query->delete('calendarobjects')
2696
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2697
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2698
-			->executeStatement();
2699
-
2700
-		$query->delete('calendarchanges')
2701
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2702
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2703
-			->executeStatement();
2704
-
2705
-		$query->delete($this->dbObjectPropertiesTable)
2706
-			->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2707
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2708
-			->executeStatement();
2709
-
2710
-		foreach ($uris as $uri) {
2711
-			$this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2712
-		}
2713
-	}
2714
-
2715
-	/**
2716
-	 * Move a calendar from one user to another
2717
-	 *
2718
-	 * @param string $uriName
2719
-	 * @param string $uriOrigin
2720
-	 * @param string $uriDestination
2721
-	 * @param string $newUriName (optional) the new uriName
2722
-	 */
2723
-	public function moveCalendar($uriName, $uriOrigin, $uriDestination, $newUriName = null) {
2724
-		$query = $this->db->getQueryBuilder();
2725
-		$query->update('calendars')
2726
-			->set('principaluri', $query->createNamedParameter($uriDestination))
2727
-			->set('uri', $query->createNamedParameter($newUriName ?: $uriName))
2728
-			->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2729
-			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2730
-			->executeStatement();
2731
-	}
2732
-
2733
-	/**
2734
-	 * read VCalendar data into a VCalendar object
2735
-	 *
2736
-	 * @param string $objectData
2737
-	 * @return VCalendar
2738
-	 */
2739
-	protected function readCalendarData($objectData) {
2740
-		return Reader::read($objectData);
2741
-	}
2742
-
2743
-	/**
2744
-	 * delete all properties from a given calendar object
2745
-	 *
2746
-	 * @param int $calendarId
2747
-	 * @param int $objectId
2748
-	 */
2749
-	protected function purgeProperties($calendarId, $objectId) {
2750
-		$query = $this->db->getQueryBuilder();
2751
-		$query->delete($this->dbObjectPropertiesTable)
2752
-			->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2753
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2754
-		$query->executeStatement();
2755
-	}
2756
-
2757
-	/**
2758
-	 * get ID from a given calendar object
2759
-	 *
2760
-	 * @param int $calendarId
2761
-	 * @param string $uri
2762
-	 * @param int $calendarType
2763
-	 * @return int
2764
-	 */
2765
-	protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2766
-		$query = $this->db->getQueryBuilder();
2767
-		$query->select('id')
2768
-			->from('calendarobjects')
2769
-			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2770
-			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2771
-			->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2772
-
2773
-		$result = $query->executeQuery();
2774
-		$objectIds = $result->fetch();
2775
-		$result->closeCursor();
2776
-
2777
-		if (!isset($objectIds['id'])) {
2778
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2779
-		}
2780
-
2781
-		return (int)$objectIds['id'];
2782
-	}
2783
-
2784
-	/**
2785
-	 * return legacy endpoint principal name to new principal name
2786
-	 *
2787
-	 * @param $principalUri
2788
-	 * @param $toV2
2789
-	 * @return string
2790
-	 */
2791
-	private function convertPrincipal($principalUri, $toV2) {
2792
-		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2793
-			[, $name] = Uri\split($principalUri);
2794
-			if ($toV2 === true) {
2795
-				return "principals/users/$name";
2796
-			}
2797
-			return "principals/$name";
2798
-		}
2799
-		return $principalUri;
2800
-	}
2801
-
2802
-	/**
2803
-	 * adds information about an owner to the calendar data
2804
-	 *
2805
-	 * @param $calendarInfo
2806
-	 */
2807
-	private function addOwnerPrincipal(&$calendarInfo) {
2808
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2809
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2810
-		if (isset($calendarInfo[$ownerPrincipalKey])) {
2811
-			$uri = $calendarInfo[$ownerPrincipalKey];
2812
-		} else {
2813
-			$uri = $calendarInfo['principaluri'];
2814
-		}
2815
-
2816
-		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2817
-		if (isset($principalInformation['{DAV:}displayname'])) {
2818
-			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2819
-		}
2820
-	}
99
+    public const CALENDAR_TYPE_CALENDAR = 0;
100
+    public const CALENDAR_TYPE_SUBSCRIPTION = 1;
101
+
102
+    public const PERSONAL_CALENDAR_URI = 'personal';
103
+    public const PERSONAL_CALENDAR_NAME = 'Personal';
104
+
105
+    public const RESOURCE_BOOKING_CALENDAR_URI = 'calendar';
106
+    public const RESOURCE_BOOKING_CALENDAR_NAME = 'Calendar';
107
+
108
+    /**
109
+     * We need to specify a max date, because we need to stop *somewhere*
110
+     *
111
+     * On 32 bit system the maximum for a signed integer is 2147483647, so
112
+     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
113
+     * in 2038-01-19 to avoid problems when the date is converted
114
+     * to a unix timestamp.
115
+     */
116
+    public const MAX_DATE = '2038-01-01';
117
+
118
+    public const ACCESS_PUBLIC = 4;
119
+    public const CLASSIFICATION_PUBLIC = 0;
120
+    public const CLASSIFICATION_PRIVATE = 1;
121
+    public const CLASSIFICATION_CONFIDENTIAL = 2;
122
+
123
+    /**
124
+     * List of CalDAV properties, and how they map to database field names
125
+     * Add your own properties by simply adding on to this array.
126
+     *
127
+     * Note that only string-based properties are supported here.
128
+     *
129
+     * @var array
130
+     */
131
+    public $propertyMap = [
132
+        '{DAV:}displayname' => 'displayname',
133
+        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
134
+        '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone',
135
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
136
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
137
+    ];
138
+
139
+    /**
140
+     * List of subscription properties, and how they map to database field names.
141
+     *
142
+     * @var array
143
+     */
144
+    public $subscriptionPropertyMap = [
145
+        '{DAV:}displayname' => 'displayname',
146
+        '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate',
147
+        '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder',
148
+        '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor',
149
+        '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos',
150
+        '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms',
151
+        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
152
+    ];
153
+
154
+    /** @var array properties to index */
155
+    public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
156
+        'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
157
+        'ORGANIZER'];
158
+
159
+    /** @var array parameters to index */
160
+    public static $indexParameters = [
161
+        'ATTENDEE' => ['CN'],
162
+        'ORGANIZER' => ['CN'],
163
+    ];
164
+
165
+    /**
166
+     * @var string[] Map of uid => display name
167
+     */
168
+    protected $userDisplayNames;
169
+
170
+    /** @var IDBConnection */
171
+    private $db;
172
+
173
+    /** @var Backend */
174
+    private $calendarSharingBackend;
175
+
176
+    /** @var Principal */
177
+    private $principalBackend;
178
+
179
+    /** @var IUserManager */
180
+    private $userManager;
181
+
182
+    /** @var ISecureRandom */
183
+    private $random;
184
+
185
+    /** @var ILogger */
186
+    private $logger;
187
+
188
+    /** @var IEventDispatcher */
189
+    private $dispatcher;
190
+
191
+    /** @var EventDispatcherInterface */
192
+    private $legacyDispatcher;
193
+
194
+    /** @var bool */
195
+    private $legacyEndpoint;
196
+
197
+    /** @var string */
198
+    private $dbObjectPropertiesTable = 'calendarobjects_props';
199
+
200
+    /**
201
+     * CalDavBackend constructor.
202
+     *
203
+     * @param IDBConnection $db
204
+     * @param Principal $principalBackend
205
+     * @param IUserManager $userManager
206
+     * @param IGroupManager $groupManager
207
+     * @param ISecureRandom $random
208
+     * @param ILogger $logger
209
+     * @param IEventDispatcher $dispatcher
210
+     * @param EventDispatcherInterface $legacyDispatcher
211
+     * @param bool $legacyEndpoint
212
+     */
213
+    public function __construct(IDBConnection $db,
214
+                                Principal $principalBackend,
215
+                                IUserManager $userManager,
216
+                                IGroupManager $groupManager,
217
+                                ISecureRandom $random,
218
+                                ILogger $logger,
219
+                                IEventDispatcher $dispatcher,
220
+                                EventDispatcherInterface $legacyDispatcher,
221
+                                bool $legacyEndpoint = false) {
222
+        $this->db = $db;
223
+        $this->principalBackend = $principalBackend;
224
+        $this->userManager = $userManager;
225
+        $this->calendarSharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
226
+        $this->random = $random;
227
+        $this->logger = $logger;
228
+        $this->dispatcher = $dispatcher;
229
+        $this->legacyDispatcher = $legacyDispatcher;
230
+        $this->legacyEndpoint = $legacyEndpoint;
231
+    }
232
+
233
+    /**
234
+     * Return the number of calendars for a principal
235
+     *
236
+     * By default this excludes the automatically generated birthday calendar
237
+     *
238
+     * @param $principalUri
239
+     * @param bool $excludeBirthday
240
+     * @return int
241
+     */
242
+    public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
243
+        $principalUri = $this->convertPrincipal($principalUri, true);
244
+        $query = $this->db->getQueryBuilder();
245
+        $query->select($query->func()->count('*'))
246
+            ->from('calendars');
247
+
248
+        if ($principalUri === '') {
249
+            $query->where($query->expr()->emptyString('principaluri'));
250
+        } else {
251
+            $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
252
+        }
253
+
254
+        if ($excludeBirthday) {
255
+            $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
256
+        }
257
+
258
+        $result = $query->executeQuery();
259
+        $column = (int)$result->fetchOne();
260
+        $result->closeCursor();
261
+        return $column;
262
+    }
263
+
264
+    /**
265
+     * Returns a list of calendars for a principal.
266
+     *
267
+     * Every project is an array with the following keys:
268
+     *  * id, a unique id that will be used by other functions to modify the
269
+     *    calendar. This can be the same as the uri or a database key.
270
+     *  * uri, which the basename of the uri with which the calendar is
271
+     *    accessed.
272
+     *  * principaluri. The owner of the calendar. Almost always the same as
273
+     *    principalUri passed to this method.
274
+     *
275
+     * Furthermore it can contain webdav properties in clark notation. A very
276
+     * common one is '{DAV:}displayname'.
277
+     *
278
+     * Many clients also require:
279
+     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
280
+     * For this property, you can just return an instance of
281
+     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
282
+     *
283
+     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
284
+     * ACL will automatically be put in read-only mode.
285
+     *
286
+     * @param string $principalUri
287
+     * @return array
288
+     */
289
+    public function getCalendarsForUser($principalUri) {
290
+        $principalUriOriginal = $principalUri;
291
+        $principalUri = $this->convertPrincipal($principalUri, true);
292
+        $fields = array_values($this->propertyMap);
293
+        $fields[] = 'id';
294
+        $fields[] = 'uri';
295
+        $fields[] = 'synctoken';
296
+        $fields[] = 'components';
297
+        $fields[] = 'principaluri';
298
+        $fields[] = 'transparent';
299
+
300
+        // Making fields a comma-delimited list
301
+        $query = $this->db->getQueryBuilder();
302
+        $query->select($fields)
303
+            ->from('calendars')
304
+            ->orderBy('calendarorder', 'ASC');
305
+
306
+        if ($principalUri === '') {
307
+            $query->where($query->expr()->emptyString('principaluri'));
308
+        } else {
309
+            $query->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
310
+        }
311
+
312
+        $result = $query->executeQuery();
313
+
314
+        $calendars = [];
315
+        while ($row = $result->fetch()) {
316
+            $row['principaluri'] = (string) $row['principaluri'];
317
+            $components = [];
318
+            if ($row['components']) {
319
+                $components = explode(',',$row['components']);
320
+            }
321
+
322
+            $calendar = [
323
+                'id' => $row['id'],
324
+                'uri' => $row['uri'],
325
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
326
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
327
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
328
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
330
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
331
+            ];
332
+
333
+            foreach ($this->propertyMap as $xmlName => $dbName) {
334
+                $calendar[$xmlName] = $row[$dbName];
335
+            }
336
+
337
+            $this->addOwnerPrincipal($calendar);
338
+
339
+            if (!isset($calendars[$calendar['id']])) {
340
+                $calendars[$calendar['id']] = $calendar;
341
+            }
342
+        }
343
+        $result->closeCursor();
344
+
345
+        // query for shared calendars
346
+        $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
347
+        $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUriOriginal));
348
+
349
+        $principals[] = $principalUri;
350
+
351
+        $fields = array_values($this->propertyMap);
352
+        $fields[] = 'a.id';
353
+        $fields[] = 'a.uri';
354
+        $fields[] = 'a.synctoken';
355
+        $fields[] = 'a.components';
356
+        $fields[] = 'a.principaluri';
357
+        $fields[] = 'a.transparent';
358
+        $fields[] = 's.access';
359
+        $query = $this->db->getQueryBuilder();
360
+        $query->select($fields)
361
+            ->from('dav_shares', 's')
362
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
363
+            ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
364
+            ->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
365
+            ->setParameter('type', 'calendar')
366
+            ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY);
367
+
368
+        $result = $query->executeQuery();
369
+
370
+        $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
371
+        while ($row = $result->fetch()) {
372
+            $row['principaluri'] = (string) $row['principaluri'];
373
+            if ($row['principaluri'] === $principalUri) {
374
+                continue;
375
+            }
376
+
377
+            $readOnly = (int) $row['access'] === Backend::ACCESS_READ;
378
+            if (isset($calendars[$row['id']])) {
379
+                if ($readOnly) {
380
+                    // New share can not have more permissions then the old one.
381
+                    continue;
382
+                }
383
+                if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
384
+                    $calendars[$row['id']][$readOnlyPropertyName] === 0) {
385
+                    // Old share is already read-write, no more permissions can be gained
386
+                    continue;
387
+                }
388
+            }
389
+
390
+            [, $name] = Uri\split($row['principaluri']);
391
+            $uri = $row['uri'] . '_shared_by_' . $name;
392
+            $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
393
+            $components = [];
394
+            if ($row['components']) {
395
+                $components = explode(',',$row['components']);
396
+            }
397
+            $calendar = [
398
+                'id' => $row['id'],
399
+                'uri' => $uri,
400
+                'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
401
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
402
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
403
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
406
+                $readOnlyPropertyName => $readOnly,
407
+            ];
408
+
409
+            foreach ($this->propertyMap as $xmlName => $dbName) {
410
+                $calendar[$xmlName] = $row[$dbName];
411
+            }
412
+
413
+            $this->addOwnerPrincipal($calendar);
414
+
415
+            $calendars[$calendar['id']] = $calendar;
416
+        }
417
+        $result->closeCursor();
418
+
419
+        return array_values($calendars);
420
+    }
421
+
422
+    /**
423
+     * @param $principalUri
424
+     * @return array
425
+     */
426
+    public function getUsersOwnCalendars($principalUri) {
427
+        $principalUri = $this->convertPrincipal($principalUri, true);
428
+        $fields = array_values($this->propertyMap);
429
+        $fields[] = 'id';
430
+        $fields[] = 'uri';
431
+        $fields[] = 'synctoken';
432
+        $fields[] = 'components';
433
+        $fields[] = 'principaluri';
434
+        $fields[] = 'transparent';
435
+        // Making fields a comma-delimited list
436
+        $query = $this->db->getQueryBuilder();
437
+        $query->select($fields)->from('calendars')
438
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
439
+            ->orderBy('calendarorder', 'ASC');
440
+        $stmt = $query->executeQuery();
441
+        $calendars = [];
442
+        while ($row = $stmt->fetch()) {
443
+            $row['principaluri'] = (string) $row['principaluri'];
444
+            $components = [];
445
+            if ($row['components']) {
446
+                $components = explode(',',$row['components']);
447
+            }
448
+            $calendar = [
449
+                'id' => $row['id'],
450
+                'uri' => $row['uri'],
451
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
452
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
453
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
454
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
456
+            ];
457
+            foreach ($this->propertyMap as $xmlName => $dbName) {
458
+                $calendar[$xmlName] = $row[$dbName];
459
+            }
460
+
461
+            $this->addOwnerPrincipal($calendar);
462
+
463
+            if (!isset($calendars[$calendar['id']])) {
464
+                $calendars[$calendar['id']] = $calendar;
465
+            }
466
+        }
467
+        $stmt->closeCursor();
468
+        return array_values($calendars);
469
+    }
470
+
471
+
472
+    /**
473
+     * @param $uid
474
+     * @return string
475
+     */
476
+    private function getUserDisplayName($uid) {
477
+        if (!isset($this->userDisplayNames[$uid])) {
478
+            $user = $this->userManager->get($uid);
479
+
480
+            if ($user instanceof IUser) {
481
+                $this->userDisplayNames[$uid] = $user->getDisplayName();
482
+            } else {
483
+                $this->userDisplayNames[$uid] = $uid;
484
+            }
485
+        }
486
+
487
+        return $this->userDisplayNames[$uid];
488
+    }
489
+
490
+    /**
491
+     * @return array
492
+     */
493
+    public function getPublicCalendars() {
494
+        $fields = array_values($this->propertyMap);
495
+        $fields[] = 'a.id';
496
+        $fields[] = 'a.uri';
497
+        $fields[] = 'a.synctoken';
498
+        $fields[] = 'a.components';
499
+        $fields[] = 'a.principaluri';
500
+        $fields[] = 'a.transparent';
501
+        $fields[] = 's.access';
502
+        $fields[] = 's.publicuri';
503
+        $calendars = [];
504
+        $query = $this->db->getQueryBuilder();
505
+        $result = $query->select($fields)
506
+            ->from('dav_shares', 's')
507
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
508
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
509
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
510
+            ->executeQuery();
511
+
512
+        while ($row = $result->fetch()) {
513
+            $row['principaluri'] = (string) $row['principaluri'];
514
+            [, $name] = Uri\split($row['principaluri']);
515
+            $row['displayname'] = $row['displayname'] . "($name)";
516
+            $components = [];
517
+            if ($row['components']) {
518
+                $components = explode(',',$row['components']);
519
+            }
520
+            $calendar = [
521
+                'id' => $row['id'],
522
+                'uri' => $row['publicuri'],
523
+                'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
+                '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
525
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
526
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
+                '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
528
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
530
+                '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
531
+            ];
532
+
533
+            foreach ($this->propertyMap as $xmlName => $dbName) {
534
+                $calendar[$xmlName] = $row[$dbName];
535
+            }
536
+
537
+            $this->addOwnerPrincipal($calendar);
538
+
539
+            if (!isset($calendars[$calendar['id']])) {
540
+                $calendars[$calendar['id']] = $calendar;
541
+            }
542
+        }
543
+        $result->closeCursor();
544
+
545
+        return array_values($calendars);
546
+    }
547
+
548
+    /**
549
+     * @param string $uri
550
+     * @return array
551
+     * @throws NotFound
552
+     */
553
+    public function getPublicCalendar($uri) {
554
+        $fields = array_values($this->propertyMap);
555
+        $fields[] = 'a.id';
556
+        $fields[] = 'a.uri';
557
+        $fields[] = 'a.synctoken';
558
+        $fields[] = 'a.components';
559
+        $fields[] = 'a.principaluri';
560
+        $fields[] = 'a.transparent';
561
+        $fields[] = 's.access';
562
+        $fields[] = 's.publicuri';
563
+        $query = $this->db->getQueryBuilder();
564
+        $result = $query->select($fields)
565
+            ->from('dav_shares', 's')
566
+            ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
567
+            ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
568
+            ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
569
+            ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
570
+            ->executeQuery();
571
+
572
+        $row = $result->fetch();
573
+
574
+        $result->closeCursor();
575
+
576
+        if ($row === false) {
577
+            throw new NotFound('Node with name \'' . $uri . '\' could not be found');
578
+        }
579
+
580
+        $row['principaluri'] = (string) $row['principaluri'];
581
+        [, $name] = Uri\split($row['principaluri']);
582
+        $row['displayname'] = $row['displayname'] . ' ' . "($name)";
583
+        $components = [];
584
+        if ($row['components']) {
585
+            $components = explode(',',$row['components']);
586
+        }
587
+        $calendar = [
588
+            'id' => $row['id'],
589
+            'uri' => $row['publicuri'],
590
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
591
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
592
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
593
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
595
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
597
+            '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
598
+        ];
599
+
600
+        foreach ($this->propertyMap as $xmlName => $dbName) {
601
+            $calendar[$xmlName] = $row[$dbName];
602
+        }
603
+
604
+        $this->addOwnerPrincipal($calendar);
605
+
606
+        return $calendar;
607
+    }
608
+
609
+    /**
610
+     * @param string $principal
611
+     * @param string $uri
612
+     * @return array|null
613
+     */
614
+    public function getCalendarByUri($principal, $uri) {
615
+        $fields = array_values($this->propertyMap);
616
+        $fields[] = 'id';
617
+        $fields[] = 'uri';
618
+        $fields[] = 'synctoken';
619
+        $fields[] = 'components';
620
+        $fields[] = 'principaluri';
621
+        $fields[] = 'transparent';
622
+
623
+        // Making fields a comma-delimited list
624
+        $query = $this->db->getQueryBuilder();
625
+        $query->select($fields)->from('calendars')
626
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
627
+            ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
628
+            ->setMaxResults(1);
629
+        $stmt = $query->executeQuery();
630
+
631
+        $row = $stmt->fetch();
632
+        $stmt->closeCursor();
633
+        if ($row === false) {
634
+            return null;
635
+        }
636
+
637
+        $row['principaluri'] = (string) $row['principaluri'];
638
+        $components = [];
639
+        if ($row['components']) {
640
+            $components = explode(',',$row['components']);
641
+        }
642
+
643
+        $calendar = [
644
+            'id' => $row['id'],
645
+            'uri' => $row['uri'],
646
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
647
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
648
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
649
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
651
+        ];
652
+
653
+        foreach ($this->propertyMap as $xmlName => $dbName) {
654
+            $calendar[$xmlName] = $row[$dbName];
655
+        }
656
+
657
+        $this->addOwnerPrincipal($calendar);
658
+
659
+        return $calendar;
660
+    }
661
+
662
+    /**
663
+     * @param $calendarId
664
+     * @return array|null
665
+     */
666
+    public function getCalendarById($calendarId) {
667
+        $fields = array_values($this->propertyMap);
668
+        $fields[] = 'id';
669
+        $fields[] = 'uri';
670
+        $fields[] = 'synctoken';
671
+        $fields[] = 'components';
672
+        $fields[] = 'principaluri';
673
+        $fields[] = 'transparent';
674
+
675
+        // Making fields a comma-delimited list
676
+        $query = $this->db->getQueryBuilder();
677
+        $query->select($fields)->from('calendars')
678
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
679
+            ->setMaxResults(1);
680
+        $stmt = $query->executeQuery();
681
+
682
+        $row = $stmt->fetch();
683
+        $stmt->closeCursor();
684
+        if ($row === false) {
685
+            return null;
686
+        }
687
+
688
+        $row['principaluri'] = (string) $row['principaluri'];
689
+        $components = [];
690
+        if ($row['components']) {
691
+            $components = explode(',',$row['components']);
692
+        }
693
+
694
+        $calendar = [
695
+            'id' => $row['id'],
696
+            'uri' => $row['uri'],
697
+            'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
698
+            '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
699
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
700
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
+            '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
702
+        ];
703
+
704
+        foreach ($this->propertyMap as $xmlName => $dbName) {
705
+            $calendar[$xmlName] = $row[$dbName];
706
+        }
707
+
708
+        $this->addOwnerPrincipal($calendar);
709
+
710
+        return $calendar;
711
+    }
712
+
713
+    /**
714
+     * @param $subscriptionId
715
+     */
716
+    public function getSubscriptionById($subscriptionId) {
717
+        $fields = array_values($this->subscriptionPropertyMap);
718
+        $fields[] = 'id';
719
+        $fields[] = 'uri';
720
+        $fields[] = 'source';
721
+        $fields[] = 'synctoken';
722
+        $fields[] = 'principaluri';
723
+        $fields[] = 'lastmodified';
724
+
725
+        $query = $this->db->getQueryBuilder();
726
+        $query->select($fields)
727
+            ->from('calendarsubscriptions')
728
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
729
+            ->orderBy('calendarorder', 'asc');
730
+        $stmt = $query->executeQuery();
731
+
732
+        $row = $stmt->fetch();
733
+        $stmt->closeCursor();
734
+        if ($row === false) {
735
+            return null;
736
+        }
737
+
738
+        $row['principaluri'] = (string) $row['principaluri'];
739
+        $subscription = [
740
+            'id' => $row['id'],
741
+            'uri' => $row['uri'],
742
+            'principaluri' => $row['principaluri'],
743
+            'source' => $row['source'],
744
+            'lastmodified' => $row['lastmodified'],
745
+            '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
+            '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
747
+        ];
748
+
749
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
750
+            if (!is_null($row[$dbName])) {
751
+                $subscription[$xmlName] = $row[$dbName];
752
+            }
753
+        }
754
+
755
+        return $subscription;
756
+    }
757
+
758
+    /**
759
+     * Creates a new calendar for a principal.
760
+     *
761
+     * If the creation was a success, an id must be returned that can be used to reference
762
+     * this calendar in other methods, such as updateCalendar.
763
+     *
764
+     * @param string $principalUri
765
+     * @param string $calendarUri
766
+     * @param array $properties
767
+     * @return int
768
+     */
769
+    public function createCalendar($principalUri, $calendarUri, array $properties) {
770
+        $values = [
771
+            'principaluri' => $this->convertPrincipal($principalUri, true),
772
+            'uri' => $calendarUri,
773
+            'synctoken' => 1,
774
+            'transparent' => 0,
775
+            'components' => 'VEVENT,VTODO',
776
+            'displayname' => $calendarUri
777
+        ];
778
+
779
+        // Default value
780
+        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
781
+        if (isset($properties[$sccs])) {
782
+            if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
783
+                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
784
+            }
785
+            $values['components'] = implode(',',$properties[$sccs]->getValue());
786
+        } elseif (isset($properties['components'])) {
787
+            // Allow to provide components internally without having
788
+            // to create a SupportedCalendarComponentSet object
789
+            $values['components'] = $properties['components'];
790
+        }
791
+
792
+        $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
793
+        if (isset($properties[$transp])) {
794
+            $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
795
+        }
796
+
797
+        foreach ($this->propertyMap as $xmlName => $dbName) {
798
+            if (isset($properties[$xmlName])) {
799
+                $values[$dbName] = $properties[$xmlName];
800
+            }
801
+        }
802
+
803
+        $query = $this->db->getQueryBuilder();
804
+        $query->insert('calendars');
805
+        foreach ($values as $column => $value) {
806
+            $query->setValue($column, $query->createNamedParameter($value));
807
+        }
808
+        $query->executeStatement();
809
+        $calendarId = $query->getLastInsertId();
810
+
811
+        $calendarData = $this->getCalendarById($calendarId);
812
+        $this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
813
+
814
+        return $calendarId;
815
+    }
816
+
817
+    /**
818
+     * Updates properties for a calendar.
819
+     *
820
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
821
+     * To do the actual updates, you must tell this object which properties
822
+     * you're going to process with the handle() method.
823
+     *
824
+     * Calling the handle method is like telling the PropPatch object "I
825
+     * promise I can handle updating this property".
826
+     *
827
+     * Read the PropPatch documentation for more info and examples.
828
+     *
829
+     * @param mixed $calendarId
830
+     * @param PropPatch $propPatch
831
+     * @return void
832
+     */
833
+    public function updateCalendar($calendarId, PropPatch $propPatch) {
834
+        $supportedProperties = array_keys($this->propertyMap);
835
+        $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
836
+
837
+        $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
838
+            $newValues = [];
839
+            foreach ($mutations as $propertyName => $propertyValue) {
840
+                switch ($propertyName) {
841
+                    case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
842
+                        $fieldName = 'transparent';
843
+                        $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
844
+                        break;
845
+                    default:
846
+                        $fieldName = $this->propertyMap[$propertyName];
847
+                        $newValues[$fieldName] = $propertyValue;
848
+                        break;
849
+                }
850
+            }
851
+            $query = $this->db->getQueryBuilder();
852
+            $query->update('calendars');
853
+            foreach ($newValues as $fieldName => $value) {
854
+                $query->set($fieldName, $query->createNamedParameter($value));
855
+            }
856
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
857
+            $query->executeStatement();
858
+
859
+            $this->addChange($calendarId, "", 2);
860
+
861
+            $calendarData = $this->getCalendarById($calendarId);
862
+            $shares = $this->getShares($calendarId);
863
+            $this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
864
+
865
+            return true;
866
+        });
867
+    }
868
+
869
+    /**
870
+     * Delete a calendar and all it's objects
871
+     *
872
+     * @param mixed $calendarId
873
+     * @return void
874
+     */
875
+    public function deleteCalendar($calendarId) {
876
+        $calendarData = $this->getCalendarById($calendarId);
877
+        $shares = $this->getShares($calendarId);
878
+
879
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `calendartype` = ?');
880
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
881
+
882
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
883
+        $stmt->execute([$calendarId]);
884
+
885
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ? AND `calendartype` = ?');
886
+        $stmt->execute([$calendarId, self::CALENDAR_TYPE_CALENDAR]);
887
+
888
+        $this->calendarSharingBackend->deleteAllShares($calendarId);
889
+
890
+        $query = $this->db->getQueryBuilder();
891
+        $query->delete($this->dbObjectPropertiesTable)
892
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
893
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)))
894
+            ->executeStatement();
895
+
896
+        // Only dispatch if we actually deleted anything
897
+        if ($calendarData) {
898
+            $this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
899
+        }
900
+    }
901
+
902
+    /**
903
+     * Delete all of an user's shares
904
+     *
905
+     * @param string $principaluri
906
+     * @return void
907
+     */
908
+    public function deleteAllSharesByUser($principaluri) {
909
+        $this->calendarSharingBackend->deleteAllSharesByUser($principaluri);
910
+    }
911
+
912
+    /**
913
+     * Returns all calendar objects within a calendar.
914
+     *
915
+     * Every item contains an array with the following keys:
916
+     *   * calendardata - The iCalendar-compatible calendar data
917
+     *   * uri - a unique key which will be used to construct the uri. This can
918
+     *     be any arbitrary string, but making sure it ends with '.ics' is a
919
+     *     good idea. This is only the basename, or filename, not the full
920
+     *     path.
921
+     *   * lastmodified - a timestamp of the last modification time
922
+     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
923
+     *   '"abcdef"')
924
+     *   * size - The size of the calendar objects, in bytes.
925
+     *   * component - optional, a string containing the type of object, such
926
+     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
927
+     *     the Content-Type header.
928
+     *
929
+     * Note that the etag is optional, but it's highly encouraged to return for
930
+     * speed reasons.
931
+     *
932
+     * The calendardata is also optional. If it's not returned
933
+     * 'getCalendarObject' will be called later, which *is* expected to return
934
+     * calendardata.
935
+     *
936
+     * If neither etag or size are specified, the calendardata will be
937
+     * used/fetched to determine these numbers. If both are specified the
938
+     * amount of times this is needed is reduced by a great degree.
939
+     *
940
+     * @param mixed $calendarId
941
+     * @param int $calendarType
942
+     * @return array
943
+     */
944
+    public function getCalendarObjects($calendarId, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
945
+        $query = $this->db->getQueryBuilder();
946
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
947
+            ->from('calendarobjects')
948
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
949
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
950
+        $stmt = $query->executeQuery();
951
+
952
+        $result = [];
953
+        foreach ($stmt->fetchAll() as $row) {
954
+            $result[] = [
955
+                'id' => $row['id'],
956
+                'uri' => $row['uri'],
957
+                'lastmodified' => $row['lastmodified'],
958
+                'etag' => '"' . $row['etag'] . '"',
959
+                'calendarid' => $row['calendarid'],
960
+                'size' => (int)$row['size'],
961
+                'component' => strtolower($row['componenttype']),
962
+                'classification' => (int)$row['classification']
963
+            ];
964
+        }
965
+        $stmt->closeCursor();
966
+
967
+        return $result;
968
+    }
969
+
970
+    /**
971
+     * Returns information from a single calendar object, based on it's object
972
+     * uri.
973
+     *
974
+     * The object uri is only the basename, or filename and not a full path.
975
+     *
976
+     * The returned array must have the same keys as getCalendarObjects. The
977
+     * 'calendardata' object is required here though, while it's not required
978
+     * for getCalendarObjects.
979
+     *
980
+     * This method must return null if the object did not exist.
981
+     *
982
+     * @param mixed $calendarId
983
+     * @param string $objectUri
984
+     * @param int $calendarType
985
+     * @return array|null
986
+     */
987
+    public function getCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
988
+        $query = $this->db->getQueryBuilder();
989
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
990
+            ->from('calendarobjects')
991
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
992
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
993
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
994
+        $stmt = $query->executeQuery();
995
+        $row = $stmt->fetch();
996
+        $stmt->closeCursor();
997
+
998
+        if (!$row) {
999
+            return null;
1000
+        }
1001
+
1002
+        return [
1003
+            'id' => $row['id'],
1004
+            'uri' => $row['uri'],
1005
+            'lastmodified' => $row['lastmodified'],
1006
+            'etag' => '"' . $row['etag'] . '"',
1007
+            'calendarid' => $row['calendarid'],
1008
+            'size' => (int)$row['size'],
1009
+            'calendardata' => $this->readBlob($row['calendardata']),
1010
+            'component' => strtolower($row['componenttype']),
1011
+            'classification' => (int)$row['classification']
1012
+        ];
1013
+    }
1014
+
1015
+    /**
1016
+     * Returns a list of calendar objects.
1017
+     *
1018
+     * This method should work identical to getCalendarObject, but instead
1019
+     * return all the calendar objects in the list as an array.
1020
+     *
1021
+     * If the backend supports this, it may allow for some speed-ups.
1022
+     *
1023
+     * @param mixed $calendarId
1024
+     * @param string[] $uris
1025
+     * @param int $calendarType
1026
+     * @return array
1027
+     */
1028
+    public function getMultipleCalendarObjects($calendarId, array $uris, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1029
+        if (empty($uris)) {
1030
+            return [];
1031
+        }
1032
+
1033
+        $chunks = array_chunk($uris, 100);
1034
+        $objects = [];
1035
+
1036
+        $query = $this->db->getQueryBuilder();
1037
+        $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
1038
+            ->from('calendarobjects')
1039
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1040
+            ->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
1041
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1042
+
1043
+        foreach ($chunks as $uris) {
1044
+            $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
1045
+            $result = $query->executeQuery();
1046
+
1047
+            while ($row = $result->fetch()) {
1048
+                $objects[] = [
1049
+                    'id' => $row['id'],
1050
+                    'uri' => $row['uri'],
1051
+                    'lastmodified' => $row['lastmodified'],
1052
+                    'etag' => '"' . $row['etag'] . '"',
1053
+                    'calendarid' => $row['calendarid'],
1054
+                    'size' => (int)$row['size'],
1055
+                    'calendardata' => $this->readBlob($row['calendardata']),
1056
+                    'component' => strtolower($row['componenttype']),
1057
+                    'classification' => (int)$row['classification']
1058
+                ];
1059
+            }
1060
+            $result->closeCursor();
1061
+        }
1062
+
1063
+        return $objects;
1064
+    }
1065
+
1066
+    /**
1067
+     * Creates a new calendar object.
1068
+     *
1069
+     * The object uri is only the basename, or filename and not a full path.
1070
+     *
1071
+     * It is possible return an etag from this function, which will be used in
1072
+     * the response to this PUT request. Note that the ETag must be surrounded
1073
+     * by double-quotes.
1074
+     *
1075
+     * However, you should only really return this ETag if you don't mangle the
1076
+     * calendar-data. If the result of a subsequent GET to this object is not
1077
+     * the exact same as this request body, you should omit the ETag.
1078
+     *
1079
+     * @param mixed $calendarId
1080
+     * @param string $objectUri
1081
+     * @param string $calendarData
1082
+     * @param int $calendarType
1083
+     * @return string
1084
+     */
1085
+    public function createCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1086
+        $extraData = $this->getDenormalizedData($calendarData);
1087
+
1088
+        $q = $this->db->getQueryBuilder();
1089
+        $q->select($q->func()->count('*'))
1090
+            ->from('calendarobjects')
1091
+            ->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
1092
+            ->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])))
1093
+            ->andWhere($q->expr()->eq('calendartype', $q->createNamedParameter($calendarType)));
1094
+
1095
+        $result = $q->executeQuery();
1096
+        $count = (int) $result->fetchOne();
1097
+        $result->closeCursor();
1098
+
1099
+        if ($count !== 0) {
1100
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
1101
+        }
1102
+
1103
+        $query = $this->db->getQueryBuilder();
1104
+        $query->insert('calendarobjects')
1105
+            ->values([
1106
+                'calendarid' => $query->createNamedParameter($calendarId),
1107
+                'uri' => $query->createNamedParameter($objectUri),
1108
+                'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
1109
+                'lastmodified' => $query->createNamedParameter(time()),
1110
+                'etag' => $query->createNamedParameter($extraData['etag']),
1111
+                'size' => $query->createNamedParameter($extraData['size']),
1112
+                'componenttype' => $query->createNamedParameter($extraData['componentType']),
1113
+                'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
1114
+                'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
1115
+                'classification' => $query->createNamedParameter($extraData['classification']),
1116
+                'uid' => $query->createNamedParameter($extraData['uid']),
1117
+                'calendartype' => $query->createNamedParameter($calendarType),
1118
+            ])
1119
+            ->executeStatement();
1120
+
1121
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1122
+        $this->addChange($calendarId, $objectUri, 1, $calendarType);
1123
+
1124
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1125
+        if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1126
+            $calendarRow = $this->getCalendarById($calendarId);
1127
+            $shares = $this->getShares($calendarId);
1128
+
1129
+            $this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1130
+        } else {
1131
+            $subscriptionRow = $this->getSubscriptionById($calendarId);
1132
+
1133
+            $this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1134
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1135
+                '\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1136
+                [
1137
+                    'subscriptionId' => $calendarId,
1138
+                    'calendarData' => $subscriptionRow,
1139
+                    'shares' => [],
1140
+                    'objectData' => $objectRow,
1141
+                ]
1142
+            ));
1143
+        }
1144
+
1145
+        return '"' . $extraData['etag'] . '"';
1146
+    }
1147
+
1148
+    /**
1149
+     * Updates an existing calendarobject, based on it's uri.
1150
+     *
1151
+     * The object uri is only the basename, or filename and not a full path.
1152
+     *
1153
+     * It is possible return an etag from this function, which will be used in
1154
+     * the response to this PUT request. Note that the ETag must be surrounded
1155
+     * by double-quotes.
1156
+     *
1157
+     * However, you should only really return this ETag if you don't mangle the
1158
+     * calendar-data. If the result of a subsequent GET to this object is not
1159
+     * the exact same as this request body, you should omit the ETag.
1160
+     *
1161
+     * @param mixed $calendarId
1162
+     * @param string $objectUri
1163
+     * @param string $calendarData
1164
+     * @param int $calendarType
1165
+     * @return string
1166
+     */
1167
+    public function updateCalendarObject($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1168
+        $extraData = $this->getDenormalizedData($calendarData);
1169
+        $query = $this->db->getQueryBuilder();
1170
+        $query->update('calendarobjects')
1171
+                ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1172
+                ->set('lastmodified', $query->createNamedParameter(time()))
1173
+                ->set('etag', $query->createNamedParameter($extraData['etag']))
1174
+                ->set('size', $query->createNamedParameter($extraData['size']))
1175
+                ->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1176
+                ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1177
+                ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1178
+                ->set('classification', $query->createNamedParameter($extraData['classification']))
1179
+                ->set('uid', $query->createNamedParameter($extraData['uid']))
1180
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1181
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1182
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)))
1183
+            ->executeStatement();
1184
+
1185
+        $this->updateProperties($calendarId, $objectUri, $calendarData, $calendarType);
1186
+        $this->addChange($calendarId, $objectUri, 2, $calendarType);
1187
+
1188
+        $objectRow = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1189
+        if (is_array($objectRow)) {
1190
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1191
+                $calendarRow = $this->getCalendarById($calendarId);
1192
+                $shares = $this->getShares($calendarId);
1193
+
1194
+                $this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1195
+            } else {
1196
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1197
+
1198
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1199
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1200
+                    '\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1201
+                    [
1202
+                        'subscriptionId' => $calendarId,
1203
+                        'calendarData' => $subscriptionRow,
1204
+                        'shares' => [],
1205
+                        'objectData' => $objectRow,
1206
+                    ]
1207
+                ));
1208
+            }
1209
+        }
1210
+
1211
+        return '"' . $extraData['etag'] . '"';
1212
+    }
1213
+
1214
+    /**
1215
+     * @param int $calendarObjectId
1216
+     * @param int $classification
1217
+     */
1218
+    public function setClassification($calendarObjectId, $classification) {
1219
+        if (!in_array($classification, [
1220
+            self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1221
+        ])) {
1222
+            throw new \InvalidArgumentException();
1223
+        }
1224
+        $query = $this->db->getQueryBuilder();
1225
+        $query->update('calendarobjects')
1226
+            ->set('classification', $query->createNamedParameter($classification))
1227
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1228
+            ->executeStatement();
1229
+    }
1230
+
1231
+    /**
1232
+     * Deletes an existing calendar object.
1233
+     *
1234
+     * The object uri is only the basename, or filename and not a full path.
1235
+     *
1236
+     * @param mixed $calendarId
1237
+     * @param string $objectUri
1238
+     * @param int $calendarType
1239
+     * @return void
1240
+     */
1241
+    public function deleteCalendarObject($calendarId, $objectUri, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1242
+        $data = $this->getCalendarObject($calendarId, $objectUri, $calendarType);
1243
+        if (is_array($data)) {
1244
+            if ($calendarType === self::CALENDAR_TYPE_CALENDAR) {
1245
+                $calendarRow = $this->getCalendarById($calendarId);
1246
+                $shares = $this->getShares($calendarId);
1247
+
1248
+                $this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1249
+            } else {
1250
+                $subscriptionRow = $this->getSubscriptionById($calendarId);
1251
+
1252
+                $this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1253
+                $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1254
+                    '\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1255
+                    [
1256
+                        'subscriptionId' => $calendarId,
1257
+                        'calendarData' => $subscriptionRow,
1258
+                        'shares' => [],
1259
+                        'objectData' => $data,
1260
+                    ]
1261
+                ));
1262
+            }
1263
+        }
1264
+
1265
+        $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ? AND `calendartype` = ?');
1266
+        $stmt->execute([$calendarId, $objectUri, $calendarType]);
1267
+
1268
+        if (is_array($data)) {
1269
+            $this->purgeProperties($calendarId, $data['id'], $calendarType);
1270
+        }
1271
+
1272
+        $this->addChange($calendarId, $objectUri, 3, $calendarType);
1273
+    }
1274
+
1275
+    /**
1276
+     * Performs a calendar-query on the contents of this calendar.
1277
+     *
1278
+     * The calendar-query is defined in RFC4791 : CalDAV. Using the
1279
+     * calendar-query it is possible for a client to request a specific set of
1280
+     * object, based on contents of iCalendar properties, date-ranges and
1281
+     * iCalendar component types (VTODO, VEVENT).
1282
+     *
1283
+     * This method should just return a list of (relative) urls that match this
1284
+     * query.
1285
+     *
1286
+     * The list of filters are specified as an array. The exact array is
1287
+     * documented by Sabre\CalDAV\CalendarQueryParser.
1288
+     *
1289
+     * Note that it is extremely likely that getCalendarObject for every path
1290
+     * returned from this method will be called almost immediately after. You
1291
+     * may want to anticipate this to speed up these requests.
1292
+     *
1293
+     * This method provides a default implementation, which parses *all* the
1294
+     * iCalendar objects in the specified calendar.
1295
+     *
1296
+     * This default may well be good enough for personal use, and calendars
1297
+     * that aren't very large. But if you anticipate high usage, big calendars
1298
+     * or high loads, you are strongly advised to optimize certain paths.
1299
+     *
1300
+     * The best way to do so is override this method and to optimize
1301
+     * specifically for 'common filters'.
1302
+     *
1303
+     * Requests that are extremely common are:
1304
+     *   * requests for just VEVENTS
1305
+     *   * requests for just VTODO
1306
+     *   * requests with a time-range-filter on either VEVENT or VTODO.
1307
+     *
1308
+     * ..and combinations of these requests. It may not be worth it to try to
1309
+     * handle every possible situation and just rely on the (relatively
1310
+     * easy to use) CalendarQueryValidator to handle the rest.
1311
+     *
1312
+     * Note that especially time-range-filters may be difficult to parse. A
1313
+     * time-range filter specified on a VEVENT must for instance also handle
1314
+     * recurrence rules correctly.
1315
+     * A good example of how to interprete all these filters can also simply
1316
+     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1317
+     * as possible, so it gives you a good idea on what type of stuff you need
1318
+     * to think of.
1319
+     *
1320
+     * @param mixed $calendarId
1321
+     * @param array $filters
1322
+     * @param int $calendarType
1323
+     * @return array
1324
+     */
1325
+    public function calendarQuery($calendarId, array $filters, $calendarType = self::CALENDAR_TYPE_CALENDAR):array {
1326
+        $componentType = null;
1327
+        $requirePostFilter = true;
1328
+        $timeRange = null;
1329
+
1330
+        // if no filters were specified, we don't need to filter after a query
1331
+        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1332
+            $requirePostFilter = false;
1333
+        }
1334
+
1335
+        // Figuring out if there's a component filter
1336
+        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1337
+            $componentType = $filters['comp-filters'][0]['name'];
1338
+
1339
+            // Checking if we need post-filters
1340
+            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1341
+                $requirePostFilter = false;
1342
+            }
1343
+            // There was a time-range filter
1344
+            if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range']) && is_array($filters['comp-filters'][0]['time-range'])) {
1345
+                $timeRange = $filters['comp-filters'][0]['time-range'];
1346
+
1347
+                // If start time OR the end time is not specified, we can do a
1348
+                // 100% accurate mysql query.
1349
+                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1350
+                    $requirePostFilter = false;
1351
+                }
1352
+            }
1353
+        }
1354
+        $columns = ['uri'];
1355
+        if ($requirePostFilter) {
1356
+            $columns = ['uri', 'calendardata'];
1357
+        }
1358
+        $query = $this->db->getQueryBuilder();
1359
+        $query->select($columns)
1360
+            ->from('calendarobjects')
1361
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1362
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
1363
+
1364
+        if ($componentType) {
1365
+            $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1366
+        }
1367
+
1368
+        if ($timeRange && $timeRange['start']) {
1369
+            $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1370
+        }
1371
+        if ($timeRange && $timeRange['end']) {
1372
+            $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1373
+        }
1374
+
1375
+        $stmt = $query->executeQuery();
1376
+
1377
+        $result = [];
1378
+        while ($row = $stmt->fetch()) {
1379
+            if ($requirePostFilter) {
1380
+                // validateFilterForObject will parse the calendar data
1381
+                // catch parsing errors
1382
+                try {
1383
+                    $matches = $this->validateFilterForObject($row, $filters);
1384
+                } catch (ParseException $ex) {
1385
+                    $this->logger->logException($ex, [
1386
+                        'app' => 'dav',
1387
+                        'message' => 'Caught parsing exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1388
+                    ]);
1389
+                    continue;
1390
+                } catch (InvalidDataException $ex) {
1391
+                    $this->logger->logException($ex, [
1392
+                        'app' => 'dav',
1393
+                        'message' => 'Caught invalid data exception for calendar data. This usually indicates invalid calendar data. calendar-id:'.$calendarId.' uri:'.$row['uri']
1394
+                    ]);
1395
+                    continue;
1396
+                }
1397
+
1398
+                if (!$matches) {
1399
+                    continue;
1400
+                }
1401
+            }
1402
+            $result[] = $row['uri'];
1403
+        }
1404
+
1405
+        return $result;
1406
+    }
1407
+
1408
+    /**
1409
+     * custom Nextcloud search extension for CalDAV
1410
+     *
1411
+     * TODO - this should optionally cover cached calendar objects as well
1412
+     *
1413
+     * @param string $principalUri
1414
+     * @param array $filters
1415
+     * @param integer|null $limit
1416
+     * @param integer|null $offset
1417
+     * @return array
1418
+     */
1419
+    public function calendarSearch($principalUri, array $filters, $limit = null, $offset = null) {
1420
+        $calendars = $this->getCalendarsForUser($principalUri);
1421
+        $ownCalendars = [];
1422
+        $sharedCalendars = [];
1423
+
1424
+        $uriMapper = [];
1425
+
1426
+        foreach ($calendars as $calendar) {
1427
+            if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1428
+                $ownCalendars[] = $calendar['id'];
1429
+            } else {
1430
+                $sharedCalendars[] = $calendar['id'];
1431
+            }
1432
+            $uriMapper[$calendar['id']] = $calendar['uri'];
1433
+        }
1434
+        if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1435
+            return [];
1436
+        }
1437
+
1438
+        $query = $this->db->getQueryBuilder();
1439
+        // Calendar id expressions
1440
+        $calendarExpressions = [];
1441
+        foreach ($ownCalendars as $id) {
1442
+            $calendarExpressions[] = $query->expr()->andX(
1443
+                $query->expr()->eq('c.calendarid',
1444
+                    $query->createNamedParameter($id)),
1445
+                $query->expr()->eq('c.calendartype',
1446
+                        $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1447
+        }
1448
+        foreach ($sharedCalendars as $id) {
1449
+            $calendarExpressions[] = $query->expr()->andX(
1450
+                $query->expr()->eq('c.calendarid',
1451
+                    $query->createNamedParameter($id)),
1452
+                $query->expr()->eq('c.classification',
1453
+                    $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)),
1454
+                $query->expr()->eq('c.calendartype',
1455
+                    $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1456
+        }
1457
+
1458
+        if (count($calendarExpressions) === 1) {
1459
+            $calExpr = $calendarExpressions[0];
1460
+        } else {
1461
+            $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1462
+        }
1463
+
1464
+        // Component expressions
1465
+        $compExpressions = [];
1466
+        foreach ($filters['comps'] as $comp) {
1467
+            $compExpressions[] = $query->expr()
1468
+                ->eq('c.componenttype', $query->createNamedParameter($comp));
1469
+        }
1470
+
1471
+        if (count($compExpressions) === 1) {
1472
+            $compExpr = $compExpressions[0];
1473
+        } else {
1474
+            $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1475
+        }
1476
+
1477
+        if (!isset($filters['props'])) {
1478
+            $filters['props'] = [];
1479
+        }
1480
+        if (!isset($filters['params'])) {
1481
+            $filters['params'] = [];
1482
+        }
1483
+
1484
+        $propParamExpressions = [];
1485
+        foreach ($filters['props'] as $prop) {
1486
+            $propParamExpressions[] = $query->expr()->andX(
1487
+                $query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1488
+                $query->expr()->isNull('i.parameter')
1489
+            );
1490
+        }
1491
+        foreach ($filters['params'] as $param) {
1492
+            $propParamExpressions[] = $query->expr()->andX(
1493
+                $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1494
+                $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1495
+            );
1496
+        }
1497
+
1498
+        if (count($propParamExpressions) === 1) {
1499
+            $propParamExpr = $propParamExpressions[0];
1500
+        } else {
1501
+            $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1502
+        }
1503
+
1504
+        $query->select(['c.calendarid', 'c.uri'])
1505
+            ->from($this->dbObjectPropertiesTable, 'i')
1506
+            ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1507
+            ->where($calExpr)
1508
+            ->andWhere($compExpr)
1509
+            ->andWhere($propParamExpr)
1510
+            ->andWhere($query->expr()->iLike('i.value',
1511
+                $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1512
+
1513
+        if ($offset) {
1514
+            $query->setFirstResult($offset);
1515
+        }
1516
+        if ($limit) {
1517
+            $query->setMaxResults($limit);
1518
+        }
1519
+
1520
+        $stmt = $query->executeQuery();
1521
+
1522
+        $result = [];
1523
+        while ($row = $stmt->fetch()) {
1524
+            $path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1525
+            if (!in_array($path, $result)) {
1526
+                $result[] = $path;
1527
+            }
1528
+        }
1529
+
1530
+        return $result;
1531
+    }
1532
+
1533
+    /**
1534
+     * used for Nextcloud's calendar API
1535
+     *
1536
+     * @param array $calendarInfo
1537
+     * @param string $pattern
1538
+     * @param array $searchProperties
1539
+     * @param array $options
1540
+     * @param integer|null $limit
1541
+     * @param integer|null $offset
1542
+     *
1543
+     * @return array
1544
+     */
1545
+    public function search(array $calendarInfo, $pattern, array $searchProperties,
1546
+                            array $options, $limit, $offset) {
1547
+        $outerQuery = $this->db->getQueryBuilder();
1548
+        $innerQuery = $this->db->getQueryBuilder();
1549
+
1550
+        $innerQuery->selectDistinct('op.objectid')
1551
+            ->from($this->dbObjectPropertiesTable, 'op')
1552
+            ->andWhere($innerQuery->expr()->eq('op.calendarid',
1553
+                $outerQuery->createNamedParameter($calendarInfo['id'])))
1554
+            ->andWhere($innerQuery->expr()->eq('op.calendartype',
1555
+                $outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1556
+
1557
+        // only return public items for shared calendars for now
1558
+        if ($calendarInfo['principaluri'] !== $calendarInfo['{http://owncloud.org/ns}owner-principal']) {
1559
+            $innerQuery->andWhere($innerQuery->expr()->eq('c.classification',
1560
+                $outerQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1561
+        }
1562
+
1563
+        $or = $innerQuery->expr()->orX();
1564
+        foreach ($searchProperties as $searchProperty) {
1565
+            $or->add($innerQuery->expr()->eq('op.name',
1566
+                $outerQuery->createNamedParameter($searchProperty)));
1567
+        }
1568
+        $innerQuery->andWhere($or);
1569
+
1570
+        if ($pattern !== '') {
1571
+            $innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1572
+                $outerQuery->createNamedParameter('%' .
1573
+                    $this->db->escapeLikeParameter($pattern) . '%')));
1574
+        }
1575
+
1576
+        $outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
1577
+            ->from('calendarobjects', 'c');
1578
+
1579
+        if (isset($options['timerange'])) {
1580
+            if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTime) {
1581
+                $outerQuery->andWhere($outerQuery->expr()->gt('lastoccurence',
1582
+                    $outerQuery->createNamedParameter($options['timerange']['start']->getTimeStamp())));
1583
+            }
1584
+            if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTime) {
1585
+                $outerQuery->andWhere($outerQuery->expr()->lt('firstoccurence',
1586
+                    $outerQuery->createNamedParameter($options['timerange']['end']->getTimeStamp())));
1587
+            }
1588
+        }
1589
+
1590
+        if (isset($options['types'])) {
1591
+            $or = $outerQuery->expr()->orX();
1592
+            foreach ($options['types'] as $type) {
1593
+                $or->add($outerQuery->expr()->eq('componenttype',
1594
+                    $outerQuery->createNamedParameter($type)));
1595
+            }
1596
+            $outerQuery->andWhere($or);
1597
+        }
1598
+
1599
+        $outerQuery->andWhere($outerQuery->expr()->in('c.id',
1600
+            $outerQuery->createFunction($innerQuery->getSQL())));
1601
+
1602
+        if ($offset) {
1603
+            $outerQuery->setFirstResult($offset);
1604
+        }
1605
+        if ($limit) {
1606
+            $outerQuery->setMaxResults($limit);
1607
+        }
1608
+
1609
+        $result = $outerQuery->executeQuery();
1610
+        $calendarObjects = $result->fetchAll();
1611
+
1612
+        return array_map(function ($o) {
1613
+            $calendarData = Reader::read($o['calendardata']);
1614
+            $comps = $calendarData->getComponents();
1615
+            $objects = [];
1616
+            $timezones = [];
1617
+            foreach ($comps as $comp) {
1618
+                if ($comp instanceof VTimeZone) {
1619
+                    $timezones[] = $comp;
1620
+                } else {
1621
+                    $objects[] = $comp;
1622
+                }
1623
+            }
1624
+
1625
+            return [
1626
+                'id' => $o['id'],
1627
+                'type' => $o['componenttype'],
1628
+                'uid' => $o['uid'],
1629
+                'uri' => $o['uri'],
1630
+                'objects' => array_map(function ($c) {
1631
+                    return $this->transformSearchData($c);
1632
+                }, $objects),
1633
+                'timezones' => array_map(function ($c) {
1634
+                    return $this->transformSearchData($c);
1635
+                }, $timezones),
1636
+            ];
1637
+        }, $calendarObjects);
1638
+    }
1639
+
1640
+    /**
1641
+     * @param Component $comp
1642
+     * @return array
1643
+     */
1644
+    private function transformSearchData(Component $comp) {
1645
+        $data = [];
1646
+        /** @var Component[] $subComponents */
1647
+        $subComponents = $comp->getComponents();
1648
+        /** @var Property[] $properties */
1649
+        $properties = array_filter($comp->children(), function ($c) {
1650
+            return $c instanceof Property;
1651
+        });
1652
+        $validationRules = $comp->getValidationRules();
1653
+
1654
+        foreach ($subComponents as $subComponent) {
1655
+            $name = $subComponent->name;
1656
+            if (!isset($data[$name])) {
1657
+                $data[$name] = [];
1658
+            }
1659
+            $data[$name][] = $this->transformSearchData($subComponent);
1660
+        }
1661
+
1662
+        foreach ($properties as $property) {
1663
+            $name = $property->name;
1664
+            if (!isset($validationRules[$name])) {
1665
+                $validationRules[$name] = '*';
1666
+            }
1667
+
1668
+            $rule = $validationRules[$property->name];
1669
+            if ($rule === '+' || $rule === '*') { // multiple
1670
+                if (!isset($data[$name])) {
1671
+                    $data[$name] = [];
1672
+                }
1673
+
1674
+                $data[$name][] = $this->transformSearchProperty($property);
1675
+            } else { // once
1676
+                $data[$name] = $this->transformSearchProperty($property);
1677
+            }
1678
+        }
1679
+
1680
+        return $data;
1681
+    }
1682
+
1683
+    /**
1684
+     * @param Property $prop
1685
+     * @return array
1686
+     */
1687
+    private function transformSearchProperty(Property $prop) {
1688
+        // No need to check Date, as it extends DateTime
1689
+        if ($prop instanceof Property\ICalendar\DateTime) {
1690
+            $value = $prop->getDateTime();
1691
+        } else {
1692
+            $value = $prop->getValue();
1693
+        }
1694
+
1695
+        return [
1696
+            $value,
1697
+            $prop->parameters()
1698
+        ];
1699
+    }
1700
+
1701
+    /**
1702
+     * @param string $principalUri
1703
+     * @param string $pattern
1704
+     * @param array $componentTypes
1705
+     * @param array $searchProperties
1706
+     * @param array $searchParameters
1707
+     * @param array $options
1708
+     * @return array
1709
+     */
1710
+    public function searchPrincipalUri(string $principalUri,
1711
+                                        string $pattern,
1712
+                                        array $componentTypes,
1713
+                                        array $searchProperties,
1714
+                                        array $searchParameters,
1715
+                                        array $options = []): array {
1716
+        $escapePattern = !\array_key_exists('escape_like_param', $options) || $options['escape_like_param'] !== false;
1717
+
1718
+        $calendarObjectIdQuery = $this->db->getQueryBuilder();
1719
+        $calendarOr = $calendarObjectIdQuery->expr()->orX();
1720
+        $searchOr = $calendarObjectIdQuery->expr()->orX();
1721
+
1722
+        // Fetch calendars and subscription
1723
+        $calendars = $this->getCalendarsForUser($principalUri);
1724
+        $subscriptions = $this->getSubscriptionsForUser($principalUri);
1725
+        foreach ($calendars as $calendar) {
1726
+            $calendarAnd = $calendarObjectIdQuery->expr()->andX();
1727
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1728
+            $calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1729
+
1730
+            // If it's shared, limit search to public events
1731
+            if (isset($calendar['{http://owncloud.org/ns}owner-principal'])
1732
+                && $calendar['principaluri'] !== $calendar['{http://owncloud.org/ns}owner-principal']) {
1733
+                $calendarAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1734
+            }
1735
+
1736
+            $calendarOr->add($calendarAnd);
1737
+        }
1738
+        foreach ($subscriptions as $subscription) {
1739
+            $subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1740
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1741
+            $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1742
+
1743
+            // If it's shared, limit search to public events
1744
+            if (isset($subscription['{http://owncloud.org/ns}owner-principal'])
1745
+                && $subscription['principaluri'] !== $subscription['{http://owncloud.org/ns}owner-principal']) {
1746
+                $subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('co.classification', $calendarObjectIdQuery->createNamedParameter(self::CLASSIFICATION_PUBLIC)));
1747
+            }
1748
+
1749
+            $calendarOr->add($subscriptionAnd);
1750
+        }
1751
+
1752
+        foreach ($searchProperties as $property) {
1753
+            $propertyAnd = $calendarObjectIdQuery->expr()->andX();
1754
+            $propertyAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1755
+            $propertyAnd->add($calendarObjectIdQuery->expr()->isNull('cob.parameter'));
1756
+
1757
+            $searchOr->add($propertyAnd);
1758
+        }
1759
+        foreach ($searchParameters as $property => $parameter) {
1760
+            $parameterAnd = $calendarObjectIdQuery->expr()->andX();
1761
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.name', $calendarObjectIdQuery->createNamedParameter($property, IQueryBuilder::PARAM_STR)));
1762
+            $parameterAnd->add($calendarObjectIdQuery->expr()->eq('cob.parameter', $calendarObjectIdQuery->createNamedParameter($parameter, IQueryBuilder::PARAM_STR_ARRAY)));
1763
+
1764
+            $searchOr->add($parameterAnd);
1765
+        }
1766
+
1767
+        if ($calendarOr->count() === 0) {
1768
+            return [];
1769
+        }
1770
+        if ($searchOr->count() === 0) {
1771
+            return [];
1772
+        }
1773
+
1774
+        $calendarObjectIdQuery->selectDistinct('cob.objectid')
1775
+            ->from($this->dbObjectPropertiesTable, 'cob')
1776
+            ->leftJoin('cob', 'calendarobjects', 'co', $calendarObjectIdQuery->expr()->eq('co.id', 'cob.objectid'))
1777
+            ->andWhere($calendarObjectIdQuery->expr()->in('co.componenttype', $calendarObjectIdQuery->createNamedParameter($componentTypes, IQueryBuilder::PARAM_STR_ARRAY)))
1778
+            ->andWhere($calendarOr)
1779
+            ->andWhere($searchOr);
1780
+
1781
+        if ('' !== $pattern) {
1782
+            if (!$escapePattern) {
1783
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1784
+            } else {
1785
+                $calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1786
+            }
1787
+        }
1788
+
1789
+        if (isset($options['limit'])) {
1790
+            $calendarObjectIdQuery->setMaxResults($options['limit']);
1791
+        }
1792
+        if (isset($options['offset'])) {
1793
+            $calendarObjectIdQuery->setFirstResult($options['offset']);
1794
+        }
1795
+
1796
+        $result = $calendarObjectIdQuery->executeQuery();
1797
+        $matches = $result->fetchAll();
1798
+        $result->closeCursor();
1799
+        $matches = array_map(static function (array $match):int {
1800
+            return (int) $match['objectid'];
1801
+        }, $matches);
1802
+
1803
+        $query = $this->db->getQueryBuilder();
1804
+        $query->select('calendardata', 'uri', 'calendarid', 'calendartype')
1805
+            ->from('calendarobjects')
1806
+            ->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));
1807
+
1808
+        $result = $query->executeQuery();
1809
+        $calendarObjects = $result->fetchAll();
1810
+        $result->closeCursor();
1811
+
1812
+        return array_map(function (array $array): array {
1813
+            $array['calendarid'] = (int)$array['calendarid'];
1814
+            $array['calendartype'] = (int)$array['calendartype'];
1815
+            $array['calendardata'] = $this->readBlob($array['calendardata']);
1816
+
1817
+            return $array;
1818
+        }, $calendarObjects);
1819
+    }
1820
+
1821
+    /**
1822
+     * Searches through all of a users calendars and calendar objects to find
1823
+     * an object with a specific UID.
1824
+     *
1825
+     * This method should return the path to this object, relative to the
1826
+     * calendar home, so this path usually only contains two parts:
1827
+     *
1828
+     * calendarpath/objectpath.ics
1829
+     *
1830
+     * If the uid is not found, return null.
1831
+     *
1832
+     * This method should only consider * objects that the principal owns, so
1833
+     * any calendars owned by other principals that also appear in this
1834
+     * collection should be ignored.
1835
+     *
1836
+     * @param string $principalUri
1837
+     * @param string $uid
1838
+     * @return string|null
1839
+     */
1840
+    public function getCalendarObjectByUID($principalUri, $uid) {
1841
+        $query = $this->db->getQueryBuilder();
1842
+        $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1843
+            ->from('calendarobjects', 'co')
1844
+            ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1845
+            ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1846
+            ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1847
+
1848
+        $stmt = $query->executeQuery();
1849
+        $row = $stmt->fetch();
1850
+        $stmt->closeCursor();
1851
+        if ($row) {
1852
+            return $row['calendaruri'] . '/' . $row['objecturi'];
1853
+        }
1854
+
1855
+        return null;
1856
+    }
1857
+
1858
+    /**
1859
+     * The getChanges method returns all the changes that have happened, since
1860
+     * the specified syncToken in the specified calendar.
1861
+     *
1862
+     * This function should return an array, such as the following:
1863
+     *
1864
+     * [
1865
+     *   'syncToken' => 'The current synctoken',
1866
+     *   'added'   => [
1867
+     *      'new.txt',
1868
+     *   ],
1869
+     *   'modified'   => [
1870
+     *      'modified.txt',
1871
+     *   ],
1872
+     *   'deleted' => [
1873
+     *      'foo.php.bak',
1874
+     *      'old.txt'
1875
+     *   ]
1876
+     * );
1877
+     *
1878
+     * The returned syncToken property should reflect the *current* syncToken
1879
+     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1880
+     * property This is * needed here too, to ensure the operation is atomic.
1881
+     *
1882
+     * If the $syncToken argument is specified as null, this is an initial
1883
+     * sync, and all members should be reported.
1884
+     *
1885
+     * The modified property is an array of nodenames that have changed since
1886
+     * the last token.
1887
+     *
1888
+     * The deleted property is an array with nodenames, that have been deleted
1889
+     * from collection.
1890
+     *
1891
+     * The $syncLevel argument is basically the 'depth' of the report. If it's
1892
+     * 1, you only have to report changes that happened only directly in
1893
+     * immediate descendants. If it's 2, it should also include changes from
1894
+     * the nodes below the child collections. (grandchildren)
1895
+     *
1896
+     * The $limit argument allows a client to specify how many results should
1897
+     * be returned at most. If the limit is not specified, it should be treated
1898
+     * as infinite.
1899
+     *
1900
+     * If the limit (infinite or not) is higher than you're willing to return,
1901
+     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1902
+     *
1903
+     * If the syncToken is expired (due to data cleanup) or unknown, you must
1904
+     * return null.
1905
+     *
1906
+     * The limit is 'suggestive'. You are free to ignore it.
1907
+     *
1908
+     * @param string $calendarId
1909
+     * @param string $syncToken
1910
+     * @param int $syncLevel
1911
+     * @param int|null $limit
1912
+     * @param int $calendarType
1913
+     * @return array
1914
+     */
1915
+    public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
1916
+        // Current synctoken
1917
+        $qb = $this->db->getQueryBuilder();
1918
+        $qb->select('synctoken')
1919
+            ->from('calendars')
1920
+            ->where(
1921
+                $qb->expr()->eq('id', $qb->createNamedParameter($calendarId))
1922
+            );
1923
+        $stmt = $qb->executeQuery();
1924
+        $currentToken = $stmt->fetchOne();
1925
+
1926
+        if ($currentToken === false) {
1927
+            return null;
1928
+        }
1929
+
1930
+        $result = [
1931
+            'syncToken' => $currentToken,
1932
+            'added' => [],
1933
+            'modified' => [],
1934
+            'deleted' => [],
1935
+        ];
1936
+
1937
+        if ($syncToken) {
1938
+            $qb = $this->db->getQueryBuilder();
1939
+
1940
+            $qb->select('uri', 'operation')
1941
+                ->from('calendarchanges')
1942
+                ->where(
1943
+                    $qb->expr()->andX(
1944
+                        $qb->expr()->gte('synctoken', $qb->createNamedParameter($syncToken)),
1945
+                        $qb->expr()->lt('synctoken', $qb->createNamedParameter($currentToken)),
1946
+                        $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
1947
+                        $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType))
1948
+                    )
1949
+                )->orderBy('synctoken');
1950
+            if (is_int($limit) && $limit > 0) {
1951
+                $qb->setMaxResults($limit);
1952
+            }
1953
+
1954
+            // Fetching all changes
1955
+            $stmt = $qb->executeQuery();
1956
+            $changes = [];
1957
+
1958
+            // This loop ensures that any duplicates are overwritten, only the
1959
+            // last change on a node is relevant.
1960
+            while ($row = $stmt->fetch()) {
1961
+                $changes[$row['uri']] = $row['operation'];
1962
+            }
1963
+            $stmt->closeCursor();
1964
+
1965
+            foreach ($changes as $uri => $operation) {
1966
+                switch ($operation) {
1967
+                    case 1:
1968
+                        $result['added'][] = $uri;
1969
+                        break;
1970
+                    case 2:
1971
+                        $result['modified'][] = $uri;
1972
+                        break;
1973
+                    case 3:
1974
+                        $result['deleted'][] = $uri;
1975
+                        break;
1976
+                }
1977
+            }
1978
+        } else {
1979
+            // No synctoken supplied, this is the initial sync.
1980
+            $qb = $this->db->getQueryBuilder();
1981
+            $qb->select('uri')
1982
+                ->from('calendarobjects')
1983
+                ->where(
1984
+                    $qb->expr()->andX(
1985
+                        $qb->expr()->eq('calendarid', $qb->createNamedParameter($calendarId)),
1986
+                        $qb->expr()->eq('calendartype', $qb->createNamedParameter($calendarType))
1987
+                    )
1988
+                );
1989
+            $stmt = $qb->executeQuery();
1990
+            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1991
+            $stmt->closeCursor();
1992
+        }
1993
+        return $result;
1994
+    }
1995
+
1996
+    /**
1997
+     * Returns a list of subscriptions for a principal.
1998
+     *
1999
+     * Every subscription is an array with the following keys:
2000
+     *  * id, a unique id that will be used by other functions to modify the
2001
+     *    subscription. This can be the same as the uri or a database key.
2002
+     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
2003
+     *  * principaluri. The owner of the subscription. Almost always the same as
2004
+     *    principalUri passed to this method.
2005
+     *
2006
+     * Furthermore, all the subscription info must be returned too:
2007
+     *
2008
+     * 1. {DAV:}displayname
2009
+     * 2. {http://apple.com/ns/ical/}refreshrate
2010
+     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
2011
+     *    should not be stripped).
2012
+     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
2013
+     *    should not be stripped).
2014
+     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
2015
+     *    attachments should not be stripped).
2016
+     * 6. {http://calendarserver.org/ns/}source (Must be a
2017
+     *     Sabre\DAV\Property\Href).
2018
+     * 7. {http://apple.com/ns/ical/}calendar-color
2019
+     * 8. {http://apple.com/ns/ical/}calendar-order
2020
+     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
2021
+     *    (should just be an instance of
2022
+     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
2023
+     *    default components).
2024
+     *
2025
+     * @param string $principalUri
2026
+     * @return array
2027
+     */
2028
+    public function getSubscriptionsForUser($principalUri) {
2029
+        $fields = array_values($this->subscriptionPropertyMap);
2030
+        $fields[] = 'id';
2031
+        $fields[] = 'uri';
2032
+        $fields[] = 'source';
2033
+        $fields[] = 'principaluri';
2034
+        $fields[] = 'lastmodified';
2035
+        $fields[] = 'synctoken';
2036
+
2037
+        $query = $this->db->getQueryBuilder();
2038
+        $query->select($fields)
2039
+            ->from('calendarsubscriptions')
2040
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2041
+            ->orderBy('calendarorder', 'asc');
2042
+        $stmt = $query->executeQuery();
2043
+
2044
+        $subscriptions = [];
2045
+        while ($row = $stmt->fetch()) {
2046
+            $subscription = [
2047
+                'id' => $row['id'],
2048
+                'uri' => $row['uri'],
2049
+                'principaluri' => $row['principaluri'],
2050
+                'source' => $row['source'],
2051
+                'lastmodified' => $row['lastmodified'],
2052
+
2053
+                '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2054
+                '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2055
+            ];
2056
+
2057
+            foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2058
+                if (!is_null($row[$dbName])) {
2059
+                    $subscription[$xmlName] = $row[$dbName];
2060
+                }
2061
+            }
2062
+
2063
+            $subscriptions[] = $subscription;
2064
+        }
2065
+
2066
+        return $subscriptions;
2067
+    }
2068
+
2069
+    /**
2070
+     * Creates a new subscription for a principal.
2071
+     *
2072
+     * If the creation was a success, an id must be returned that can be used to reference
2073
+     * this subscription in other methods, such as updateSubscription.
2074
+     *
2075
+     * @param string $principalUri
2076
+     * @param string $uri
2077
+     * @param array $properties
2078
+     * @return mixed
2079
+     */
2080
+    public function createSubscription($principalUri, $uri, array $properties) {
2081
+        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
2082
+            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
2083
+        }
2084
+
2085
+        $values = [
2086
+            'principaluri' => $principalUri,
2087
+            'uri' => $uri,
2088
+            'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(),
2089
+            'lastmodified' => time(),
2090
+        ];
2091
+
2092
+        $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
2093
+
2094
+        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
2095
+            if (array_key_exists($xmlName, $properties)) {
2096
+                $values[$dbName] = $properties[$xmlName];
2097
+                if (in_array($dbName, $propertiesBoolean)) {
2098
+                    $values[$dbName] = true;
2099
+                }
2100
+            }
2101
+        }
2102
+
2103
+        $valuesToInsert = [];
2104
+
2105
+        $query = $this->db->getQueryBuilder();
2106
+
2107
+        foreach (array_keys($values) as $name) {
2108
+            $valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
2109
+        }
2110
+
2111
+        $query->insert('calendarsubscriptions')
2112
+            ->values($valuesToInsert)
2113
+            ->executeStatement();
2114
+
2115
+        $subscriptionId = $query->getLastInsertId();
2116
+
2117
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2118
+        $this->dispatcher->dispatchTyped(new SubscriptionCreatedEvent($subscriptionId, $subscriptionRow));
2119
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createSubscription', new GenericEvent(
2120
+            '\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
2121
+            [
2122
+                'subscriptionId' => $subscriptionId,
2123
+                'subscriptionData' => $subscriptionRow,
2124
+            ]));
2125
+
2126
+        return $subscriptionId;
2127
+    }
2128
+
2129
+    /**
2130
+     * Updates a subscription
2131
+     *
2132
+     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
2133
+     * To do the actual updates, you must tell this object which properties
2134
+     * you're going to process with the handle() method.
2135
+     *
2136
+     * Calling the handle method is like telling the PropPatch object "I
2137
+     * promise I can handle updating this property".
2138
+     *
2139
+     * Read the PropPatch documentation for more info and examples.
2140
+     *
2141
+     * @param mixed $subscriptionId
2142
+     * @param PropPatch $propPatch
2143
+     * @return void
2144
+     */
2145
+    public function updateSubscription($subscriptionId, PropPatch $propPatch) {
2146
+        $supportedProperties = array_keys($this->subscriptionPropertyMap);
2147
+        $supportedProperties[] = '{http://calendarserver.org/ns/}source';
2148
+
2149
+        $propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2150
+            $newValues = [];
2151
+
2152
+            foreach ($mutations as $propertyName => $propertyValue) {
2153
+                if ($propertyName === '{http://calendarserver.org/ns/}source') {
2154
+                    $newValues['source'] = $propertyValue->getHref();
2155
+                } else {
2156
+                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
2157
+                    $newValues[$fieldName] = $propertyValue;
2158
+                }
2159
+            }
2160
+
2161
+            $query = $this->db->getQueryBuilder();
2162
+            $query->update('calendarsubscriptions')
2163
+                ->set('lastmodified', $query->createNamedParameter(time()));
2164
+            foreach ($newValues as $fieldName => $value) {
2165
+                $query->set($fieldName, $query->createNamedParameter($value));
2166
+            }
2167
+            $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2168
+                ->executeStatement();
2169
+
2170
+            $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2171
+            $this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2172
+            $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2173
+                '\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2174
+                [
2175
+                    'subscriptionId' => $subscriptionId,
2176
+                    'subscriptionData' => $subscriptionRow,
2177
+                    'propertyMutations' => $mutations,
2178
+                ]));
2179
+
2180
+            return true;
2181
+        });
2182
+    }
2183
+
2184
+    /**
2185
+     * Deletes a subscription.
2186
+     *
2187
+     * @param mixed $subscriptionId
2188
+     * @return void
2189
+     */
2190
+    public function deleteSubscription($subscriptionId) {
2191
+        $subscriptionRow = $this->getSubscriptionById($subscriptionId);
2192
+
2193
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription', new GenericEvent(
2194
+            '\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
2195
+            [
2196
+                'subscriptionId' => $subscriptionId,
2197
+                'subscriptionData' => $this->getSubscriptionById($subscriptionId),
2198
+            ]));
2199
+
2200
+        $query = $this->db->getQueryBuilder();
2201
+        $query->delete('calendarsubscriptions')
2202
+            ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
2203
+            ->executeStatement();
2204
+
2205
+        $query = $this->db->getQueryBuilder();
2206
+        $query->delete('calendarobjects')
2207
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2208
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2209
+            ->executeStatement();
2210
+
2211
+        $query->delete('calendarchanges')
2212
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2213
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2214
+            ->executeStatement();
2215
+
2216
+        $query->delete($this->dbObjectPropertiesTable)
2217
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2218
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2219
+            ->executeStatement();
2220
+
2221
+        if ($subscriptionRow) {
2222
+            $this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2223
+        }
2224
+    }
2225
+
2226
+    /**
2227
+     * Returns a single scheduling object for the inbox collection.
2228
+     *
2229
+     * The returned array should contain the following elements:
2230
+     *   * uri - A unique basename for the object. This will be used to
2231
+     *           construct a full uri.
2232
+     *   * calendardata - The iCalendar object
2233
+     *   * lastmodified - The last modification date. Can be an int for a unix
2234
+     *                    timestamp, or a PHP DateTime object.
2235
+     *   * etag - A unique token that must change if the object changed.
2236
+     *   * size - The size of the object, in bytes.
2237
+     *
2238
+     * @param string $principalUri
2239
+     * @param string $objectUri
2240
+     * @return array
2241
+     */
2242
+    public function getSchedulingObject($principalUri, $objectUri) {
2243
+        $query = $this->db->getQueryBuilder();
2244
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2245
+            ->from('schedulingobjects')
2246
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2247
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2248
+            ->executeQuery();
2249
+
2250
+        $row = $stmt->fetch();
2251
+
2252
+        if (!$row) {
2253
+            return null;
2254
+        }
2255
+
2256
+        return [
2257
+            'uri' => $row['uri'],
2258
+            'calendardata' => $row['calendardata'],
2259
+            'lastmodified' => $row['lastmodified'],
2260
+            'etag' => '"' . $row['etag'] . '"',
2261
+            'size' => (int)$row['size'],
2262
+        ];
2263
+    }
2264
+
2265
+    /**
2266
+     * Returns all scheduling objects for the inbox collection.
2267
+     *
2268
+     * These objects should be returned as an array. Every item in the array
2269
+     * should follow the same structure as returned from getSchedulingObject.
2270
+     *
2271
+     * The main difference is that 'calendardata' is optional.
2272
+     *
2273
+     * @param string $principalUri
2274
+     * @return array
2275
+     */
2276
+    public function getSchedulingObjects($principalUri) {
2277
+        $query = $this->db->getQueryBuilder();
2278
+        $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
2279
+                ->from('schedulingobjects')
2280
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2281
+                ->executeQuery();
2282
+
2283
+        $result = [];
2284
+        foreach ($stmt->fetchAll() as $row) {
2285
+            $result[] = [
2286
+                'calendardata' => $row['calendardata'],
2287
+                'uri' => $row['uri'],
2288
+                'lastmodified' => $row['lastmodified'],
2289
+                'etag' => '"' . $row['etag'] . '"',
2290
+                'size' => (int)$row['size'],
2291
+            ];
2292
+        }
2293
+
2294
+        return $result;
2295
+    }
2296
+
2297
+    /**
2298
+     * Deletes a scheduling object from the inbox collection.
2299
+     *
2300
+     * @param string $principalUri
2301
+     * @param string $objectUri
2302
+     * @return void
2303
+     */
2304
+    public function deleteSchedulingObject($principalUri, $objectUri) {
2305
+        $query = $this->db->getQueryBuilder();
2306
+        $query->delete('schedulingobjects')
2307
+                ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
2308
+                ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
2309
+                ->executeStatement();
2310
+    }
2311
+
2312
+    /**
2313
+     * Creates a new scheduling object. This should land in a users' inbox.
2314
+     *
2315
+     * @param string $principalUri
2316
+     * @param string $objectUri
2317
+     * @param string $objectData
2318
+     * @return void
2319
+     */
2320
+    public function createSchedulingObject($principalUri, $objectUri, $objectData) {
2321
+        $query = $this->db->getQueryBuilder();
2322
+        $query->insert('schedulingobjects')
2323
+            ->values([
2324
+                'principaluri' => $query->createNamedParameter($principalUri),
2325
+                'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
2326
+                'uri' => $query->createNamedParameter($objectUri),
2327
+                'lastmodified' => $query->createNamedParameter(time()),
2328
+                'etag' => $query->createNamedParameter(md5($objectData)),
2329
+                'size' => $query->createNamedParameter(strlen($objectData))
2330
+            ])
2331
+            ->executeStatement();
2332
+    }
2333
+
2334
+    /**
2335
+     * Adds a change record to the calendarchanges table.
2336
+     *
2337
+     * @param mixed $calendarId
2338
+     * @param string $objectUri
2339
+     * @param int $operation 1 = add, 2 = modify, 3 = delete.
2340
+     * @param int $calendarType
2341
+     * @return void
2342
+     */
2343
+    protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2344
+        $table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2345
+
2346
+        $query = $this->db->getQueryBuilder();
2347
+        $query->select('synctoken')
2348
+            ->from($table)
2349
+            ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2350
+        $result = $query->executeQuery();
2351
+        $syncToken = (int)$result->fetchOne();
2352
+        $result->closeCursor();
2353
+
2354
+        $query = $this->db->getQueryBuilder();
2355
+        $query->insert('calendarchanges')
2356
+            ->values([
2357
+                'uri' => $query->createNamedParameter($objectUri),
2358
+                'synctoken' => $query->createNamedParameter($syncToken),
2359
+                'calendarid' => $query->createNamedParameter($calendarId),
2360
+                'operation' => $query->createNamedParameter($operation),
2361
+                'calendartype' => $query->createNamedParameter($calendarType),
2362
+            ])
2363
+            ->executeStatement();
2364
+
2365
+        $stmt = $this->db->prepare("UPDATE `*PREFIX*$table` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?");
2366
+        $stmt->execute([
2367
+            $calendarId
2368
+        ]);
2369
+    }
2370
+
2371
+    /**
2372
+     * Parses some information from calendar objects, used for optimized
2373
+     * calendar-queries.
2374
+     *
2375
+     * Returns an array with the following keys:
2376
+     *   * etag - An md5 checksum of the object without the quotes.
2377
+     *   * size - Size of the object in bytes
2378
+     *   * componentType - VEVENT, VTODO or VJOURNAL
2379
+     *   * firstOccurence
2380
+     *   * lastOccurence
2381
+     *   * uid - value of the UID property
2382
+     *
2383
+     * @param string $calendarData
2384
+     * @return array
2385
+     */
2386
+    public function getDenormalizedData($calendarData) {
2387
+        $vObject = Reader::read($calendarData);
2388
+        $vEvents = [];
2389
+        $componentType = null;
2390
+        $component = null;
2391
+        $firstOccurrence = null;
2392
+        $lastOccurrence = null;
2393
+        $uid = null;
2394
+        $classification = self::CLASSIFICATION_PUBLIC;
2395
+        $hasDTSTART = false;
2396
+        foreach ($vObject->getComponents() as $component) {
2397
+            if ($component->name !== 'VTIMEZONE') {
2398
+                // Finding all VEVENTs, and track them
2399
+                if ($component->name === 'VEVENT') {
2400
+                    array_push($vEvents, $component);
2401
+                    if ($component->DTSTART) {
2402
+                        $hasDTSTART = true;
2403
+                    }
2404
+                }
2405
+                // Track first component type and uid
2406
+                if ($uid === null) {
2407
+                    $componentType = $component->name;
2408
+                    $uid = (string)$component->UID;
2409
+                }
2410
+            }
2411
+        }
2412
+        if (!$componentType) {
2413
+            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
2414
+        }
2415
+
2416
+        if ($hasDTSTART) {
2417
+            $component = $vEvents[0];
2418
+
2419
+            // Finding the last occurrence is a bit harder
2420
+            if (!isset($component->RRULE) && count($vEvents) === 1) {
2421
+                $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
2422
+                if (isset($component->DTEND)) {
2423
+                    $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
2424
+                } elseif (isset($component->DURATION)) {
2425
+                    $endDate = clone $component->DTSTART->getDateTime();
2426
+                    $endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
2427
+                    $lastOccurrence = $endDate->getTimeStamp();
2428
+                } elseif (!$component->DTSTART->hasTime()) {
2429
+                    $endDate = clone $component->DTSTART->getDateTime();
2430
+                    $endDate->modify('+1 day');
2431
+                    $lastOccurrence = $endDate->getTimeStamp();
2432
+                } else {
2433
+                    $lastOccurrence = $firstOccurrence;
2434
+                }
2435
+            } else {
2436
+                $it = new EventIterator($vEvents);
2437
+                $maxDate = new DateTime(self::MAX_DATE);
2438
+                $firstOccurrence = $it->getDtStart()->getTimestamp();
2439
+                if ($it->isInfinite()) {
2440
+                    $lastOccurrence = $maxDate->getTimestamp();
2441
+                } else {
2442
+                    $end = $it->getDtEnd();
2443
+                    while ($it->valid() && $end < $maxDate) {
2444
+                        $end = $it->getDtEnd();
2445
+                        $it->next();
2446
+                    }
2447
+                    $lastOccurrence = $end->getTimestamp();
2448
+                }
2449
+            }
2450
+        }
2451
+
2452
+        if ($component->CLASS) {
2453
+            $classification = CalDavBackend::CLASSIFICATION_PRIVATE;
2454
+            switch ($component->CLASS->getValue()) {
2455
+                case 'PUBLIC':
2456
+                    $classification = CalDavBackend::CLASSIFICATION_PUBLIC;
2457
+                    break;
2458
+                case 'CONFIDENTIAL':
2459
+                    $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
2460
+                    break;
2461
+            }
2462
+        }
2463
+        return [
2464
+            'etag' => md5($calendarData),
2465
+            'size' => strlen($calendarData),
2466
+            'componentType' => $componentType,
2467
+            'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
2468
+            'lastOccurence' => $lastOccurrence,
2469
+            'uid' => $uid,
2470
+            'classification' => $classification
2471
+        ];
2472
+    }
2473
+
2474
+    /**
2475
+     * @param $cardData
2476
+     * @return bool|string
2477
+     */
2478
+    private function readBlob($cardData) {
2479
+        if (is_resource($cardData)) {
2480
+            return stream_get_contents($cardData);
2481
+        }
2482
+
2483
+        return $cardData;
2484
+    }
2485
+
2486
+    /**
2487
+     * @param IShareable $shareable
2488
+     * @param array $add
2489
+     * @param array $remove
2490
+     */
2491
+    public function updateShares($shareable, $add, $remove) {
2492
+        $calendarId = $shareable->getResourceId();
2493
+        $calendarRow = $this->getCalendarById($calendarId);
2494
+        $oldShares = $this->getShares($calendarId);
2495
+
2496
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
2497
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2498
+            [
2499
+                'calendarId' => $calendarId,
2500
+                'calendarData' => $calendarRow,
2501
+                'shares' => $oldShares,
2502
+                'add' => $add,
2503
+                'remove' => $remove,
2504
+            ]));
2505
+        $this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2506
+
2507
+        $this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2508
+    }
2509
+
2510
+    /**
2511
+     * @param int $resourceId
2512
+     * @return array
2513
+     */
2514
+    public function getShares($resourceId) {
2515
+        return $this->calendarSharingBackend->getShares($resourceId);
2516
+    }
2517
+
2518
+    /**
2519
+     * @param boolean $value
2520
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2521
+     * @return string|null
2522
+     */
2523
+    public function setPublishStatus($value, $calendar) {
2524
+        $calendarId = $calendar->getResourceId();
2525
+        $calendarData = $this->getCalendarById($calendarId);
2526
+        $this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', new GenericEvent(
2527
+            '\OCA\DAV\CalDAV\CalDavBackend::updateShares',
2528
+            [
2529
+                'calendarId' => $calendarId,
2530
+                'calendarData' => $calendarData,
2531
+                'public' => $value,
2532
+            ]));
2533
+
2534
+        $query = $this->db->getQueryBuilder();
2535
+        if ($value) {
2536
+            $publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
2537
+            $query->insert('dav_shares')
2538
+                ->values([
2539
+                    'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
2540
+                    'type' => $query->createNamedParameter('calendar'),
2541
+                    'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
2542
+                    'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
2543
+                    'publicuri' => $query->createNamedParameter($publicUri)
2544
+                ]);
2545
+            $query->executeStatement();
2546
+
2547
+            $this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2548
+            return $publicUri;
2549
+        }
2550
+        $query->delete('dav_shares')
2551
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2552
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2553
+        $query->executeStatement();
2554
+
2555
+        $this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2556
+        return null;
2557
+    }
2558
+
2559
+    /**
2560
+     * @param \OCA\DAV\CalDAV\Calendar $calendar
2561
+     * @return mixed
2562
+     */
2563
+    public function getPublishStatus($calendar) {
2564
+        $query = $this->db->getQueryBuilder();
2565
+        $result = $query->select('publicuri')
2566
+            ->from('dav_shares')
2567
+            ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
2568
+            ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
2569
+            ->executeQuery();
2570
+
2571
+        $row = $result->fetch();
2572
+        $result->closeCursor();
2573
+        return $row ? reset($row) : false;
2574
+    }
2575
+
2576
+    /**
2577
+     * @param int $resourceId
2578
+     * @param array $acl
2579
+     * @return array
2580
+     */
2581
+    public function applyShareAcl($resourceId, $acl) {
2582
+        return $this->calendarSharingBackend->applyShareAcl($resourceId, $acl);
2583
+    }
2584
+
2585
+
2586
+
2587
+    /**
2588
+     * update properties table
2589
+     *
2590
+     * @param int $calendarId
2591
+     * @param string $objectUri
2592
+     * @param string $calendarData
2593
+     * @param int $calendarType
2594
+     */
2595
+    public function updateProperties($calendarId, $objectUri, $calendarData, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2596
+        $objectId = $this->getCalendarObjectId($calendarId, $objectUri, $calendarType);
2597
+
2598
+        try {
2599
+            $vCalendar = $this->readCalendarData($calendarData);
2600
+        } catch (\Exception $ex) {
2601
+            return;
2602
+        }
2603
+
2604
+        $this->purgeProperties($calendarId, $objectId);
2605
+
2606
+        $query = $this->db->getQueryBuilder();
2607
+        $query->insert($this->dbObjectPropertiesTable)
2608
+            ->values(
2609
+                [
2610
+                    'calendarid' => $query->createNamedParameter($calendarId),
2611
+                    'calendartype' => $query->createNamedParameter($calendarType),
2612
+                    'objectid' => $query->createNamedParameter($objectId),
2613
+                    'name' => $query->createParameter('name'),
2614
+                    'parameter' => $query->createParameter('parameter'),
2615
+                    'value' => $query->createParameter('value'),
2616
+                ]
2617
+            );
2618
+
2619
+        $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2620
+        foreach ($vCalendar->getComponents() as $component) {
2621
+            if (!in_array($component->name, $indexComponents)) {
2622
+                continue;
2623
+            }
2624
+
2625
+            foreach ($component->children() as $property) {
2626
+                if (in_array($property->name, self::$indexProperties)) {
2627
+                    $value = $property->getValue();
2628
+                    // is this a shitty db?
2629
+                    if (!$this->db->supports4ByteText()) {
2630
+                        $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2631
+                    }
2632
+                    $value = mb_strcut($value, 0, 254);
2633
+
2634
+                    $query->setParameter('name', $property->name);
2635
+                    $query->setParameter('parameter', null);
2636
+                    $query->setParameter('value', $value);
2637
+                    $query->executeStatement();
2638
+                }
2639
+
2640
+                if (array_key_exists($property->name, self::$indexParameters)) {
2641
+                    $parameters = $property->parameters();
2642
+                    $indexedParametersForProperty = self::$indexParameters[$property->name];
2643
+
2644
+                    foreach ($parameters as $key => $value) {
2645
+                        if (in_array($key, $indexedParametersForProperty)) {
2646
+                            // is this a shitty db?
2647
+                            if ($this->db->supports4ByteText()) {
2648
+                                $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2649
+                            }
2650
+
2651
+                            $query->setParameter('name', $property->name);
2652
+                            $query->setParameter('parameter', mb_strcut($key, 0, 254));
2653
+                            $query->setParameter('value', mb_strcut($value, 0, 254));
2654
+                            $query->executeStatement();
2655
+                        }
2656
+                    }
2657
+                }
2658
+            }
2659
+        }
2660
+    }
2661
+
2662
+    /**
2663
+     * deletes all birthday calendars
2664
+     */
2665
+    public function deleteAllBirthdayCalendars() {
2666
+        $query = $this->db->getQueryBuilder();
2667
+        $result = $query->select(['id'])->from('calendars')
2668
+            ->where($query->expr()->eq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)))
2669
+            ->executeQuery();
2670
+
2671
+        $ids = $result->fetchAll();
2672
+        foreach ($ids as $id) {
2673
+            $this->deleteCalendar($id['id']);
2674
+        }
2675
+    }
2676
+
2677
+    /**
2678
+     * @param $subscriptionId
2679
+     */
2680
+    public function purgeAllCachedEventsForSubscription($subscriptionId) {
2681
+        $query = $this->db->getQueryBuilder();
2682
+        $query->select('uri')
2683
+            ->from('calendarobjects')
2684
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2685
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
2686
+        $stmt = $query->executeQuery();
2687
+
2688
+        $uris = [];
2689
+        foreach ($stmt->fetchAll() as $row) {
2690
+            $uris[] = $row['uri'];
2691
+        }
2692
+        $stmt->closeCursor();
2693
+
2694
+        $query = $this->db->getQueryBuilder();
2695
+        $query->delete('calendarobjects')
2696
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2697
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2698
+            ->executeStatement();
2699
+
2700
+        $query->delete('calendarchanges')
2701
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2702
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2703
+            ->executeStatement();
2704
+
2705
+        $query->delete($this->dbObjectPropertiesTable)
2706
+            ->where($query->expr()->eq('calendarid', $query->createNamedParameter($subscriptionId)))
2707
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)))
2708
+            ->executeStatement();
2709
+
2710
+        foreach ($uris as $uri) {
2711
+            $this->addChange($subscriptionId, $uri, 3, self::CALENDAR_TYPE_SUBSCRIPTION);
2712
+        }
2713
+    }
2714
+
2715
+    /**
2716
+     * Move a calendar from one user to another
2717
+     *
2718
+     * @param string $uriName
2719
+     * @param string $uriOrigin
2720
+     * @param string $uriDestination
2721
+     * @param string $newUriName (optional) the new uriName
2722
+     */
2723
+    public function moveCalendar($uriName, $uriOrigin, $uriDestination, $newUriName = null) {
2724
+        $query = $this->db->getQueryBuilder();
2725
+        $query->update('calendars')
2726
+            ->set('principaluri', $query->createNamedParameter($uriDestination))
2727
+            ->set('uri', $query->createNamedParameter($newUriName ?: $uriName))
2728
+            ->where($query->expr()->eq('principaluri', $query->createNamedParameter($uriOrigin)))
2729
+            ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($uriName)))
2730
+            ->executeStatement();
2731
+    }
2732
+
2733
+    /**
2734
+     * read VCalendar data into a VCalendar object
2735
+     *
2736
+     * @param string $objectData
2737
+     * @return VCalendar
2738
+     */
2739
+    protected function readCalendarData($objectData) {
2740
+        return Reader::read($objectData);
2741
+    }
2742
+
2743
+    /**
2744
+     * delete all properties from a given calendar object
2745
+     *
2746
+     * @param int $calendarId
2747
+     * @param int $objectId
2748
+     */
2749
+    protected function purgeProperties($calendarId, $objectId) {
2750
+        $query = $this->db->getQueryBuilder();
2751
+        $query->delete($this->dbObjectPropertiesTable)
2752
+            ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2753
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2754
+        $query->executeStatement();
2755
+    }
2756
+
2757
+    /**
2758
+     * get ID from a given calendar object
2759
+     *
2760
+     * @param int $calendarId
2761
+     * @param string $uri
2762
+     * @param int $calendarType
2763
+     * @return int
2764
+     */
2765
+    protected function getCalendarObjectId($calendarId, $uri, $calendarType):int {
2766
+        $query = $this->db->getQueryBuilder();
2767
+        $query->select('id')
2768
+            ->from('calendarobjects')
2769
+            ->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2770
+            ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
2771
+            ->andWhere($query->expr()->eq('calendartype', $query->createNamedParameter($calendarType)));
2772
+
2773
+        $result = $query->executeQuery();
2774
+        $objectIds = $result->fetch();
2775
+        $result->closeCursor();
2776
+
2777
+        if (!isset($objectIds['id'])) {
2778
+            throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2779
+        }
2780
+
2781
+        return (int)$objectIds['id'];
2782
+    }
2783
+
2784
+    /**
2785
+     * return legacy endpoint principal name to new principal name
2786
+     *
2787
+     * @param $principalUri
2788
+     * @param $toV2
2789
+     * @return string
2790
+     */
2791
+    private function convertPrincipal($principalUri, $toV2) {
2792
+        if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2793
+            [, $name] = Uri\split($principalUri);
2794
+            if ($toV2 === true) {
2795
+                return "principals/users/$name";
2796
+            }
2797
+            return "principals/$name";
2798
+        }
2799
+        return $principalUri;
2800
+    }
2801
+
2802
+    /**
2803
+     * adds information about an owner to the calendar data
2804
+     *
2805
+     * @param $calendarInfo
2806
+     */
2807
+    private function addOwnerPrincipal(&$calendarInfo) {
2808
+        $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2809
+        $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2810
+        if (isset($calendarInfo[$ownerPrincipalKey])) {
2811
+            $uri = $calendarInfo[$ownerPrincipalKey];
2812
+        } else {
2813
+            $uri = $calendarInfo['principaluri'];
2814
+        }
2815
+
2816
+        $principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2817
+        if (isset($principalInformation['{DAV:}displayname'])) {
2818
+            $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2819
+        }
2820
+    }
2821 2821
 }
Please login to merge, or discard this patch.
Spacing   +112 added lines, -112 removed lines patch added patch discarded remove patch
@@ -256,7 +256,7 @@  discard block
 block discarded – undo
256 256
 		}
257 257
 
258 258
 		$result = $query->executeQuery();
259
-		$column = (int)$result->fetchOne();
259
+		$column = (int) $result->fetchOne();
260 260
 		$result->closeCursor();
261 261
 		return $column;
262 262
 	}
@@ -316,18 +316,18 @@  discard block
 block discarded – undo
316 316
 			$row['principaluri'] = (string) $row['principaluri'];
317 317
 			$components = [];
318 318
 			if ($row['components']) {
319
-				$components = explode(',',$row['components']);
319
+				$components = explode(',', $row['components']);
320 320
 			}
321 321
 
322 322
 			$calendar = [
323 323
 				'id' => $row['id'],
324 324
 				'uri' => $row['uri'],
325 325
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
326
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
327
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
328
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
330
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
326
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
327
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
328
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
329
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
330
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
331 331
 			];
332 332
 
333 333
 			foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -367,7 +367,7 @@  discard block
 block discarded – undo
367 367
 
368 368
 		$result = $query->executeQuery();
369 369
 
370
-		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
370
+		$readOnlyPropertyName = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only';
371 371
 		while ($row = $result->fetch()) {
372 372
 			$row['principaluri'] = (string) $row['principaluri'];
373 373
 			if ($row['principaluri'] === $principalUri) {
@@ -388,21 +388,21 @@  discard block
 block discarded – undo
388 388
 			}
389 389
 
390 390
 			[, $name] = Uri\split($row['principaluri']);
391
-			$uri = $row['uri'] . '_shared_by_' . $name;
392
-			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
391
+			$uri = $row['uri'].'_shared_by_'.$name;
392
+			$row['displayname'] = $row['displayname'].' ('.$this->getUserDisplayName($name).')';
393 393
 			$components = [];
394 394
 			if ($row['components']) {
395
-				$components = explode(',',$row['components']);
395
+				$components = explode(',', $row['components']);
396 396
 			}
397 397
 			$calendar = [
398 398
 				'id' => $row['id'],
399 399
 				'uri' => $uri,
400 400
 				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
401
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
402
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
403
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
401
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
402
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
403
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
404
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp('transparent'),
405
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
406 406
 				$readOnlyPropertyName => $readOnly,
407 407
 			];
408 408
 
@@ -443,16 +443,16 @@  discard block
 block discarded – undo
443 443
 			$row['principaluri'] = (string) $row['principaluri'];
444 444
 			$components = [];
445 445
 			if ($row['components']) {
446
-				$components = explode(',',$row['components']);
446
+				$components = explode(',', $row['components']);
447 447
 			}
448 448
 			$calendar = [
449 449
 				'id' => $row['id'],
450 450
 				'uri' => $row['uri'],
451 451
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
452
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
453
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
454
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
452
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
453
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
454
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
455
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
456 456
 			];
457 457
 			foreach ($this->propertyMap as $xmlName => $dbName) {
458 458
 				$calendar[$xmlName] = $row[$dbName];
@@ -512,22 +512,22 @@  discard block
 block discarded – undo
512 512
 		while ($row = $result->fetch()) {
513 513
 			$row['principaluri'] = (string) $row['principaluri'];
514 514
 			[, $name] = Uri\split($row['principaluri']);
515
-			$row['displayname'] = $row['displayname'] . "($name)";
515
+			$row['displayname'] = $row['displayname']."($name)";
516 516
 			$components = [];
517 517
 			if ($row['components']) {
518
-				$components = explode(',',$row['components']);
518
+				$components = explode(',', $row['components']);
519 519
 			}
520 520
 			$calendar = [
521 521
 				'id' => $row['id'],
522 522
 				'uri' => $row['publicuri'],
523 523
 				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
-				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
525
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
526
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
-				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
528
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
530
-				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
524
+				'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
525
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
526
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
527
+				'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
528
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
529
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
530
+				'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
531 531
 			];
532 532
 
533 533
 			foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -574,27 +574,27 @@  discard block
 block discarded – undo
574 574
 		$result->closeCursor();
575 575
 
576 576
 		if ($row === false) {
577
-			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
577
+			throw new NotFound('Node with name \''.$uri.'\' could not be found');
578 578
 		}
579 579
 
580 580
 		$row['principaluri'] = (string) $row['principaluri'];
581 581
 		[, $name] = Uri\split($row['principaluri']);
582
-		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
582
+		$row['displayname'] = $row['displayname'].' '."($name)";
583 583
 		$components = [];
584 584
 		if ($row['components']) {
585
-			$components = explode(',',$row['components']);
585
+			$components = explode(',', $row['components']);
586 586
 		}
587 587
 		$calendar = [
588 588
 			'id' => $row['id'],
589 589
 			'uri' => $row['publicuri'],
590 590
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
591
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
592
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
593
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
595
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
597
-			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
591
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
592
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
593
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
594
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
595
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
596
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}read-only' => (int) $row['access'] === Backend::ACCESS_READ,
597
+			'{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}public' => (int) $row['access'] === self::ACCESS_PUBLIC,
598 598
 		];
599 599
 
600 600
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -637,17 +637,17 @@  discard block
 block discarded – undo
637 637
 		$row['principaluri'] = (string) $row['principaluri'];
638 638
 		$components = [];
639 639
 		if ($row['components']) {
640
-			$components = explode(',',$row['components']);
640
+			$components = explode(',', $row['components']);
641 641
 		}
642 642
 
643 643
 		$calendar = [
644 644
 			'id' => $row['id'],
645 645
 			'uri' => $row['uri'],
646 646
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
647
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
648
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
649
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
647
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
648
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
649
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
650
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
651 651
 		];
652 652
 
653 653
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -688,17 +688,17 @@  discard block
 block discarded – undo
688 688
 		$row['principaluri'] = (string) $row['principaluri'];
689 689
 		$components = [];
690 690
 		if ($row['components']) {
691
-			$components = explode(',',$row['components']);
691
+			$components = explode(',', $row['components']);
692 692
 		}
693 693
 
694 694
 		$calendar = [
695 695
 			'id' => $row['id'],
696 696
 			'uri' => $row['uri'],
697 697
 			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
698
-			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
699
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
700
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
-			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
698
+			'{'.Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'),
699
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
700
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
701
+			'{'.Plugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
702 702
 		];
703 703
 
704 704
 		foreach ($this->propertyMap as $xmlName => $dbName) {
@@ -742,8 +742,8 @@  discard block
 block discarded – undo
742 742
 			'principaluri' => $row['principaluri'],
743 743
 			'source' => $row['source'],
744 744
 			'lastmodified' => $row['lastmodified'],
745
-			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
-			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
745
+			'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
746
+			'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
747 747
 		];
748 748
 
749 749
 		foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
@@ -780,16 +780,16 @@  discard block
 block discarded – undo
780 780
 		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
781 781
 		if (isset($properties[$sccs])) {
782 782
 			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
783
-				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
783
+				throw new DAV\Exception('The '.$sccs.' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
784 784
 			}
785
-			$values['components'] = implode(',',$properties[$sccs]->getValue());
785
+			$values['components'] = implode(',', $properties[$sccs]->getValue());
786 786
 		} elseif (isset($properties['components'])) {
787 787
 			// Allow to provide components internally without having
788 788
 			// to create a SupportedCalendarComponentSet object
789 789
 			$values['components'] = $properties['components'];
790 790
 		}
791 791
 
792
-		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
792
+		$transp = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
793 793
 		if (isset($properties[$transp])) {
794 794
 			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
795 795
 		}
@@ -809,7 +809,7 @@  discard block
 block discarded – undo
809 809
 		$calendarId = $query->getLastInsertId();
810 810
 
811 811
 		$calendarData = $this->getCalendarById($calendarId);
812
-		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int)$calendarId, $calendarData));
812
+		$this->dispatcher->dispatchTyped(new CalendarCreatedEvent((int) $calendarId, $calendarData));
813 813
 
814 814
 		return $calendarId;
815 815
 	}
@@ -832,13 +832,13 @@  discard block
 block discarded – undo
832 832
 	 */
833 833
 	public function updateCalendar($calendarId, PropPatch $propPatch) {
834 834
 		$supportedProperties = array_keys($this->propertyMap);
835
-		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
835
+		$supportedProperties[] = '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp';
836 836
 
837
-		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
837
+		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
838 838
 			$newValues = [];
839 839
 			foreach ($mutations as $propertyName => $propertyValue) {
840 840
 				switch ($propertyName) {
841
-					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
841
+					case '{'.Plugin::NS_CALDAV.'}schedule-calendar-transp':
842 842
 						$fieldName = 'transparent';
843 843
 						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
844 844
 						break;
@@ -860,7 +860,7 @@  discard block
 block discarded – undo
860 860
 
861 861
 			$calendarData = $this->getCalendarById($calendarId);
862 862
 			$shares = $this->getShares($calendarId);
863
-			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int)$calendarId, $calendarData, $shares, $mutations));
863
+			$this->dispatcher->dispatchTyped(new CalendarUpdatedEvent((int) $calendarId, $calendarData, $shares, $mutations));
864 864
 
865 865
 			return true;
866 866
 		});
@@ -895,7 +895,7 @@  discard block
 block discarded – undo
895 895
 
896 896
 		// Only dispatch if we actually deleted anything
897 897
 		if ($calendarData) {
898
-			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int)$calendarId, $calendarData, $shares));
898
+			$this->dispatcher->dispatchTyped(new CalendarDeletedEvent((int) $calendarId, $calendarData, $shares));
899 899
 		}
900 900
 	}
901 901
 
@@ -955,11 +955,11 @@  discard block
 block discarded – undo
955 955
 				'id' => $row['id'],
956 956
 				'uri' => $row['uri'],
957 957
 				'lastmodified' => $row['lastmodified'],
958
-				'etag' => '"' . $row['etag'] . '"',
958
+				'etag' => '"'.$row['etag'].'"',
959 959
 				'calendarid' => $row['calendarid'],
960
-				'size' => (int)$row['size'],
960
+				'size' => (int) $row['size'],
961 961
 				'component' => strtolower($row['componenttype']),
962
-				'classification' => (int)$row['classification']
962
+				'classification' => (int) $row['classification']
963 963
 			];
964 964
 		}
965 965
 		$stmt->closeCursor();
@@ -1003,12 +1003,12 @@  discard block
 block discarded – undo
1003 1003
 			'id' => $row['id'],
1004 1004
 			'uri' => $row['uri'],
1005 1005
 			'lastmodified' => $row['lastmodified'],
1006
-			'etag' => '"' . $row['etag'] . '"',
1006
+			'etag' => '"'.$row['etag'].'"',
1007 1007
 			'calendarid' => $row['calendarid'],
1008
-			'size' => (int)$row['size'],
1008
+			'size' => (int) $row['size'],
1009 1009
 			'calendardata' => $this->readBlob($row['calendardata']),
1010 1010
 			'component' => strtolower($row['componenttype']),
1011
-			'classification' => (int)$row['classification']
1011
+			'classification' => (int) $row['classification']
1012 1012
 		];
1013 1013
 	}
1014 1014
 
@@ -1049,12 +1049,12 @@  discard block
 block discarded – undo
1049 1049
 					'id' => $row['id'],
1050 1050
 					'uri' => $row['uri'],
1051 1051
 					'lastmodified' => $row['lastmodified'],
1052
-					'etag' => '"' . $row['etag'] . '"',
1052
+					'etag' => '"'.$row['etag'].'"',
1053 1053
 					'calendarid' => $row['calendarid'],
1054
-					'size' => (int)$row['size'],
1054
+					'size' => (int) $row['size'],
1055 1055
 					'calendardata' => $this->readBlob($row['calendardata']),
1056 1056
 					'component' => strtolower($row['componenttype']),
1057
-					'classification' => (int)$row['classification']
1057
+					'classification' => (int) $row['classification']
1058 1058
 				];
1059 1059
 			}
1060 1060
 			$result->closeCursor();
@@ -1126,11 +1126,11 @@  discard block
 block discarded – undo
1126 1126
 			$calendarRow = $this->getCalendarById($calendarId);
1127 1127
 			$shares = $this->getShares($calendarId);
1128 1128
 
1129
-			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1129
+			$this->dispatcher->dispatchTyped(new CalendarObjectCreatedEvent((int) $calendarId, $calendarRow, $shares, $objectRow));
1130 1130
 		} else {
1131 1131
 			$subscriptionRow = $this->getSubscriptionById($calendarId);
1132 1132
 
1133
-			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1133
+			$this->dispatcher->dispatchTyped(new CachedCalendarObjectCreatedEvent((int) $calendarId, $subscriptionRow, [], $objectRow));
1134 1134
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject', new GenericEvent(
1135 1135
 				'\OCA\DAV\CalDAV\CalDavBackend::createCachedCalendarObject',
1136 1136
 				[
@@ -1142,7 +1142,7 @@  discard block
 block discarded – undo
1142 1142
 			));
1143 1143
 		}
1144 1144
 
1145
-		return '"' . $extraData['etag'] . '"';
1145
+		return '"'.$extraData['etag'].'"';
1146 1146
 	}
1147 1147
 
1148 1148
 	/**
@@ -1191,11 +1191,11 @@  discard block
 block discarded – undo
1191 1191
 				$calendarRow = $this->getCalendarById($calendarId);
1192 1192
 				$shares = $this->getShares($calendarId);
1193 1193
 
1194
-				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int)$calendarId, $calendarRow, $shares, $objectRow));
1194
+				$this->dispatcher->dispatchTyped(new CalendarObjectUpdatedEvent((int) $calendarId, $calendarRow, $shares, $objectRow));
1195 1195
 			} else {
1196 1196
 				$subscriptionRow = $this->getSubscriptionById($calendarId);
1197 1197
 
1198
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int)$calendarId, $subscriptionRow, [], $objectRow));
1198
+				$this->dispatcher->dispatchTyped(new CachedCalendarObjectUpdatedEvent((int) $calendarId, $subscriptionRow, [], $objectRow));
1199 1199
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject', new GenericEvent(
1200 1200
 					'\OCA\DAV\CalDAV\CalDavBackend::updateCachedCalendarObject',
1201 1201
 					[
@@ -1208,7 +1208,7 @@  discard block
 block discarded – undo
1208 1208
 			}
1209 1209
 		}
1210 1210
 
1211
-		return '"' . $extraData['etag'] . '"';
1211
+		return '"'.$extraData['etag'].'"';
1212 1212
 	}
1213 1213
 
1214 1214
 	/**
@@ -1245,11 +1245,11 @@  discard block
 block discarded – undo
1245 1245
 				$calendarRow = $this->getCalendarById($calendarId);
1246 1246
 				$shares = $this->getShares($calendarId);
1247 1247
 
1248
-				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int)$calendarId, $calendarRow, $shares, $data));
1248
+				$this->dispatcher->dispatchTyped(new CalendarObjectDeletedEvent((int) $calendarId, $calendarRow, $shares, $data));
1249 1249
 			} else {
1250 1250
 				$subscriptionRow = $this->getSubscriptionById($calendarId);
1251 1251
 
1252
-				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int)$calendarId, $subscriptionRow, [], $data));
1252
+				$this->dispatcher->dispatchTyped(new CachedCalendarObjectDeletedEvent((int) $calendarId, $subscriptionRow, [], $data));
1253 1253
 				$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject', new GenericEvent(
1254 1254
 					'\OCA\DAV\CalDAV\CalDavBackend::deleteCachedCalendarObject',
1255 1255
 					[
@@ -1521,7 +1521,7 @@  discard block
 block discarded – undo
1521 1521
 
1522 1522
 		$result = [];
1523 1523
 		while ($row = $stmt->fetch()) {
1524
-			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1524
+			$path = $uriMapper[$row['calendarid']].'/'.$row['uri'];
1525 1525
 			if (!in_array($path, $result)) {
1526 1526
 				$result[] = $path;
1527 1527
 			}
@@ -1569,8 +1569,8 @@  discard block
 block discarded – undo
1569 1569
 
1570 1570
 		if ($pattern !== '') {
1571 1571
 			$innerQuery->andWhere($innerQuery->expr()->iLike('op.value',
1572
-				$outerQuery->createNamedParameter('%' .
1573
-					$this->db->escapeLikeParameter($pattern) . '%')));
1572
+				$outerQuery->createNamedParameter('%'.
1573
+					$this->db->escapeLikeParameter($pattern).'%')));
1574 1574
 		}
1575 1575
 
1576 1576
 		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
@@ -1609,7 +1609,7 @@  discard block
 block discarded – undo
1609 1609
 		$result = $outerQuery->executeQuery();
1610 1610
 		$calendarObjects = $result->fetchAll();
1611 1611
 
1612
-		return array_map(function ($o) {
1612
+		return array_map(function($o) {
1613 1613
 			$calendarData = Reader::read($o['calendardata']);
1614 1614
 			$comps = $calendarData->getComponents();
1615 1615
 			$objects = [];
@@ -1627,10 +1627,10 @@  discard block
 block discarded – undo
1627 1627
 				'type' => $o['componenttype'],
1628 1628
 				'uid' => $o['uid'],
1629 1629
 				'uri' => $o['uri'],
1630
-				'objects' => array_map(function ($c) {
1630
+				'objects' => array_map(function($c) {
1631 1631
 					return $this->transformSearchData($c);
1632 1632
 				}, $objects),
1633
-				'timezones' => array_map(function ($c) {
1633
+				'timezones' => array_map(function($c) {
1634 1634
 					return $this->transformSearchData($c);
1635 1635
 				}, $timezones),
1636 1636
 			];
@@ -1646,7 +1646,7 @@  discard block
 block discarded – undo
1646 1646
 		/** @var Component[] $subComponents */
1647 1647
 		$subComponents = $comp->getComponents();
1648 1648
 		/** @var Property[] $properties */
1649
-		$properties = array_filter($comp->children(), function ($c) {
1649
+		$properties = array_filter($comp->children(), function($c) {
1650 1650
 			return $c instanceof Property;
1651 1651
 		});
1652 1652
 		$validationRules = $comp->getValidationRules();
@@ -1724,7 +1724,7 @@  discard block
 block discarded – undo
1724 1724
 		$subscriptions = $this->getSubscriptionsForUser($principalUri);
1725 1725
 		foreach ($calendars as $calendar) {
1726 1726
 			$calendarAnd = $calendarObjectIdQuery->expr()->andX();
1727
-			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$calendar['id'])));
1727
+			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int) $calendar['id'])));
1728 1728
 			$calendarAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
1729 1729
 
1730 1730
 			// If it's shared, limit search to public events
@@ -1737,7 +1737,7 @@  discard block
 block discarded – undo
1737 1737
 		}
1738 1738
 		foreach ($subscriptions as $subscription) {
1739 1739
 			$subscriptionAnd = $calendarObjectIdQuery->expr()->andX();
1740
-			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int)$subscription['id'])));
1740
+			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendarid', $calendarObjectIdQuery->createNamedParameter((int) $subscription['id'])));
1741 1741
 			$subscriptionAnd->add($calendarObjectIdQuery->expr()->eq('cob.calendartype', $calendarObjectIdQuery->createNamedParameter(self::CALENDAR_TYPE_SUBSCRIPTION)));
1742 1742
 
1743 1743
 			// If it's shared, limit search to public events
@@ -1782,7 +1782,7 @@  discard block
 block discarded – undo
1782 1782
 			if (!$escapePattern) {
1783 1783
 				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter($pattern)));
1784 1784
 			} else {
1785
-				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%' . $this->db->escapeLikeParameter($pattern) . '%')));
1785
+				$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->ilike('cob.value', $calendarObjectIdQuery->createNamedParameter('%'.$this->db->escapeLikeParameter($pattern).'%')));
1786 1786
 			}
1787 1787
 		}
1788 1788
 
@@ -1796,7 +1796,7 @@  discard block
 block discarded – undo
1796 1796
 		$result = $calendarObjectIdQuery->executeQuery();
1797 1797
 		$matches = $result->fetchAll();
1798 1798
 		$result->closeCursor();
1799
-		$matches = array_map(static function (array $match):int {
1799
+		$matches = array_map(static function(array $match):int {
1800 1800
 			return (int) $match['objectid'];
1801 1801
 		}, $matches);
1802 1802
 
@@ -1809,9 +1809,9 @@  discard block
 block discarded – undo
1809 1809
 		$calendarObjects = $result->fetchAll();
1810 1810
 		$result->closeCursor();
1811 1811
 
1812
-		return array_map(function (array $array): array {
1813
-			$array['calendarid'] = (int)$array['calendarid'];
1814
-			$array['calendartype'] = (int)$array['calendartype'];
1812
+		return array_map(function(array $array): array {
1813
+			$array['calendarid'] = (int) $array['calendarid'];
1814
+			$array['calendartype'] = (int) $array['calendartype'];
1815 1815
 			$array['calendardata'] = $this->readBlob($array['calendardata']);
1816 1816
 
1817 1817
 			return $array;
@@ -1849,7 +1849,7 @@  discard block
 block discarded – undo
1849 1849
 		$row = $stmt->fetch();
1850 1850
 		$stmt->closeCursor();
1851 1851
 		if ($row) {
1852
-			return $row['calendaruri'] . '/' . $row['objecturi'];
1852
+			return $row['calendaruri'].'/'.$row['objecturi'];
1853 1853
 		}
1854 1854
 
1855 1855
 		return null;
@@ -2050,8 +2050,8 @@  discard block
 block discarded – undo
2050 2050
 				'source' => $row['source'],
2051 2051
 				'lastmodified' => $row['lastmodified'],
2052 2052
 
2053
-				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2054
-				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
2053
+				'{'.Plugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
2054
+				'{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0',
2055 2055
 			];
2056 2056
 
2057 2057
 			foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
@@ -2146,7 +2146,7 @@  discard block
 block discarded – undo
2146 2146
 		$supportedProperties = array_keys($this->subscriptionPropertyMap);
2147 2147
 		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
2148 2148
 
2149
-		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
2149
+		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
2150 2150
 			$newValues = [];
2151 2151
 
2152 2152
 			foreach ($mutations as $propertyName => $propertyValue) {
@@ -2168,7 +2168,7 @@  discard block
 block discarded – undo
2168 2168
 				->executeStatement();
2169 2169
 
2170 2170
 			$subscriptionRow = $this->getSubscriptionById($subscriptionId);
2171
-			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int)$subscriptionId, $subscriptionRow, [], $mutations));
2171
+			$this->dispatcher->dispatchTyped(new SubscriptionUpdatedEvent((int) $subscriptionId, $subscriptionRow, [], $mutations));
2172 2172
 			$this->legacyDispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateSubscription', new GenericEvent(
2173 2173
 				'\OCA\DAV\CalDAV\CalDavBackend::updateSubscription',
2174 2174
 				[
@@ -2219,7 +2219,7 @@  discard block
 block discarded – undo
2219 2219
 			->executeStatement();
2220 2220
 
2221 2221
 		if ($subscriptionRow) {
2222
-			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int)$subscriptionId, $subscriptionRow, []));
2222
+			$this->dispatcher->dispatchTyped(new SubscriptionDeletedEvent((int) $subscriptionId, $subscriptionRow, []));
2223 2223
 		}
2224 2224
 	}
2225 2225
 
@@ -2257,8 +2257,8 @@  discard block
 block discarded – undo
2257 2257
 			'uri' => $row['uri'],
2258 2258
 			'calendardata' => $row['calendardata'],
2259 2259
 			'lastmodified' => $row['lastmodified'],
2260
-			'etag' => '"' . $row['etag'] . '"',
2261
-			'size' => (int)$row['size'],
2260
+			'etag' => '"'.$row['etag'].'"',
2261
+			'size' => (int) $row['size'],
2262 2262
 		];
2263 2263
 	}
2264 2264
 
@@ -2286,8 +2286,8 @@  discard block
 block discarded – undo
2286 2286
 				'calendardata' => $row['calendardata'],
2287 2287
 				'uri' => $row['uri'],
2288 2288
 				'lastmodified' => $row['lastmodified'],
2289
-				'etag' => '"' . $row['etag'] . '"',
2290
-				'size' => (int)$row['size'],
2289
+				'etag' => '"'.$row['etag'].'"',
2290
+				'size' => (int) $row['size'],
2291 2291
 			];
2292 2292
 		}
2293 2293
 
@@ -2341,14 +2341,14 @@  discard block
 block discarded – undo
2341 2341
 	 * @return void
2342 2342
 	 */
2343 2343
 	protected function addChange($calendarId, $objectUri, $operation, $calendarType = self::CALENDAR_TYPE_CALENDAR) {
2344
-		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars': 'calendarsubscriptions';
2344
+		$table = $calendarType === self::CALENDAR_TYPE_CALENDAR ? 'calendars' : 'calendarsubscriptions';
2345 2345
 
2346 2346
 		$query = $this->db->getQueryBuilder();
2347 2347
 		$query->select('synctoken')
2348 2348
 			->from($table)
2349 2349
 			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
2350 2350
 		$result = $query->executeQuery();
2351
-		$syncToken = (int)$result->fetchOne();
2351
+		$syncToken = (int) $result->fetchOne();
2352 2352
 		$result->closeCursor();
2353 2353
 
2354 2354
 		$query = $this->db->getQueryBuilder();
@@ -2405,7 +2405,7 @@  discard block
 block discarded – undo
2405 2405
 				// Track first component type and uid
2406 2406
 				if ($uid === null) {
2407 2407
 					$componentType = $component->name;
2408
-					$uid = (string)$component->UID;
2408
+					$uid = (string) $component->UID;
2409 2409
 				}
2410 2410
 			}
2411 2411
 		}
@@ -2504,7 +2504,7 @@  discard block
 block discarded – undo
2504 2504
 			]));
2505 2505
 		$this->calendarSharingBackend->updateShares($shareable, $add, $remove);
2506 2506
 
2507
-		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int)$calendarId, $calendarRow, $oldShares, $add, $remove));
2507
+		$this->dispatcher->dispatchTyped(new CalendarShareUpdatedEvent((int) $calendarId, $calendarRow, $oldShares, $add, $remove));
2508 2508
 	}
2509 2509
 
2510 2510
 	/**
@@ -2544,7 +2544,7 @@  discard block
 block discarded – undo
2544 2544
 				]);
2545 2545
 			$query->executeStatement();
2546 2546
 
2547
-			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int)$calendarId, $calendarData, $publicUri));
2547
+			$this->dispatcher->dispatchTyped(new CalendarPublishedEvent((int) $calendarId, $calendarData, $publicUri));
2548 2548
 			return $publicUri;
2549 2549
 		}
2550 2550
 		$query->delete('dav_shares')
@@ -2552,7 +2552,7 @@  discard block
 block discarded – undo
2552 2552
 			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
2553 2553
 		$query->executeStatement();
2554 2554
 
2555
-		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int)$calendarId, $calendarData));
2555
+		$this->dispatcher->dispatchTyped(new CalendarUnpublishedEvent((int) $calendarId, $calendarData));
2556 2556
 		return null;
2557 2557
 	}
2558 2558
 
@@ -2775,10 +2775,10 @@  discard block
 block discarded – undo
2775 2775
 		$result->closeCursor();
2776 2776
 
2777 2777
 		if (!isset($objectIds['id'])) {
2778
-			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2778
+			throw new \InvalidArgumentException('Calendarobject does not exists: '.$uri);
2779 2779
 		}
2780 2780
 
2781
-		return (int)$objectIds['id'];
2781
+		return (int) $objectIds['id'];
2782 2782
 	}
2783 2783
 
2784 2784
 	/**
@@ -2805,8 +2805,8 @@  discard block
 block discarded – undo
2805 2805
 	 * @param $calendarInfo
2806 2806
 	 */
2807 2807
 	private function addOwnerPrincipal(&$calendarInfo) {
2808
-		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2809
-		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2808
+		$ownerPrincipalKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}owner-principal';
2809
+		$displaynameKey = '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD.'}owner-displayname';
2810 2810
 		if (isset($calendarInfo[$ownerPrincipalKey])) {
2811 2811
 			$uri = $calendarInfo[$ownerPrincipalKey];
2812 2812
 		} else {
Please login to merge, or discard this patch.
apps/dav/lib/AppInfo/Application.php 2 patches
Indentation   +254 added lines, -254 removed lines patch added patch discarded remove patch
@@ -83,267 +83,267 @@
 block discarded – undo
83 83
 use function is_null;
84 84
 
85 85
 class Application extends App implements IBootstrap {
86
-	public const APP_ID = 'dav';
86
+    public const APP_ID = 'dav';
87 87
 
88
-	public function __construct() {
89
-		parent::__construct(self::APP_ID);
90
-	}
88
+    public function __construct() {
89
+        parent::__construct(self::APP_ID);
90
+    }
91 91
 
92
-	public function register(IRegistrationContext $context): void {
93
-		$context->registerServiceAlias('CardDAVSyncService', SyncService::class);
94
-		$context->registerService(PhotoCache::class, function (ContainerInterface $c) {
95
-			/** @var IServerContainer $server */
96
-			$server = $c->get(IServerContainer::class);
92
+    public function register(IRegistrationContext $context): void {
93
+        $context->registerServiceAlias('CardDAVSyncService', SyncService::class);
94
+        $context->registerService(PhotoCache::class, function (ContainerInterface $c) {
95
+            /** @var IServerContainer $server */
96
+            $server = $c->get(IServerContainer::class);
97 97
 
98
-			return new PhotoCache(
99
-				$server->getAppDataDir('dav-photocache'),
100
-				$c->get(ILogger::class)
101
-			);
102
-		});
98
+            return new PhotoCache(
99
+                $server->getAppDataDir('dav-photocache'),
100
+                $c->get(ILogger::class)
101
+            );
102
+        });
103 103
 
104
-		/*
104
+        /*
105 105
 		 * Register capabilities
106 106
 		 */
107
-		$context->registerCapability(Capabilities::class);
107
+        $context->registerCapability(Capabilities::class);
108 108
 
109
-		/*
109
+        /*
110 110
 		 * Register Search Providers
111 111
 		 */
112
-		$context->registerSearchProvider(ContactsSearchProvider::class);
113
-		$context->registerSearchProvider(EventsSearchProvider::class);
114
-		$context->registerSearchProvider(TasksSearchProvider::class);
115
-
116
-		/**
117
-		 * Register event listeners
118
-		 */
119
-		$context->registerEventListener(CalendarCreatedEvent::class, ActivityUpdaterListener::class);
120
-		$context->registerEventListener(CalendarDeletedEvent::class, ActivityUpdaterListener::class);
121
-		$context->registerEventListener(CalendarDeletedEvent::class, CalendarObjectReminderUpdaterListener::class);
122
-		$context->registerEventListener(CalendarDeletedEvent::class, CalendarDeletionDefaultUpdaterListener::class);
123
-		$context->registerEventListener(CalendarUpdatedEvent::class, ActivityUpdaterListener::class);
124
-		$context->registerEventListener(CalendarObjectCreatedEvent::class, ActivityUpdaterListener::class);
125
-		$context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarContactInteractionListener::class);
126
-		$context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarObjectReminderUpdaterListener::class);
127
-		$context->registerEventListener(CalendarObjectUpdatedEvent::class, ActivityUpdaterListener::class);
128
-		$context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarContactInteractionListener::class);
129
-		$context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarObjectReminderUpdaterListener::class);
130
-		$context->registerEventListener(CalendarObjectDeletedEvent::class, ActivityUpdaterListener::class);
131
-		$context->registerEventListener(CalendarObjectDeletedEvent::class, CalendarObjectReminderUpdaterListener::class);
132
-		$context->registerEventListener(CalendarShareUpdatedEvent::class, CalendarContactInteractionListener::class);
133
-
134
-		$context->registerNotifierService(Notifier::class);
135
-	}
136
-
137
-	public function boot(IBootContext $context): void {
138
-		// Load all dav apps
139
-		\OC_App::loadApps(['dav']);
140
-
141
-		$context->injectFn([$this, 'registerHooks']);
142
-		$context->injectFn([$this, 'registerContactsManager']);
143
-		$context->injectFn([$this, 'registerCalendarManager']);
144
-		$context->injectFn([$this, 'registerCalendarReminders']);
145
-	}
146
-
147
-	public function registerHooks(HookManager $hm,
148
-								   EventDispatcherInterface $dispatcher,
149
-								   IAppContainer $container,
150
-								   IServerContainer $serverContainer) {
151
-		$hm->setup();
152
-
153
-		// first time login event setup
154
-		$dispatcher->addListener(IUser::class . '::firstLogin', function ($event) use ($hm) {
155
-			if ($event instanceof GenericEvent) {
156
-				$hm->firstLogin($event->getSubject());
157
-			}
158
-		});
159
-
160
-		$birthdayListener = function ($event) use ($container): void {
161
-			if ($event instanceof GenericEvent) {
162
-				/** @var BirthdayService $b */
163
-				$b = $container->query(BirthdayService::class);
164
-				$b->onCardChanged(
165
-					(int) $event->getArgument('addressBookId'),
166
-					(string) $event->getArgument('cardUri'),
167
-					(string) $event->getArgument('cardData')
168
-				);
169
-			}
170
-		};
171
-
172
-		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $birthdayListener);
173
-		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $birthdayListener);
174
-		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function ($event) use ($container) {
175
-			if ($event instanceof GenericEvent) {
176
-				/** @var BirthdayService $b */
177
-				$b = $container->query(BirthdayService::class);
178
-				$b->onCardDeleted(
179
-					(int) $event->getArgument('addressBookId'),
180
-					(string) $event->getArgument('cardUri')
181
-				);
182
-			}
183
-		});
184
-
185
-		$clearPhotoCache = function ($event) use ($container): void {
186
-			if ($event instanceof GenericEvent) {
187
-				/** @var PhotoCache $p */
188
-				$p = $container->query(PhotoCache::class);
189
-				$p->delete(
190
-					$event->getArgument('addressBookId'),
191
-					$event->getArgument('cardUri')
192
-				);
193
-			}
194
-		};
195
-		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $clearPhotoCache);
196
-		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', $clearPhotoCache);
197
-
198
-		$dispatcher->addListener('OC\AccountManager::userUpdated', function (GenericEvent $event) use ($container) {
199
-			$user = $event->getSubject();
200
-			/** @var SyncService $syncService */
201
-			$syncService = $container->query(SyncService::class);
202
-			$syncService->updateUser($user);
203
-		});
204
-
205
-		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function (GenericEvent $event) use ($container) {
206
-			/** @var Backend $backend */
207
-			$backend = $container->query(Backend::class);
208
-			$backend->onCalendarUpdateShares(
209
-				$event->getArgument('calendarData'),
210
-				$event->getArgument('shares'),
211
-				$event->getArgument('add'),
212
-				$event->getArgument('remove')
213
-			);
214
-
215
-			// Here we should recalculate if reminders should be sent to new or old sharees
216
-		});
217
-
218
-		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function (GenericEvent $event) use ($container) {
219
-			/** @var Backend $backend */
220
-			$backend = $container->query(Backend::class);
221
-			$backend->onCalendarPublication(
222
-				$event->getArgument('calendarData'),
223
-				$event->getArgument('public')
224
-			);
225
-		});
226
-
227
-
228
-		$dispatcher->addListener('OCP\Federation\TrustedServerEvent::remove',
229
-			function (GenericEvent $event) {
230
-				/** @var CardDavBackend $cardDavBackend */
231
-				$cardDavBackend = \OC::$server->query(CardDavBackend::class);
232
-				$addressBookUri = $event->getSubject();
233
-				$addressBook = $cardDavBackend->getAddressBooksByUri('principals/system/system', $addressBookUri);
234
-				if (!is_null($addressBook)) {
235
-					$cardDavBackend->deleteAddressBook($addressBook['id']);
236
-				}
237
-			}
238
-		);
239
-
240
-		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
241
-			function (GenericEvent $event) use ($container, $serverContainer) {
242
-				$jobList = $serverContainer->getJobList();
243
-				$subscriptionData = $event->getArgument('subscriptionData');
244
-
245
-				/**
246
-				 * Initial subscription refetch
247
-				 *
248
-				 * @var RefreshWebcalService $refreshWebcalService
249
-				 */
250
-				$refreshWebcalService = $container->query(RefreshWebcalService::class);
251
-				$refreshWebcalService->refreshSubscription(
252
-					(string) $subscriptionData['principaluri'],
253
-					(string) $subscriptionData['uri']
254
-				);
255
-
256
-				$jobList->add(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
257
-					'principaluri' => $subscriptionData['principaluri'],
258
-					'uri' => $subscriptionData['uri']
259
-				]);
260
-			}
261
-		);
262
-
263
-		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
264
-			function (GenericEvent $event) use ($container, $serverContainer) {
265
-				$jobList = $serverContainer->getJobList();
266
-				$subscriptionData = $event->getArgument('subscriptionData');
267
-
268
-				$jobList->remove(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
269
-					'principaluri' => $subscriptionData['principaluri'],
270
-					'uri' => $subscriptionData['uri']
271
-				]);
272
-
273
-				/** @var CalDavBackend $calDavBackend */
274
-				$calDavBackend = $container->query(CalDavBackend::class);
275
-				$calDavBackend->purgeAllCachedEventsForSubscription($subscriptionData['id']);
276
-			}
277
-		);
278
-
279
-		$eventHandler = function () use ($container, $serverContainer): void {
280
-			try {
281
-				/** @var UpdateCalendarResourcesRoomsBackgroundJob $job */
282
-				$job = $container->query(UpdateCalendarResourcesRoomsBackgroundJob::class);
283
-				$job->run([]);
284
-				$serverContainer->getJobList()->setLastRun($job);
285
-			} catch (Exception $ex) {
286
-				$serverContainer->getLogger()->logException($ex);
287
-			}
288
-		};
289
-
290
-		$dispatcher->addListener('\OCP\Calendar\Resource\ForceRefreshEvent', $eventHandler);
291
-		$dispatcher->addListener('\OCP\Calendar\Room\ForceRefreshEvent', $eventHandler);
292
-	}
293
-
294
-	public function registerContactsManager(IContactsManager $cm, IAppContainer $container): void {
295
-		$cm->register(function () use ($container, $cm): void {
296
-			$user = \OC::$server->getUserSession()->getUser();
297
-			if (!is_null($user)) {
298
-				$this->setupContactsProvider($cm, $container, $user->getUID());
299
-			} else {
300
-				$this->setupSystemContactsProvider($cm, $container);
301
-			}
302
-		});
303
-	}
304
-
305
-	private function setupContactsProvider(IContactsManager $contactsManager,
306
-										   IAppContainer $container,
307
-										   string $userID): void {
308
-		/** @var ContactsManager $cm */
309
-		$cm = $container->query(ContactsManager::class);
310
-		$urlGenerator = $container->getServer()->getURLGenerator();
311
-		$cm->setupContactsProvider($contactsManager, $userID, $urlGenerator);
312
-	}
313
-
314
-	private function setupSystemContactsProvider(IContactsManager $contactsManager,
315
-												 IAppContainer $container): void {
316
-		/** @var ContactsManager $cm */
317
-		$cm = $container->query(ContactsManager::class);
318
-		$urlGenerator = $container->getServer()->getURLGenerator();
319
-		$cm->setupSystemContactsProvider($contactsManager, $urlGenerator);
320
-	}
321
-
322
-	public function registerCalendarManager(ICalendarManager $calendarManager,
323
-											 IAppContainer $container): void {
324
-		$calendarManager->register(function () use ($container, $calendarManager) {
325
-			$user = \OC::$server->getUserSession()->getUser();
326
-			if ($user !== null) {
327
-				$this->setupCalendarProvider($calendarManager, $container, $user->getUID());
328
-			}
329
-		});
330
-	}
331
-
332
-	private function setupCalendarProvider(ICalendarManager $calendarManager,
333
-										   IAppContainer $container,
334
-										   $userId) {
335
-		$cm = $container->query(CalendarManager::class);
336
-		$cm->setupCalendarProvider($calendarManager, $userId);
337
-	}
338
-
339
-	public function registerCalendarReminders(NotificationProviderManager $manager,
340
-											   ILogger $logger): void {
341
-		try {
342
-			$manager->registerProvider(AudioProvider::class);
343
-			$manager->registerProvider(EmailProvider::class);
344
-			$manager->registerProvider(PushProvider::class);
345
-		} catch (Throwable $ex) {
346
-			$logger->logException($ex);
347
-		}
348
-	}
112
+        $context->registerSearchProvider(ContactsSearchProvider::class);
113
+        $context->registerSearchProvider(EventsSearchProvider::class);
114
+        $context->registerSearchProvider(TasksSearchProvider::class);
115
+
116
+        /**
117
+         * Register event listeners
118
+         */
119
+        $context->registerEventListener(CalendarCreatedEvent::class, ActivityUpdaterListener::class);
120
+        $context->registerEventListener(CalendarDeletedEvent::class, ActivityUpdaterListener::class);
121
+        $context->registerEventListener(CalendarDeletedEvent::class, CalendarObjectReminderUpdaterListener::class);
122
+        $context->registerEventListener(CalendarDeletedEvent::class, CalendarDeletionDefaultUpdaterListener::class);
123
+        $context->registerEventListener(CalendarUpdatedEvent::class, ActivityUpdaterListener::class);
124
+        $context->registerEventListener(CalendarObjectCreatedEvent::class, ActivityUpdaterListener::class);
125
+        $context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarContactInteractionListener::class);
126
+        $context->registerEventListener(CalendarObjectCreatedEvent::class, CalendarObjectReminderUpdaterListener::class);
127
+        $context->registerEventListener(CalendarObjectUpdatedEvent::class, ActivityUpdaterListener::class);
128
+        $context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarContactInteractionListener::class);
129
+        $context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarObjectReminderUpdaterListener::class);
130
+        $context->registerEventListener(CalendarObjectDeletedEvent::class, ActivityUpdaterListener::class);
131
+        $context->registerEventListener(CalendarObjectDeletedEvent::class, CalendarObjectReminderUpdaterListener::class);
132
+        $context->registerEventListener(CalendarShareUpdatedEvent::class, CalendarContactInteractionListener::class);
133
+
134
+        $context->registerNotifierService(Notifier::class);
135
+    }
136
+
137
+    public function boot(IBootContext $context): void {
138
+        // Load all dav apps
139
+        \OC_App::loadApps(['dav']);
140
+
141
+        $context->injectFn([$this, 'registerHooks']);
142
+        $context->injectFn([$this, 'registerContactsManager']);
143
+        $context->injectFn([$this, 'registerCalendarManager']);
144
+        $context->injectFn([$this, 'registerCalendarReminders']);
145
+    }
146
+
147
+    public function registerHooks(HookManager $hm,
148
+                                    EventDispatcherInterface $dispatcher,
149
+                                    IAppContainer $container,
150
+                                    IServerContainer $serverContainer) {
151
+        $hm->setup();
152
+
153
+        // first time login event setup
154
+        $dispatcher->addListener(IUser::class . '::firstLogin', function ($event) use ($hm) {
155
+            if ($event instanceof GenericEvent) {
156
+                $hm->firstLogin($event->getSubject());
157
+            }
158
+        });
159
+
160
+        $birthdayListener = function ($event) use ($container): void {
161
+            if ($event instanceof GenericEvent) {
162
+                /** @var BirthdayService $b */
163
+                $b = $container->query(BirthdayService::class);
164
+                $b->onCardChanged(
165
+                    (int) $event->getArgument('addressBookId'),
166
+                    (string) $event->getArgument('cardUri'),
167
+                    (string) $event->getArgument('cardData')
168
+                );
169
+            }
170
+        };
171
+
172
+        $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $birthdayListener);
173
+        $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $birthdayListener);
174
+        $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function ($event) use ($container) {
175
+            if ($event instanceof GenericEvent) {
176
+                /** @var BirthdayService $b */
177
+                $b = $container->query(BirthdayService::class);
178
+                $b->onCardDeleted(
179
+                    (int) $event->getArgument('addressBookId'),
180
+                    (string) $event->getArgument('cardUri')
181
+                );
182
+            }
183
+        });
184
+
185
+        $clearPhotoCache = function ($event) use ($container): void {
186
+            if ($event instanceof GenericEvent) {
187
+                /** @var PhotoCache $p */
188
+                $p = $container->query(PhotoCache::class);
189
+                $p->delete(
190
+                    $event->getArgument('addressBookId'),
191
+                    $event->getArgument('cardUri')
192
+                );
193
+            }
194
+        };
195
+        $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $clearPhotoCache);
196
+        $dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', $clearPhotoCache);
197
+
198
+        $dispatcher->addListener('OC\AccountManager::userUpdated', function (GenericEvent $event) use ($container) {
199
+            $user = $event->getSubject();
200
+            /** @var SyncService $syncService */
201
+            $syncService = $container->query(SyncService::class);
202
+            $syncService->updateUser($user);
203
+        });
204
+
205
+        $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function (GenericEvent $event) use ($container) {
206
+            /** @var Backend $backend */
207
+            $backend = $container->query(Backend::class);
208
+            $backend->onCalendarUpdateShares(
209
+                $event->getArgument('calendarData'),
210
+                $event->getArgument('shares'),
211
+                $event->getArgument('add'),
212
+                $event->getArgument('remove')
213
+            );
214
+
215
+            // Here we should recalculate if reminders should be sent to new or old sharees
216
+        });
217
+
218
+        $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function (GenericEvent $event) use ($container) {
219
+            /** @var Backend $backend */
220
+            $backend = $container->query(Backend::class);
221
+            $backend->onCalendarPublication(
222
+                $event->getArgument('calendarData'),
223
+                $event->getArgument('public')
224
+            );
225
+        });
226
+
227
+
228
+        $dispatcher->addListener('OCP\Federation\TrustedServerEvent::remove',
229
+            function (GenericEvent $event) {
230
+                /** @var CardDavBackend $cardDavBackend */
231
+                $cardDavBackend = \OC::$server->query(CardDavBackend::class);
232
+                $addressBookUri = $event->getSubject();
233
+                $addressBook = $cardDavBackend->getAddressBooksByUri('principals/system/system', $addressBookUri);
234
+                if (!is_null($addressBook)) {
235
+                    $cardDavBackend->deleteAddressBook($addressBook['id']);
236
+                }
237
+            }
238
+        );
239
+
240
+        $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
241
+            function (GenericEvent $event) use ($container, $serverContainer) {
242
+                $jobList = $serverContainer->getJobList();
243
+                $subscriptionData = $event->getArgument('subscriptionData');
244
+
245
+                /**
246
+                 * Initial subscription refetch
247
+                 *
248
+                 * @var RefreshWebcalService $refreshWebcalService
249
+                 */
250
+                $refreshWebcalService = $container->query(RefreshWebcalService::class);
251
+                $refreshWebcalService->refreshSubscription(
252
+                    (string) $subscriptionData['principaluri'],
253
+                    (string) $subscriptionData['uri']
254
+                );
255
+
256
+                $jobList->add(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
257
+                    'principaluri' => $subscriptionData['principaluri'],
258
+                    'uri' => $subscriptionData['uri']
259
+                ]);
260
+            }
261
+        );
262
+
263
+        $dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
264
+            function (GenericEvent $event) use ($container, $serverContainer) {
265
+                $jobList = $serverContainer->getJobList();
266
+                $subscriptionData = $event->getArgument('subscriptionData');
267
+
268
+                $jobList->remove(\OCA\DAV\BackgroundJob\RefreshWebcalJob::class, [
269
+                    'principaluri' => $subscriptionData['principaluri'],
270
+                    'uri' => $subscriptionData['uri']
271
+                ]);
272
+
273
+                /** @var CalDavBackend $calDavBackend */
274
+                $calDavBackend = $container->query(CalDavBackend::class);
275
+                $calDavBackend->purgeAllCachedEventsForSubscription($subscriptionData['id']);
276
+            }
277
+        );
278
+
279
+        $eventHandler = function () use ($container, $serverContainer): void {
280
+            try {
281
+                /** @var UpdateCalendarResourcesRoomsBackgroundJob $job */
282
+                $job = $container->query(UpdateCalendarResourcesRoomsBackgroundJob::class);
283
+                $job->run([]);
284
+                $serverContainer->getJobList()->setLastRun($job);
285
+            } catch (Exception $ex) {
286
+                $serverContainer->getLogger()->logException($ex);
287
+            }
288
+        };
289
+
290
+        $dispatcher->addListener('\OCP\Calendar\Resource\ForceRefreshEvent', $eventHandler);
291
+        $dispatcher->addListener('\OCP\Calendar\Room\ForceRefreshEvent', $eventHandler);
292
+    }
293
+
294
+    public function registerContactsManager(IContactsManager $cm, IAppContainer $container): void {
295
+        $cm->register(function () use ($container, $cm): void {
296
+            $user = \OC::$server->getUserSession()->getUser();
297
+            if (!is_null($user)) {
298
+                $this->setupContactsProvider($cm, $container, $user->getUID());
299
+            } else {
300
+                $this->setupSystemContactsProvider($cm, $container);
301
+            }
302
+        });
303
+    }
304
+
305
+    private function setupContactsProvider(IContactsManager $contactsManager,
306
+                                            IAppContainer $container,
307
+                                            string $userID): void {
308
+        /** @var ContactsManager $cm */
309
+        $cm = $container->query(ContactsManager::class);
310
+        $urlGenerator = $container->getServer()->getURLGenerator();
311
+        $cm->setupContactsProvider($contactsManager, $userID, $urlGenerator);
312
+    }
313
+
314
+    private function setupSystemContactsProvider(IContactsManager $contactsManager,
315
+                                                    IAppContainer $container): void {
316
+        /** @var ContactsManager $cm */
317
+        $cm = $container->query(ContactsManager::class);
318
+        $urlGenerator = $container->getServer()->getURLGenerator();
319
+        $cm->setupSystemContactsProvider($contactsManager, $urlGenerator);
320
+    }
321
+
322
+    public function registerCalendarManager(ICalendarManager $calendarManager,
323
+                                                IAppContainer $container): void {
324
+        $calendarManager->register(function () use ($container, $calendarManager) {
325
+            $user = \OC::$server->getUserSession()->getUser();
326
+            if ($user !== null) {
327
+                $this->setupCalendarProvider($calendarManager, $container, $user->getUID());
328
+            }
329
+        });
330
+    }
331
+
332
+    private function setupCalendarProvider(ICalendarManager $calendarManager,
333
+                                            IAppContainer $container,
334
+                                            $userId) {
335
+        $cm = $container->query(CalendarManager::class);
336
+        $cm->setupCalendarProvider($calendarManager, $userId);
337
+    }
338
+
339
+    public function registerCalendarReminders(NotificationProviderManager $manager,
340
+                                                ILogger $logger): void {
341
+        try {
342
+            $manager->registerProvider(AudioProvider::class);
343
+            $manager->registerProvider(EmailProvider::class);
344
+            $manager->registerProvider(PushProvider::class);
345
+        } catch (Throwable $ex) {
346
+            $logger->logException($ex);
347
+        }
348
+    }
349 349
 }
Please login to merge, or discard this patch.
Spacing   +14 added lines, -14 removed lines patch added patch discarded remove patch
@@ -91,7 +91,7 @@  discard block
 block discarded – undo
91 91
 
92 92
 	public function register(IRegistrationContext $context): void {
93 93
 		$context->registerServiceAlias('CardDAVSyncService', SyncService::class);
94
-		$context->registerService(PhotoCache::class, function (ContainerInterface $c) {
94
+		$context->registerService(PhotoCache::class, function(ContainerInterface $c) {
95 95
 			/** @var IServerContainer $server */
96 96
 			$server = $c->get(IServerContainer::class);
97 97
 
@@ -151,13 +151,13 @@  discard block
 block discarded – undo
151 151
 		$hm->setup();
152 152
 
153 153
 		// first time login event setup
154
-		$dispatcher->addListener(IUser::class . '::firstLogin', function ($event) use ($hm) {
154
+		$dispatcher->addListener(IUser::class.'::firstLogin', function($event) use ($hm) {
155 155
 			if ($event instanceof GenericEvent) {
156 156
 				$hm->firstLogin($event->getSubject());
157 157
 			}
158 158
 		});
159 159
 
160
-		$birthdayListener = function ($event) use ($container): void {
160
+		$birthdayListener = function($event) use ($container): void {
161 161
 			if ($event instanceof GenericEvent) {
162 162
 				/** @var BirthdayService $b */
163 163
 				$b = $container->query(BirthdayService::class);
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 
172 172
 		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::createCard', $birthdayListener);
173 173
 		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $birthdayListener);
174
-		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function ($event) use ($container) {
174
+		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', function($event) use ($container) {
175 175
 			if ($event instanceof GenericEvent) {
176 176
 				/** @var BirthdayService $b */
177 177
 				$b = $container->query(BirthdayService::class);
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 			}
183 183
 		});
184 184
 
185
-		$clearPhotoCache = function ($event) use ($container): void {
185
+		$clearPhotoCache = function($event) use ($container): void {
186 186
 			if ($event instanceof GenericEvent) {
187 187
 				/** @var PhotoCache $p */
188 188
 				$p = $container->query(PhotoCache::class);
@@ -195,14 +195,14 @@  discard block
 block discarded – undo
195 195
 		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $clearPhotoCache);
196 196
 		$dispatcher->addListener('\OCA\DAV\CardDAV\CardDavBackend::deleteCard', $clearPhotoCache);
197 197
 
198
-		$dispatcher->addListener('OC\AccountManager::userUpdated', function (GenericEvent $event) use ($container) {
198
+		$dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) use ($container) {
199 199
 			$user = $event->getSubject();
200 200
 			/** @var SyncService $syncService */
201 201
 			$syncService = $container->query(SyncService::class);
202 202
 			$syncService->updateUser($user);
203 203
 		});
204 204
 
205
-		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function (GenericEvent $event) use ($container) {
205
+		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateShares', function(GenericEvent $event) use ($container) {
206 206
 			/** @var Backend $backend */
207 207
 			$backend = $container->query(Backend::class);
208 208
 			$backend->onCalendarUpdateShares(
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
 			// Here we should recalculate if reminders should be sent to new or old sharees
216 216
 		});
217 217
 
218
-		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function (GenericEvent $event) use ($container) {
218
+		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::publishCalendar', function(GenericEvent $event) use ($container) {
219 219
 			/** @var Backend $backend */
220 220
 			$backend = $container->query(Backend::class);
221 221
 			$backend->onCalendarPublication(
@@ -226,7 +226,7 @@  discard block
 block discarded – undo
226 226
 
227 227
 
228 228
 		$dispatcher->addListener('OCP\Federation\TrustedServerEvent::remove',
229
-			function (GenericEvent $event) {
229
+			function(GenericEvent $event) {
230 230
 				/** @var CardDavBackend $cardDavBackend */
231 231
 				$cardDavBackend = \OC::$server->query(CardDavBackend::class);
232 232
 				$addressBookUri = $event->getSubject();
@@ -238,7 +238,7 @@  discard block
 block discarded – undo
238 238
 		);
239 239
 
240 240
 		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createSubscription',
241
-			function (GenericEvent $event) use ($container, $serverContainer) {
241
+			function(GenericEvent $event) use ($container, $serverContainer) {
242 242
 				$jobList = $serverContainer->getJobList();
243 243
 				$subscriptionData = $event->getArgument('subscriptionData');
244 244
 
@@ -261,7 +261,7 @@  discard block
 block discarded – undo
261 261
 		);
262 262
 
263 263
 		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteSubscription',
264
-			function (GenericEvent $event) use ($container, $serverContainer) {
264
+			function(GenericEvent $event) use ($container, $serverContainer) {
265 265
 				$jobList = $serverContainer->getJobList();
266 266
 				$subscriptionData = $event->getArgument('subscriptionData');
267 267
 
@@ -276,7 +276,7 @@  discard block
 block discarded – undo
276 276
 			}
277 277
 		);
278 278
 
279
-		$eventHandler = function () use ($container, $serverContainer): void {
279
+		$eventHandler = function() use ($container, $serverContainer): void {
280 280
 			try {
281 281
 				/** @var UpdateCalendarResourcesRoomsBackgroundJob $job */
282 282
 				$job = $container->query(UpdateCalendarResourcesRoomsBackgroundJob::class);
@@ -292,7 +292,7 @@  discard block
 block discarded – undo
292 292
 	}
293 293
 
294 294
 	public function registerContactsManager(IContactsManager $cm, IAppContainer $container): void {
295
-		$cm->register(function () use ($container, $cm): void {
295
+		$cm->register(function() use ($container, $cm): void {
296 296
 			$user = \OC::$server->getUserSession()->getUser();
297 297
 			if (!is_null($user)) {
298 298
 				$this->setupContactsProvider($cm, $container, $user->getUID());
@@ -321,7 +321,7 @@  discard block
 block discarded – undo
321 321
 
322 322
 	public function registerCalendarManager(ICalendarManager $calendarManager,
323 323
 											 IAppContainer $container): void {
324
-		$calendarManager->register(function () use ($container, $calendarManager) {
324
+		$calendarManager->register(function() use ($container, $calendarManager) {
325 325
 			$user = \OC::$server->getUserSession()->getUser();
326 326
 			if ($user !== null) {
327 327
 				$this->setupCalendarProvider($calendarManager, $container, $user->getUID());
Please login to merge, or discard this patch.
apps/dav/lib/Listener/ActivityUpdaterListener.php 2 patches
Indentation   +108 added lines, -108 removed lines patch added patch discarded remove patch
@@ -40,121 +40,121 @@
 block discarded – undo
40 40
 
41 41
 class ActivityUpdaterListener implements IEventListener {
42 42
 
43
-	/** @var ActivityBackend */
44
-	private $activityBackend;
43
+    /** @var ActivityBackend */
44
+    private $activityBackend;
45 45
 
46
-	/** @var LoggerInterface */
47
-	private $logger;
46
+    /** @var LoggerInterface */
47
+    private $logger;
48 48
 
49
-	public function __construct(ActivityBackend $activityBackend,
50
-								LoggerInterface $logger) {
51
-		$this->activityBackend = $activityBackend;
52
-		$this->logger = $logger;
53
-	}
49
+    public function __construct(ActivityBackend $activityBackend,
50
+                                LoggerInterface $logger) {
51
+        $this->activityBackend = $activityBackend;
52
+        $this->logger = $logger;
53
+    }
54 54
 
55
-	public function handle(Event $event): void {
56
-		if ($event instanceof CalendarCreatedEvent) {
57
-			try {
58
-				$this->activityBackend->onCalendarAdd(
59
-					$event->getCalendarData()
60
-				);
55
+    public function handle(Event $event): void {
56
+        if ($event instanceof CalendarCreatedEvent) {
57
+            try {
58
+                $this->activityBackend->onCalendarAdd(
59
+                    $event->getCalendarData()
60
+                );
61 61
 
62
-				$this->logger->debug(
63
-					sprintf('Activity generated for new calendar %d', $event->getCalendarId())
64
-				);
65
-			} catch (Throwable $e) {
66
-				// Any error with activities shouldn't abort the calendar creation, so we just log it
67
-				$this->logger->error('Error generating activities for a new calendar: ' . $e->getMessage(), [
68
-					'exception' => $e,
69
-				]);
70
-			}
71
-		} elseif ($event instanceof CalendarUpdatedEvent) {
72
-			try {
73
-				$this->activityBackend->onCalendarUpdate(
74
-					$event->getCalendarData(),
75
-					$event->getShares(),
76
-					$event->getMutations()
77
-				);
62
+                $this->logger->debug(
63
+                    sprintf('Activity generated for new calendar %d', $event->getCalendarId())
64
+                );
65
+            } catch (Throwable $e) {
66
+                // Any error with activities shouldn't abort the calendar creation, so we just log it
67
+                $this->logger->error('Error generating activities for a new calendar: ' . $e->getMessage(), [
68
+                    'exception' => $e,
69
+                ]);
70
+            }
71
+        } elseif ($event instanceof CalendarUpdatedEvent) {
72
+            try {
73
+                $this->activityBackend->onCalendarUpdate(
74
+                    $event->getCalendarData(),
75
+                    $event->getShares(),
76
+                    $event->getMutations()
77
+                );
78 78
 
79
-				$this->logger->debug(
80
-					sprintf('Activity generated for changed calendar %d', $event->getCalendarId())
81
-				);
82
-			} catch (Throwable $e) {
83
-				// Any error with activities shouldn't abort the calendar update, so we just log it
84
-				$this->logger->error('Error generating activities for changed calendar: ' . $e->getMessage(), [
85
-					'exception' => $e,
86
-				]);
87
-			}
88
-		} elseif ($event instanceof CalendarDeletedEvent) {
89
-			try {
90
-				$this->activityBackend->onCalendarDelete(
91
-					$event->getCalendarData(),
92
-					$event->getShares()
93
-				);
79
+                $this->logger->debug(
80
+                    sprintf('Activity generated for changed calendar %d', $event->getCalendarId())
81
+                );
82
+            } catch (Throwable $e) {
83
+                // Any error with activities shouldn't abort the calendar update, so we just log it
84
+                $this->logger->error('Error generating activities for changed calendar: ' . $e->getMessage(), [
85
+                    'exception' => $e,
86
+                ]);
87
+            }
88
+        } elseif ($event instanceof CalendarDeletedEvent) {
89
+            try {
90
+                $this->activityBackend->onCalendarDelete(
91
+                    $event->getCalendarData(),
92
+                    $event->getShares()
93
+                );
94 94
 
95
-				$this->logger->debug(
96
-					sprintf('Activity generated for deleted calendar %d', $event->getCalendarId())
97
-				);
98
-			} catch (Throwable $e) {
99
-				// Any error with activities shouldn't abort the calendar deletion, so we just log it
100
-				$this->logger->error('Error generating activities for a deleted calendar: ' . $e->getMessage(), [
101
-					'exception' => $e,
102
-				]);
103
-			}
104
-		} elseif ($event instanceof CalendarObjectCreatedEvent) {
105
-			try {
106
-				$this->activityBackend->onTouchCalendarObject(
107
-					\OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_ADD,
108
-					$event->getCalendarData(),
109
-					$event->getShares(),
110
-					$event->getObjectData()
111
-				);
95
+                $this->logger->debug(
96
+                    sprintf('Activity generated for deleted calendar %d', $event->getCalendarId())
97
+                );
98
+            } catch (Throwable $e) {
99
+                // Any error with activities shouldn't abort the calendar deletion, so we just log it
100
+                $this->logger->error('Error generating activities for a deleted calendar: ' . $e->getMessage(), [
101
+                    'exception' => $e,
102
+                ]);
103
+            }
104
+        } elseif ($event instanceof CalendarObjectCreatedEvent) {
105
+            try {
106
+                $this->activityBackend->onTouchCalendarObject(
107
+                    \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_ADD,
108
+                    $event->getCalendarData(),
109
+                    $event->getShares(),
110
+                    $event->getObjectData()
111
+                );
112 112
 
113
-				$this->logger->debug(
114
-					sprintf('Activity generated for new calendar object in calendar %d', $event->getCalendarId())
115
-				);
116
-			} catch (Throwable $e) {
117
-				// Any error with activities shouldn't abort the calendar object creation, so we just log it
118
-				$this->logger->error('Error generating activity for a new calendar object: ' . $e->getMessage(), [
119
-					'exception' => $e,
120
-				]);
121
-			}
122
-		} elseif ($event instanceof CalendarObjectUpdatedEvent) {
123
-			try {
124
-				$this->activityBackend->onTouchCalendarObject(
125
-					\OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_UPDATE,
126
-					$event->getCalendarData(),
127
-					$event->getShares(),
128
-					$event->getObjectData()
129
-				);
113
+                $this->logger->debug(
114
+                    sprintf('Activity generated for new calendar object in calendar %d', $event->getCalendarId())
115
+                );
116
+            } catch (Throwable $e) {
117
+                // Any error with activities shouldn't abort the calendar object creation, so we just log it
118
+                $this->logger->error('Error generating activity for a new calendar object: ' . $e->getMessage(), [
119
+                    'exception' => $e,
120
+                ]);
121
+            }
122
+        } elseif ($event instanceof CalendarObjectUpdatedEvent) {
123
+            try {
124
+                $this->activityBackend->onTouchCalendarObject(
125
+                    \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_UPDATE,
126
+                    $event->getCalendarData(),
127
+                    $event->getShares(),
128
+                    $event->getObjectData()
129
+                );
130 130
 
131
-				$this->logger->debug(
132
-					sprintf('Activity generated for deleted calendar object %d', $event->getCalendarId())
133
-				);
134
-			} catch (Throwable $e) {
135
-				// Any error with activities shouldn't abort the calendar deletion, so we just log it
136
-				$this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
137
-					'exception' => $e,
138
-				]);
139
-			}
140
-		} elseif ($event instanceof CalendarObjectDeletedEvent) {
141
-			try {
142
-				$this->activityBackend->onTouchCalendarObject(
143
-					\OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_DELETE,
144
-					$event->getCalendarData(),
145
-					$event->getShares(),
146
-					$event->getObjectData()
147
-				);
131
+                $this->logger->debug(
132
+                    sprintf('Activity generated for deleted calendar object %d', $event->getCalendarId())
133
+                );
134
+            } catch (Throwable $e) {
135
+                // Any error with activities shouldn't abort the calendar deletion, so we just log it
136
+                $this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
137
+                    'exception' => $e,
138
+                ]);
139
+            }
140
+        } elseif ($event instanceof CalendarObjectDeletedEvent) {
141
+            try {
142
+                $this->activityBackend->onTouchCalendarObject(
143
+                    \OCA\DAV\CalDAV\Activity\Provider\Event::SUBJECT_OBJECT_DELETE,
144
+                    $event->getCalendarData(),
145
+                    $event->getShares(),
146
+                    $event->getObjectData()
147
+                );
148 148
 
149
-				$this->logger->debug(
150
-					sprintf('Activity generated for deleted calendar object %d', $event->getCalendarId())
151
-				);
152
-			} catch (Throwable $e) {
153
-				// Any error with activities shouldn't abort the calendar deletion, so we just log it
154
-				$this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
155
-					'exception' => $e,
156
-				]);
157
-			}
158
-		}
159
-	}
149
+                $this->logger->debug(
150
+                    sprintf('Activity generated for deleted calendar object %d', $event->getCalendarId())
151
+                );
152
+            } catch (Throwable $e) {
153
+                // Any error with activities shouldn't abort the calendar deletion, so we just log it
154
+                $this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
155
+                    'exception' => $e,
156
+                ]);
157
+            }
158
+        }
159
+    }
160 160
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
 				);
65 65
 			} catch (Throwable $e) {
66 66
 				// Any error with activities shouldn't abort the calendar creation, so we just log it
67
-				$this->logger->error('Error generating activities for a new calendar: ' . $e->getMessage(), [
67
+				$this->logger->error('Error generating activities for a new calendar: '.$e->getMessage(), [
68 68
 					'exception' => $e,
69 69
 				]);
70 70
 			}
@@ -81,7 +81,7 @@  discard block
 block discarded – undo
81 81
 				);
82 82
 			} catch (Throwable $e) {
83 83
 				// Any error with activities shouldn't abort the calendar update, so we just log it
84
-				$this->logger->error('Error generating activities for changed calendar: ' . $e->getMessage(), [
84
+				$this->logger->error('Error generating activities for changed calendar: '.$e->getMessage(), [
85 85
 					'exception' => $e,
86 86
 				]);
87 87
 			}
@@ -97,7 +97,7 @@  discard block
 block discarded – undo
97 97
 				);
98 98
 			} catch (Throwable $e) {
99 99
 				// Any error with activities shouldn't abort the calendar deletion, so we just log it
100
-				$this->logger->error('Error generating activities for a deleted calendar: ' . $e->getMessage(), [
100
+				$this->logger->error('Error generating activities for a deleted calendar: '.$e->getMessage(), [
101 101
 					'exception' => $e,
102 102
 				]);
103 103
 			}
@@ -115,7 +115,7 @@  discard block
 block discarded – undo
115 115
 				);
116 116
 			} catch (Throwable $e) {
117 117
 				// Any error with activities shouldn't abort the calendar object creation, so we just log it
118
-				$this->logger->error('Error generating activity for a new calendar object: ' . $e->getMessage(), [
118
+				$this->logger->error('Error generating activity for a new calendar object: '.$e->getMessage(), [
119 119
 					'exception' => $e,
120 120
 				]);
121 121
 			}
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
 				);
134 134
 			} catch (Throwable $e) {
135 135
 				// Any error with activities shouldn't abort the calendar deletion, so we just log it
136
-				$this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
136
+				$this->logger->error('Error generating activity for a deleted calendar object: '.$e->getMessage(), [
137 137
 					'exception' => $e,
138 138
 				]);
139 139
 			}
@@ -151,7 +151,7 @@  discard block
 block discarded – undo
151 151
 				);
152 152
 			} catch (Throwable $e) {
153 153
 				// Any error with activities shouldn't abort the calendar deletion, so we just log it
154
-				$this->logger->error('Error generating activity for a deleted calendar object: ' . $e->getMessage(), [
154
+				$this->logger->error('Error generating activity for a deleted calendar object: '.$e->getMessage(), [
155 155
 					'exception' => $e,
156 156
 				]);
157 157
 			}
Please login to merge, or discard this patch.