Passed
Push — master ( 6725ad...8514a2 )
by Roeland
10:48 queued 14s
created
apps/dav/lib/CalDAV/Reminder/ReminderService.php 2 patches
Indentation   +726 added lines, -726 removed lines patch added patch discarded remove patch
@@ -40,730 +40,730 @@
 block discarded – undo
40 40
 
41 41
 class ReminderService {
42 42
 
43
-	/** @var Backend */
44
-	private $backend;
45
-
46
-	/** @var NotificationProviderManager */
47
-	private $notificationProviderManager;
48
-
49
-	/** @var IUserManager */
50
-	private $userManager;
51
-
52
-	/** @var IGroupManager */
53
-	private $groupManager;
54
-
55
-	/** @var CalDavBackend */
56
-	private $caldavBackend;
57
-
58
-	/** @var ITimeFactory */
59
-	private $timeFactory;
60
-
61
-	public const REMINDER_TYPE_EMAIL = 'EMAIL';
62
-	public const REMINDER_TYPE_DISPLAY = 'DISPLAY';
63
-	public const REMINDER_TYPE_AUDIO = 'AUDIO';
64
-
65
-	/**
66
-	 * @var String[]
67
-	 *
68
-	 * Official RFC5545 reminder types
69
-	 */
70
-	public const REMINDER_TYPES = [
71
-		self::REMINDER_TYPE_EMAIL,
72
-		self::REMINDER_TYPE_DISPLAY,
73
-		self::REMINDER_TYPE_AUDIO
74
-	];
75
-
76
-	/**
77
-	 * ReminderService constructor.
78
-	 *
79
-	 * @param Backend $backend
80
-	 * @param NotificationProviderManager $notificationProviderManager
81
-	 * @param IUserManager $userManager
82
-	 * @param IGroupManager $groupManager
83
-	 * @param CalDavBackend $caldavBackend
84
-	 * @param ITimeFactory $timeFactory
85
-	 */
86
-	public function __construct(Backend $backend,
87
-								NotificationProviderManager $notificationProviderManager,
88
-								IUserManager $userManager,
89
-								IGroupManager $groupManager,
90
-								CalDavBackend $caldavBackend,
91
-								ITimeFactory $timeFactory) {
92
-		$this->backend = $backend;
93
-		$this->notificationProviderManager = $notificationProviderManager;
94
-		$this->userManager = $userManager;
95
-		$this->groupManager = $groupManager;
96
-		$this->caldavBackend = $caldavBackend;
97
-		$this->timeFactory = $timeFactory;
98
-	}
99
-
100
-	/**
101
-	 * Process reminders to activate
102
-	 *
103
-	 * @throws NotificationProvider\ProviderNotAvailableException
104
-	 * @throws NotificationTypeDoesNotExistException
105
-	 */
106
-	public function processReminders():void {
107
-		$reminders = $this->backend->getRemindersToProcess();
108
-
109
-		foreach($reminders as $reminder) {
110
-			$vcalendar = $this->parseCalendarData($reminder['calendardata']);
111
-			if (!$vcalendar) {
112
-				$this->backend->removeReminder($reminder['id']);
113
-				continue;
114
-			}
115
-
116
-			$vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
117
-			if (!$vevent) {
118
-				$this->backend->removeReminder($reminder['id']);
119
-				continue;
120
-			}
121
-
122
-			if ($this->wasEventCancelled($vevent)) {
123
-				$this->deleteOrProcessNext($reminder, $vevent);
124
-				continue;
125
-			}
126
-
127
-			if (!$this->notificationProviderManager->hasProvider($reminder['type'])) {
128
-				$this->deleteOrProcessNext($reminder, $vevent);
129
-				continue;
130
-			}
131
-
132
-			$users = $this->getAllUsersWithWriteAccessToCalendar($reminder['calendar_id']);
133
-			$user = $this->getUserFromPrincipalURI($reminder['principaluri']);
134
-			if ($user) {
135
-				$users[] = $user;
136
-			}
137
-
138
-			$notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']);
139
-			$notificationProvider->send($vevent, $reminder['displayname'], $users);
140
-
141
-			$this->deleteOrProcessNext($reminder, $vevent);
142
-		}
143
-	}
144
-
145
-	/**
146
-	 * @param string $action
147
-	 * @param array $objectData
148
-	 * @throws VObject\InvalidDataException
149
-	 */
150
-	public function onTouchCalendarObject(string $action,
151
-										  array $objectData):void {
152
-		// We only support VEvents for now
153
-		if (strcasecmp($objectData['component'], 'vevent') !== 0) {
154
-			return;
155
-		}
156
-
157
-		switch($action) {
158
-			case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject':
159
-				$this->onCalendarObjectCreate($objectData);
160
-				break;
161
-
162
-			case '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject':
163
-				$this->onCalendarObjectEdit($objectData);
164
-				break;
165
-
166
-			case '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject':
167
-				$this->onCalendarObjectDelete($objectData);
168
-				break;
169
-
170
-			default:
171
-				break;
172
-		}
173
-	}
174
-
175
-	/**
176
-	 * @param array $objectData
177
-	 */
178
-	private function onCalendarObjectCreate(array $objectData):void {
179
-		/** @var VObject\Component\VCalendar $vcalendar */
180
-		$vcalendar = $this->parseCalendarData($objectData['calendardata']);
181
-		if (!$vcalendar) {
182
-			return;
183
-		}
184
-
185
-		$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
186
-		if (count($vevents) === 0) {
187
-			return;
188
-		}
189
-
190
-		$uid = (string) $vevents[0]->UID;
191
-		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
192
-		$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
193
-		$now = $this->timeFactory->getDateTime();
194
-		$isRecurring = $masterItem ? $this->isRecurring($masterItem) : false;
195
-
196
-		foreach($recurrenceExceptions as $recurrenceException) {
197
-			$eventHash = $this->getEventHash($recurrenceException);
198
-
199
-			if (!isset($recurrenceException->VALARM)) {
200
-				continue;
201
-			}
202
-
203
-			foreach($recurrenceException->VALARM as $valarm) {
204
-				/** @var VAlarm $valarm */
205
-				$alarmHash = $this->getAlarmHash($valarm);
206
-				$triggerTime = $valarm->getEffectiveTriggerTime();
207
-				$diff = $now->diff($triggerTime);
208
-				if ($diff->invert === 1) {
209
-					continue;
210
-				}
211
-
212
-				$alarms = $this->getRemindersForVAlarm($valarm, $objectData,
213
-					$eventHash, $alarmHash, true, true);
214
-				$this->writeRemindersToDatabase($alarms);
215
-			}
216
-		}
217
-
218
-		if ($masterItem) {
219
-			$processedAlarms = [];
220
-			$masterAlarms = [];
221
-			$masterHash = $this->getEventHash($masterItem);
222
-
223
-			if (!isset($masterItem->VALARM)) {
224
-				return;
225
-			}
226
-
227
-			foreach($masterItem->VALARM as $valarm) {
228
-				$masterAlarms[] = $this->getAlarmHash($valarm);
229
-			}
230
-
231
-			try {
232
-				$iterator = new EventIterator($vevents, $uid);
233
-			} catch (NoInstancesException $e) {
234
-				// This event is recurring, but it doesn't have a single
235
-				// instance. We are skipping this event from the output
236
-				// entirely.
237
-				return;
238
-			}
239
-
240
-			while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
241
-				$event = $iterator->getEventObject();
242
-
243
-				// Recurrence-exceptions are handled separately, so just ignore them here
244
-				if (\in_array($event, $recurrenceExceptions, true)) {
245
-					$iterator->next();
246
-					continue;
247
-				}
248
-
249
-				foreach($event->VALARM as $valarm) {
250
-					/** @var VAlarm $valarm */
251
-					$alarmHash = $this->getAlarmHash($valarm);
252
-					if (\in_array($alarmHash, $processedAlarms, true)) {
253
-						continue;
254
-					}
255
-
256
-					if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) {
257
-						// Action allows x-name, we don't insert reminders
258
-						// into the database if they are not standard
259
-						$processedAlarms[] = $alarmHash;
260
-						continue;
261
-					}
262
-
263
-					$triggerTime = $valarm->getEffectiveTriggerTime();
264
-
265
-					// If effective trigger time is in the past
266
-					// just skip and generate for next event
267
-					$diff = $now->diff($triggerTime);
268
-					if ($diff->invert === 1) {
269
-						// If an absolute alarm is in the past,
270
-						// just add it to processedAlarms, so
271
-						// we don't extend till eternity
272
-						if (!$this->isAlarmRelative($valarm)) {
273
-							$processedAlarms[] = $alarmHash;
274
-						}
275
-
276
-						continue;
277
-					}
278
-
279
-					$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
280
-					$this->writeRemindersToDatabase($alarms);
281
-					$processedAlarms[] = $alarmHash;
282
-				}
283
-
284
-				$iterator->next();
285
-			}
286
-		}
287
-	}
288
-
289
-	/**
290
-	 * @param array $objectData
291
-	 */
292
-	private function onCalendarObjectEdit(array $objectData):void {
293
-		// TODO - this can be vastly improved
294
-		//  - get cached reminders
295
-		//  - ...
296
-
297
-		$this->onCalendarObjectDelete($objectData);
298
-		$this->onCalendarObjectCreate($objectData);
299
-	}
300
-
301
-	/**
302
-	 * @param array $objectData
303
-	 */
304
-	private function onCalendarObjectDelete(array $objectData):void {
305
-		$this->backend->cleanRemindersForEvent((int) $objectData['id']);
306
-	}
307
-
308
-	/**
309
-	 * @param VAlarm $valarm
310
-	 * @param array $objectData
311
-	 * @param string|null $eventHash
312
-	 * @param string|null $alarmHash
313
-	 * @param bool $isRecurring
314
-	 * @param bool $isRecurrenceException
315
-	 * @return array
316
-	 */
317
-	private function getRemindersForVAlarm(VAlarm $valarm,
318
-										   array $objectData,
319
-										   string $eventHash=null,
320
-										   string $alarmHash=null,
321
-										   bool $isRecurring=false,
322
-										   bool $isRecurrenceException=false):array {
323
-		if ($eventHash === null) {
324
-			$eventHash = $this->getEventHash($valarm->parent);
325
-		}
326
-		if ($alarmHash === null) {
327
-			$alarmHash = $this->getAlarmHash($valarm);
328
-		}
329
-
330
-		$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent);
331
-		$isRelative = $this->isAlarmRelative($valarm);
332
-		/** @var DateTimeImmutable $notificationDate */
333
-		$notificationDate = $valarm->getEffectiveTriggerTime();
334
-		$clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
335
-		$clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
336
-
337
-		$alarms = [];
338
-
339
-		$alarms[] = [
340
-			'calendar_id' => $objectData['calendarid'],
341
-			'object_id' => $objectData['id'],
342
-			'uid' => (string) $valarm->parent->UID,
343
-			'is_recurring' => $isRecurring,
344
-			'recurrence_id' => $recurrenceId,
345
-			'is_recurrence_exception' => $isRecurrenceException,
346
-			'event_hash' => $eventHash,
347
-			'alarm_hash' => $alarmHash,
348
-			'type' => (string) $valarm->ACTION,
349
-			'is_relative' => $isRelative,
350
-			'notification_date' => $notificationDate->getTimestamp(),
351
-			'is_repeat_based' => false,
352
-		];
353
-
354
-		$repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
355
-		for($i = 0; $i < $repeat; $i++) {
356
-			if ($valarm->DURATION === null) {
357
-				continue;
358
-			}
359
-
360
-			$clonedNotificationDate->add($valarm->DURATION->getDateInterval());
361
-			$alarms[] = [
362
-				'calendar_id' => $objectData['calendarid'],
363
-				'object_id' => $objectData['id'],
364
-				'uid' => (string) $valarm->parent->UID,
365
-				'is_recurring' => $isRecurring,
366
-				'recurrence_id' => $recurrenceId,
367
-				'is_recurrence_exception' => $isRecurrenceException,
368
-				'event_hash' => $eventHash,
369
-				'alarm_hash' => $alarmHash,
370
-				'type' => (string) $valarm->ACTION,
371
-				'is_relative' => $isRelative,
372
-				'notification_date' => $clonedNotificationDate->getTimestamp(),
373
-				'is_repeat_based' => true,
374
-			];
375
-		}
376
-
377
-		return $alarms;
378
-	}
379
-
380
-	/**
381
-	 * @param array $reminders
382
-	 */
383
-	private function writeRemindersToDatabase(array $reminders): void {
384
-		foreach($reminders as $reminder) {
385
-			$this->backend->insertReminder(
386
-				(int) $reminder['calendar_id'],
387
-				(int) $reminder['object_id'],
388
-				$reminder['uid'],
389
-				$reminder['is_recurring'],
390
-				(int) $reminder['recurrence_id'],
391
-				$reminder['is_recurrence_exception'],
392
-				$reminder['event_hash'],
393
-				$reminder['alarm_hash'],
394
-				$reminder['type'],
395
-				$reminder['is_relative'],
396
-				(int) $reminder['notification_date'],
397
-				$reminder['is_repeat_based']
398
-			);
399
-		}
400
-	}
401
-
402
-	/**
403
-	 * @param array $reminder
404
-	 * @param VEvent $vevent
405
-	 */
406
-	private function deleteOrProcessNext(array $reminder,
407
-										 VObject\Component\VEvent $vevent):void {
408
-		if ($reminder['is_repeat_based'] ||
409
-			!$reminder['is_recurring'] ||
410
-			!$reminder['is_relative'] ||
411
-			$reminder['is_recurrence_exception']) {
412
-
413
-			$this->backend->removeReminder($reminder['id']);
414
-			return;
415
-		}
416
-
417
-		$vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
418
-		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
419
-		$now = $this->timeFactory->getDateTime();
420
-
421
-		try {
422
-			$iterator = new EventIterator($vevents, $reminder['uid']);
423
-		} catch (NoInstancesException $e) {
424
-			// This event is recurring, but it doesn't have a single
425
-			// instance. We are skipping this event from the output
426
-			// entirely.
427
-			return;
428
-		}
429
-
430
-		while($iterator->valid()) {
431
-			$event = $iterator->getEventObject();
432
-
433
-			// Recurrence-exceptions are handled separately, so just ignore them here
434
-			if (\in_array($event, $recurrenceExceptions, true)) {
435
-				$iterator->next();
436
-				continue;
437
-			}
438
-
439
-			$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event);
440
-			if ($reminder['recurrence_id'] >= $recurrenceId) {
441
-				$iterator->next();
442
-				continue;
443
-			}
444
-
445
-			foreach($event->VALARM as $valarm) {
446
-				/** @var VAlarm $valarm */
447
-				$alarmHash = $this->getAlarmHash($valarm);
448
-				if ($alarmHash !== $reminder['alarm_hash']) {
449
-					continue;
450
-				}
451
-
452
-				$triggerTime = $valarm->getEffectiveTriggerTime();
453
-
454
-				// If effective trigger time is in the past
455
-				// just skip and generate for next event
456
-				$diff = $now->diff($triggerTime);
457
-				if ($diff->invert === 1) {
458
-					continue;
459
-				}
460
-
461
-				$this->backend->removeReminder($reminder['id']);
462
-				$alarms = $this->getRemindersForVAlarm($valarm, [
463
-					'calendarid' => $reminder['calendar_id'],
464
-					'id' => $reminder['object_id'],
465
-				], $reminder['event_hash'], $alarmHash, true, false);
466
-				$this->writeRemindersToDatabase($alarms);
467
-
468
-				// Abort generating reminders after creating one successfully
469
-				return;
470
-			}
471
-
472
-			$iterator->next();
473
-		}
474
-
475
-		$this->backend->removeReminder($reminder['id']);
476
-	}
477
-
478
-	/**
479
-	 * @param int $calendarId
480
-	 * @return IUser[]
481
-	 */
482
-	private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array {
483
-		$shares = $this->caldavBackend->getShares($calendarId);
484
-
485
-		$users = [];
486
-		$userIds = [];
487
-		$groups = [];
488
-		foreach ($shares as $share) {
489
-			// Only consider writable shares
490
-			if ($share['readOnly']) {
491
-				continue;
492
-			}
493
-
494
-			$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
495
-			if ($principal[1] === 'users') {
496
-				$user = $this->userManager->get($principal[2]);
497
-				if ($user) {
498
-					$users[] = $user;
499
-					$userIds[] = $principal[2];
500
-				}
501
-			} else if ($principal[1] === 'groups') {
502
-				$groups[] = $principal[2];
503
-			}
504
-		}
505
-
506
-		foreach ($groups as $gid) {
507
-			$group = $this->groupManager->get($gid);
508
-			if ($group instanceof IGroup) {
509
-				foreach ($group->getUsers() as $user) {
510
-					if (!\in_array($user->getUID(), $userIds, true)) {
511
-						$users[] = $user;
512
-						$userIds[] = $user->getUID();
513
-					}
514
-				}
515
-			}
516
-		}
517
-
518
-		return $users;
519
-	}
520
-
521
-	/**
522
-	 * Gets a hash of the event.
523
-	 * If the hash changes, we have to update all relative alarms.
524
-	 *
525
-	 * @param VEvent $vevent
526
-	 * @return string
527
-	 */
528
-	private function getEventHash(VEvent $vevent):string {
529
-		$properties = [
530
-			(string) $vevent->DTSTART->serialize(),
531
-		];
532
-
533
-		if ($vevent->DTEND) {
534
-			$properties[] = (string) $vevent->DTEND->serialize();
535
-		}
536
-		if ($vevent->DURATION) {
537
-			$properties[] = (string) $vevent->DURATION->serialize();
538
-		}
539
-		if ($vevent->{'RECURRENCE-ID'}) {
540
-			$properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize();
541
-		}
542
-		if ($vevent->RRULE) {
543
-			$properties[] = (string) $vevent->RRULE->serialize();
544
-		}
545
-		if ($vevent->EXDATE) {
546
-			$properties[] = (string) $vevent->EXDATE->serialize();
547
-		}
548
-		if ($vevent->RDATE) {
549
-			$properties[] = (string) $vevent->RDATE->serialize();
550
-		}
551
-
552
-		return md5(implode('::', $properties));
553
-	}
554
-
555
-	/**
556
-	 * Gets a hash of the alarm.
557
-	 * If the hash changes, we have to update oc_dav_reminders.
558
-	 *
559
-	 * @param VAlarm $valarm
560
-	 * @return string
561
-	 */
562
-	private function getAlarmHash(VAlarm $valarm):string {
563
-		$properties = [
564
-			(string) $valarm->ACTION->serialize(),
565
-			(string) $valarm->TRIGGER->serialize(),
566
-		];
567
-
568
-		if ($valarm->DURATION) {
569
-			$properties[] = (string) $valarm->DURATION->serialize();
570
-		}
571
-		if ($valarm->REPEAT) {
572
-			$properties[] = (string) $valarm->REPEAT->serialize();
573
-		}
574
-
575
-		return md5(implode('::', $properties));
576
-	}
577
-
578
-	/**
579
-	 * @param VObject\Component\VCalendar $vcalendar
580
-	 * @param int $recurrenceId
581
-	 * @param bool $isRecurrenceException
582
-	 * @return VEvent|null
583
-	 */
584
-	private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
585
-											 int $recurrenceId,
586
-											 bool $isRecurrenceException):?VEvent {
587
-		$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
588
-		if (count($vevents) === 0) {
589
-			return null;
590
-		}
591
-
592
-		$uid = (string) $vevents[0]->UID;
593
-		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
594
-		$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
595
-
596
-		// Handle recurrence-exceptions first, because recurrence-expansion is expensive
597
-		if ($isRecurrenceException) {
598
-			foreach($recurrenceExceptions as $recurrenceException) {
599
-				if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
600
-					return $recurrenceException;
601
-				}
602
-			}
603
-
604
-			return null;
605
-		}
606
-
607
-		if ($masterItem) {
608
-			try {
609
-				$iterator = new EventIterator($vevents, $uid);
610
-			} catch (NoInstancesException $e) {
611
-				// This event is recurring, but it doesn't have a single
612
-				// instance. We are skipping this event from the output
613
-				// entirely.
614
-				return null;
615
-			}
616
-
617
-			while ($iterator->valid()) {
618
-				$event = $iterator->getEventObject();
619
-
620
-				// Recurrence-exceptions are handled separately, so just ignore them here
621
-				if (\in_array($event, $recurrenceExceptions, true)) {
622
-					$iterator->next();
623
-					continue;
624
-				}
625
-
626
-				if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) {
627
-					return $event;
628
-				}
629
-
630
-				$iterator->next();
631
-			}
632
-		}
633
-
634
-		return null;
635
-	}
636
-
637
-	/**
638
-	 * @param VEvent $vevent
639
-	 * @return string
640
-	 */
641
-	private function getStatusOfEvent(VEvent $vevent):string {
642
-		if ($vevent->STATUS) {
643
-			return (string) $vevent->STATUS;
644
-		}
645
-
646
-		// Doesn't say so in the standard,
647
-		// but we consider events without a status
648
-		// to be confirmed
649
-		return 'CONFIRMED';
650
-	}
651
-
652
-	/**
653
-	 * @param VObject\Component\VEvent $vevent
654
-	 * @return bool
655
-	 */
656
-	private function wasEventCancelled(VObject\Component\VEvent $vevent):bool {
657
-		return $this->getStatusOfEvent($vevent) === 'CANCELLED';
658
-	}
659
-
660
-	/**
661
-	 * @param string $calendarData
662
-	 * @return VObject\Component\VCalendar|null
663
-	 */
664
-	private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
665
-		try {
666
-			return VObject\Reader::read($calendarData,
667
-				VObject\Reader::OPTION_FORGIVING);
668
-		} catch(ParseException $ex) {
669
-			return null;
670
-		}
671
-	}
672
-
673
-	/**
674
-	 * @param string $principalUri
675
-	 * @return IUser|null
676
-	 */
677
-	private function getUserFromPrincipalURI(string $principalUri):?IUser {
678
-		if (!$principalUri) {
679
-			return null;
680
-		}
681
-
682
-		if (stripos($principalUri, 'principals/users/') !== 0) {
683
-			return null;
684
-		}
685
-
686
-		$userId = substr($principalUri, 17);
687
-		return $this->userManager->get($userId);
688
-	}
689
-
690
-	/**
691
-	 * @param VObject\Component\VCalendar $vcalendar
692
-	 * @return VObject\Component\VEvent[]
693
-	 */
694
-	private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
695
-		$vevents = [];
696
-
697
-		foreach($vcalendar->children() as $child) {
698
-			if (!($child instanceof VObject\Component)) {
699
-				continue;
700
-			}
701
-
702
-			if ($child->name !== 'VEVENT') {
703
-				continue;
704
-			}
705
-
706
-			$vevents[] = $child;
707
-		}
708
-
709
-		return $vevents;
710
-	}
711
-
712
-	/**
713
-	 * @param array $vevents
714
-	 * @return VObject\Component\VEvent[]
715
-	 */
716
-	private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array {
717
-		return array_values(array_filter($vevents, function(VEvent $vevent) {
718
-			return $vevent->{'RECURRENCE-ID'} !== null;
719
-		}));
720
-	}
721
-
722
-	/**
723
-	 * @param array $vevents
724
-	 * @return VEvent|null
725
-	 */
726
-	private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
727
-		$elements = array_values(array_filter($vevents, function(VEvent $vevent) {
728
-			return $vevent->{'RECURRENCE-ID'} === null;
729
-		}));
730
-
731
-		if (count($elements) === 0) {
732
-			return null;
733
-		}
734
-		if (count($elements) > 1) {
735
-			throw new \TypeError('Multiple master objects');
736
-		}
737
-
738
-		return $elements[0];
739
-	}
740
-
741
-	/**
742
-	 * @param VAlarm $valarm
743
-	 * @return bool
744
-	 */
745
-	private function isAlarmRelative(VAlarm $valarm):bool {
746
-		$trigger = $valarm->TRIGGER;
747
-		return $trigger instanceof VObject\Property\ICalendar\Duration;
748
-	}
749
-
750
-	/**
751
-	 * @param VEvent $vevent
752
-	 * @return int
753
-	 */
754
-	private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
755
-		if (isset($vevent->{'RECURRENCE-ID'})) {
756
-			return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp();
757
-		}
758
-
759
-		return $vevent->DTSTART->getDateTime()->getTimestamp();
760
-	}
761
-
762
-	/**
763
-	 * @param VEvent $vevent
764
-	 * @return bool
765
-	 */
766
-	private function isRecurring(VEvent $vevent):bool {
767
-		return isset($vevent->RRULE) || isset($vevent->RDATE);
768
-	}
43
+    /** @var Backend */
44
+    private $backend;
45
+
46
+    /** @var NotificationProviderManager */
47
+    private $notificationProviderManager;
48
+
49
+    /** @var IUserManager */
50
+    private $userManager;
51
+
52
+    /** @var IGroupManager */
53
+    private $groupManager;
54
+
55
+    /** @var CalDavBackend */
56
+    private $caldavBackend;
57
+
58
+    /** @var ITimeFactory */
59
+    private $timeFactory;
60
+
61
+    public const REMINDER_TYPE_EMAIL = 'EMAIL';
62
+    public const REMINDER_TYPE_DISPLAY = 'DISPLAY';
63
+    public const REMINDER_TYPE_AUDIO = 'AUDIO';
64
+
65
+    /**
66
+     * @var String[]
67
+     *
68
+     * Official RFC5545 reminder types
69
+     */
70
+    public const REMINDER_TYPES = [
71
+        self::REMINDER_TYPE_EMAIL,
72
+        self::REMINDER_TYPE_DISPLAY,
73
+        self::REMINDER_TYPE_AUDIO
74
+    ];
75
+
76
+    /**
77
+     * ReminderService constructor.
78
+     *
79
+     * @param Backend $backend
80
+     * @param NotificationProviderManager $notificationProviderManager
81
+     * @param IUserManager $userManager
82
+     * @param IGroupManager $groupManager
83
+     * @param CalDavBackend $caldavBackend
84
+     * @param ITimeFactory $timeFactory
85
+     */
86
+    public function __construct(Backend $backend,
87
+                                NotificationProviderManager $notificationProviderManager,
88
+                                IUserManager $userManager,
89
+                                IGroupManager $groupManager,
90
+                                CalDavBackend $caldavBackend,
91
+                                ITimeFactory $timeFactory) {
92
+        $this->backend = $backend;
93
+        $this->notificationProviderManager = $notificationProviderManager;
94
+        $this->userManager = $userManager;
95
+        $this->groupManager = $groupManager;
96
+        $this->caldavBackend = $caldavBackend;
97
+        $this->timeFactory = $timeFactory;
98
+    }
99
+
100
+    /**
101
+     * Process reminders to activate
102
+     *
103
+     * @throws NotificationProvider\ProviderNotAvailableException
104
+     * @throws NotificationTypeDoesNotExistException
105
+     */
106
+    public function processReminders():void {
107
+        $reminders = $this->backend->getRemindersToProcess();
108
+
109
+        foreach($reminders as $reminder) {
110
+            $vcalendar = $this->parseCalendarData($reminder['calendardata']);
111
+            if (!$vcalendar) {
112
+                $this->backend->removeReminder($reminder['id']);
113
+                continue;
114
+            }
115
+
116
+            $vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
117
+            if (!$vevent) {
118
+                $this->backend->removeReminder($reminder['id']);
119
+                continue;
120
+            }
121
+
122
+            if ($this->wasEventCancelled($vevent)) {
123
+                $this->deleteOrProcessNext($reminder, $vevent);
124
+                continue;
125
+            }
126
+
127
+            if (!$this->notificationProviderManager->hasProvider($reminder['type'])) {
128
+                $this->deleteOrProcessNext($reminder, $vevent);
129
+                continue;
130
+            }
131
+
132
+            $users = $this->getAllUsersWithWriteAccessToCalendar($reminder['calendar_id']);
133
+            $user = $this->getUserFromPrincipalURI($reminder['principaluri']);
134
+            if ($user) {
135
+                $users[] = $user;
136
+            }
137
+
138
+            $notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']);
139
+            $notificationProvider->send($vevent, $reminder['displayname'], $users);
140
+
141
+            $this->deleteOrProcessNext($reminder, $vevent);
142
+        }
143
+    }
144
+
145
+    /**
146
+     * @param string $action
147
+     * @param array $objectData
148
+     * @throws VObject\InvalidDataException
149
+     */
150
+    public function onTouchCalendarObject(string $action,
151
+                                            array $objectData):void {
152
+        // We only support VEvents for now
153
+        if (strcasecmp($objectData['component'], 'vevent') !== 0) {
154
+            return;
155
+        }
156
+
157
+        switch($action) {
158
+            case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject':
159
+                $this->onCalendarObjectCreate($objectData);
160
+                break;
161
+
162
+            case '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject':
163
+                $this->onCalendarObjectEdit($objectData);
164
+                break;
165
+
166
+            case '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject':
167
+                $this->onCalendarObjectDelete($objectData);
168
+                break;
169
+
170
+            default:
171
+                break;
172
+        }
173
+    }
174
+
175
+    /**
176
+     * @param array $objectData
177
+     */
178
+    private function onCalendarObjectCreate(array $objectData):void {
179
+        /** @var VObject\Component\VCalendar $vcalendar */
180
+        $vcalendar = $this->parseCalendarData($objectData['calendardata']);
181
+        if (!$vcalendar) {
182
+            return;
183
+        }
184
+
185
+        $vevents = $this->getAllVEventsFromVCalendar($vcalendar);
186
+        if (count($vevents) === 0) {
187
+            return;
188
+        }
189
+
190
+        $uid = (string) $vevents[0]->UID;
191
+        $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
192
+        $masterItem = $this->getMasterItemFromListOfVEvents($vevents);
193
+        $now = $this->timeFactory->getDateTime();
194
+        $isRecurring = $masterItem ? $this->isRecurring($masterItem) : false;
195
+
196
+        foreach($recurrenceExceptions as $recurrenceException) {
197
+            $eventHash = $this->getEventHash($recurrenceException);
198
+
199
+            if (!isset($recurrenceException->VALARM)) {
200
+                continue;
201
+            }
202
+
203
+            foreach($recurrenceException->VALARM as $valarm) {
204
+                /** @var VAlarm $valarm */
205
+                $alarmHash = $this->getAlarmHash($valarm);
206
+                $triggerTime = $valarm->getEffectiveTriggerTime();
207
+                $diff = $now->diff($triggerTime);
208
+                if ($diff->invert === 1) {
209
+                    continue;
210
+                }
211
+
212
+                $alarms = $this->getRemindersForVAlarm($valarm, $objectData,
213
+                    $eventHash, $alarmHash, true, true);
214
+                $this->writeRemindersToDatabase($alarms);
215
+            }
216
+        }
217
+
218
+        if ($masterItem) {
219
+            $processedAlarms = [];
220
+            $masterAlarms = [];
221
+            $masterHash = $this->getEventHash($masterItem);
222
+
223
+            if (!isset($masterItem->VALARM)) {
224
+                return;
225
+            }
226
+
227
+            foreach($masterItem->VALARM as $valarm) {
228
+                $masterAlarms[] = $this->getAlarmHash($valarm);
229
+            }
230
+
231
+            try {
232
+                $iterator = new EventIterator($vevents, $uid);
233
+            } catch (NoInstancesException $e) {
234
+                // This event is recurring, but it doesn't have a single
235
+                // instance. We are skipping this event from the output
236
+                // entirely.
237
+                return;
238
+            }
239
+
240
+            while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
241
+                $event = $iterator->getEventObject();
242
+
243
+                // Recurrence-exceptions are handled separately, so just ignore them here
244
+                if (\in_array($event, $recurrenceExceptions, true)) {
245
+                    $iterator->next();
246
+                    continue;
247
+                }
248
+
249
+                foreach($event->VALARM as $valarm) {
250
+                    /** @var VAlarm $valarm */
251
+                    $alarmHash = $this->getAlarmHash($valarm);
252
+                    if (\in_array($alarmHash, $processedAlarms, true)) {
253
+                        continue;
254
+                    }
255
+
256
+                    if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) {
257
+                        // Action allows x-name, we don't insert reminders
258
+                        // into the database if they are not standard
259
+                        $processedAlarms[] = $alarmHash;
260
+                        continue;
261
+                    }
262
+
263
+                    $triggerTime = $valarm->getEffectiveTriggerTime();
264
+
265
+                    // If effective trigger time is in the past
266
+                    // just skip and generate for next event
267
+                    $diff = $now->diff($triggerTime);
268
+                    if ($diff->invert === 1) {
269
+                        // If an absolute alarm is in the past,
270
+                        // just add it to processedAlarms, so
271
+                        // we don't extend till eternity
272
+                        if (!$this->isAlarmRelative($valarm)) {
273
+                            $processedAlarms[] = $alarmHash;
274
+                        }
275
+
276
+                        continue;
277
+                    }
278
+
279
+                    $alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
280
+                    $this->writeRemindersToDatabase($alarms);
281
+                    $processedAlarms[] = $alarmHash;
282
+                }
283
+
284
+                $iterator->next();
285
+            }
286
+        }
287
+    }
288
+
289
+    /**
290
+     * @param array $objectData
291
+     */
292
+    private function onCalendarObjectEdit(array $objectData):void {
293
+        // TODO - this can be vastly improved
294
+        //  - get cached reminders
295
+        //  - ...
296
+
297
+        $this->onCalendarObjectDelete($objectData);
298
+        $this->onCalendarObjectCreate($objectData);
299
+    }
300
+
301
+    /**
302
+     * @param array $objectData
303
+     */
304
+    private function onCalendarObjectDelete(array $objectData):void {
305
+        $this->backend->cleanRemindersForEvent((int) $objectData['id']);
306
+    }
307
+
308
+    /**
309
+     * @param VAlarm $valarm
310
+     * @param array $objectData
311
+     * @param string|null $eventHash
312
+     * @param string|null $alarmHash
313
+     * @param bool $isRecurring
314
+     * @param bool $isRecurrenceException
315
+     * @return array
316
+     */
317
+    private function getRemindersForVAlarm(VAlarm $valarm,
318
+                                            array $objectData,
319
+                                            string $eventHash=null,
320
+                                            string $alarmHash=null,
321
+                                            bool $isRecurring=false,
322
+                                            bool $isRecurrenceException=false):array {
323
+        if ($eventHash === null) {
324
+            $eventHash = $this->getEventHash($valarm->parent);
325
+        }
326
+        if ($alarmHash === null) {
327
+            $alarmHash = $this->getAlarmHash($valarm);
328
+        }
329
+
330
+        $recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent);
331
+        $isRelative = $this->isAlarmRelative($valarm);
332
+        /** @var DateTimeImmutable $notificationDate */
333
+        $notificationDate = $valarm->getEffectiveTriggerTime();
334
+        $clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
335
+        $clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
336
+
337
+        $alarms = [];
338
+
339
+        $alarms[] = [
340
+            'calendar_id' => $objectData['calendarid'],
341
+            'object_id' => $objectData['id'],
342
+            'uid' => (string) $valarm->parent->UID,
343
+            'is_recurring' => $isRecurring,
344
+            'recurrence_id' => $recurrenceId,
345
+            'is_recurrence_exception' => $isRecurrenceException,
346
+            'event_hash' => $eventHash,
347
+            'alarm_hash' => $alarmHash,
348
+            'type' => (string) $valarm->ACTION,
349
+            'is_relative' => $isRelative,
350
+            'notification_date' => $notificationDate->getTimestamp(),
351
+            'is_repeat_based' => false,
352
+        ];
353
+
354
+        $repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
355
+        for($i = 0; $i < $repeat; $i++) {
356
+            if ($valarm->DURATION === null) {
357
+                continue;
358
+            }
359
+
360
+            $clonedNotificationDate->add($valarm->DURATION->getDateInterval());
361
+            $alarms[] = [
362
+                'calendar_id' => $objectData['calendarid'],
363
+                'object_id' => $objectData['id'],
364
+                'uid' => (string) $valarm->parent->UID,
365
+                'is_recurring' => $isRecurring,
366
+                'recurrence_id' => $recurrenceId,
367
+                'is_recurrence_exception' => $isRecurrenceException,
368
+                'event_hash' => $eventHash,
369
+                'alarm_hash' => $alarmHash,
370
+                'type' => (string) $valarm->ACTION,
371
+                'is_relative' => $isRelative,
372
+                'notification_date' => $clonedNotificationDate->getTimestamp(),
373
+                'is_repeat_based' => true,
374
+            ];
375
+        }
376
+
377
+        return $alarms;
378
+    }
379
+
380
+    /**
381
+     * @param array $reminders
382
+     */
383
+    private function writeRemindersToDatabase(array $reminders): void {
384
+        foreach($reminders as $reminder) {
385
+            $this->backend->insertReminder(
386
+                (int) $reminder['calendar_id'],
387
+                (int) $reminder['object_id'],
388
+                $reminder['uid'],
389
+                $reminder['is_recurring'],
390
+                (int) $reminder['recurrence_id'],
391
+                $reminder['is_recurrence_exception'],
392
+                $reminder['event_hash'],
393
+                $reminder['alarm_hash'],
394
+                $reminder['type'],
395
+                $reminder['is_relative'],
396
+                (int) $reminder['notification_date'],
397
+                $reminder['is_repeat_based']
398
+            );
399
+        }
400
+    }
401
+
402
+    /**
403
+     * @param array $reminder
404
+     * @param VEvent $vevent
405
+     */
406
+    private function deleteOrProcessNext(array $reminder,
407
+                                            VObject\Component\VEvent $vevent):void {
408
+        if ($reminder['is_repeat_based'] ||
409
+            !$reminder['is_recurring'] ||
410
+            !$reminder['is_relative'] ||
411
+            $reminder['is_recurrence_exception']) {
412
+
413
+            $this->backend->removeReminder($reminder['id']);
414
+            return;
415
+        }
416
+
417
+        $vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
418
+        $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
419
+        $now = $this->timeFactory->getDateTime();
420
+
421
+        try {
422
+            $iterator = new EventIterator($vevents, $reminder['uid']);
423
+        } catch (NoInstancesException $e) {
424
+            // This event is recurring, but it doesn't have a single
425
+            // instance. We are skipping this event from the output
426
+            // entirely.
427
+            return;
428
+        }
429
+
430
+        while($iterator->valid()) {
431
+            $event = $iterator->getEventObject();
432
+
433
+            // Recurrence-exceptions are handled separately, so just ignore them here
434
+            if (\in_array($event, $recurrenceExceptions, true)) {
435
+                $iterator->next();
436
+                continue;
437
+            }
438
+
439
+            $recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event);
440
+            if ($reminder['recurrence_id'] >= $recurrenceId) {
441
+                $iterator->next();
442
+                continue;
443
+            }
444
+
445
+            foreach($event->VALARM as $valarm) {
446
+                /** @var VAlarm $valarm */
447
+                $alarmHash = $this->getAlarmHash($valarm);
448
+                if ($alarmHash !== $reminder['alarm_hash']) {
449
+                    continue;
450
+                }
451
+
452
+                $triggerTime = $valarm->getEffectiveTriggerTime();
453
+
454
+                // If effective trigger time is in the past
455
+                // just skip and generate for next event
456
+                $diff = $now->diff($triggerTime);
457
+                if ($diff->invert === 1) {
458
+                    continue;
459
+                }
460
+
461
+                $this->backend->removeReminder($reminder['id']);
462
+                $alarms = $this->getRemindersForVAlarm($valarm, [
463
+                    'calendarid' => $reminder['calendar_id'],
464
+                    'id' => $reminder['object_id'],
465
+                ], $reminder['event_hash'], $alarmHash, true, false);
466
+                $this->writeRemindersToDatabase($alarms);
467
+
468
+                // Abort generating reminders after creating one successfully
469
+                return;
470
+            }
471
+
472
+            $iterator->next();
473
+        }
474
+
475
+        $this->backend->removeReminder($reminder['id']);
476
+    }
477
+
478
+    /**
479
+     * @param int $calendarId
480
+     * @return IUser[]
481
+     */
482
+    private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array {
483
+        $shares = $this->caldavBackend->getShares($calendarId);
484
+
485
+        $users = [];
486
+        $userIds = [];
487
+        $groups = [];
488
+        foreach ($shares as $share) {
489
+            // Only consider writable shares
490
+            if ($share['readOnly']) {
491
+                continue;
492
+            }
493
+
494
+            $principal = explode('/', $share['{http://owncloud.org/ns}principal']);
495
+            if ($principal[1] === 'users') {
496
+                $user = $this->userManager->get($principal[2]);
497
+                if ($user) {
498
+                    $users[] = $user;
499
+                    $userIds[] = $principal[2];
500
+                }
501
+            } else if ($principal[1] === 'groups') {
502
+                $groups[] = $principal[2];
503
+            }
504
+        }
505
+
506
+        foreach ($groups as $gid) {
507
+            $group = $this->groupManager->get($gid);
508
+            if ($group instanceof IGroup) {
509
+                foreach ($group->getUsers() as $user) {
510
+                    if (!\in_array($user->getUID(), $userIds, true)) {
511
+                        $users[] = $user;
512
+                        $userIds[] = $user->getUID();
513
+                    }
514
+                }
515
+            }
516
+        }
517
+
518
+        return $users;
519
+    }
520
+
521
+    /**
522
+     * Gets a hash of the event.
523
+     * If the hash changes, we have to update all relative alarms.
524
+     *
525
+     * @param VEvent $vevent
526
+     * @return string
527
+     */
528
+    private function getEventHash(VEvent $vevent):string {
529
+        $properties = [
530
+            (string) $vevent->DTSTART->serialize(),
531
+        ];
532
+
533
+        if ($vevent->DTEND) {
534
+            $properties[] = (string) $vevent->DTEND->serialize();
535
+        }
536
+        if ($vevent->DURATION) {
537
+            $properties[] = (string) $vevent->DURATION->serialize();
538
+        }
539
+        if ($vevent->{'RECURRENCE-ID'}) {
540
+            $properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize();
541
+        }
542
+        if ($vevent->RRULE) {
543
+            $properties[] = (string) $vevent->RRULE->serialize();
544
+        }
545
+        if ($vevent->EXDATE) {
546
+            $properties[] = (string) $vevent->EXDATE->serialize();
547
+        }
548
+        if ($vevent->RDATE) {
549
+            $properties[] = (string) $vevent->RDATE->serialize();
550
+        }
551
+
552
+        return md5(implode('::', $properties));
553
+    }
554
+
555
+    /**
556
+     * Gets a hash of the alarm.
557
+     * If the hash changes, we have to update oc_dav_reminders.
558
+     *
559
+     * @param VAlarm $valarm
560
+     * @return string
561
+     */
562
+    private function getAlarmHash(VAlarm $valarm):string {
563
+        $properties = [
564
+            (string) $valarm->ACTION->serialize(),
565
+            (string) $valarm->TRIGGER->serialize(),
566
+        ];
567
+
568
+        if ($valarm->DURATION) {
569
+            $properties[] = (string) $valarm->DURATION->serialize();
570
+        }
571
+        if ($valarm->REPEAT) {
572
+            $properties[] = (string) $valarm->REPEAT->serialize();
573
+        }
574
+
575
+        return md5(implode('::', $properties));
576
+    }
577
+
578
+    /**
579
+     * @param VObject\Component\VCalendar $vcalendar
580
+     * @param int $recurrenceId
581
+     * @param bool $isRecurrenceException
582
+     * @return VEvent|null
583
+     */
584
+    private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
585
+                                                int $recurrenceId,
586
+                                                bool $isRecurrenceException):?VEvent {
587
+        $vevents = $this->getAllVEventsFromVCalendar($vcalendar);
588
+        if (count($vevents) === 0) {
589
+            return null;
590
+        }
591
+
592
+        $uid = (string) $vevents[0]->UID;
593
+        $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
594
+        $masterItem = $this->getMasterItemFromListOfVEvents($vevents);
595
+
596
+        // Handle recurrence-exceptions first, because recurrence-expansion is expensive
597
+        if ($isRecurrenceException) {
598
+            foreach($recurrenceExceptions as $recurrenceException) {
599
+                if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
600
+                    return $recurrenceException;
601
+                }
602
+            }
603
+
604
+            return null;
605
+        }
606
+
607
+        if ($masterItem) {
608
+            try {
609
+                $iterator = new EventIterator($vevents, $uid);
610
+            } catch (NoInstancesException $e) {
611
+                // This event is recurring, but it doesn't have a single
612
+                // instance. We are skipping this event from the output
613
+                // entirely.
614
+                return null;
615
+            }
616
+
617
+            while ($iterator->valid()) {
618
+                $event = $iterator->getEventObject();
619
+
620
+                // Recurrence-exceptions are handled separately, so just ignore them here
621
+                if (\in_array($event, $recurrenceExceptions, true)) {
622
+                    $iterator->next();
623
+                    continue;
624
+                }
625
+
626
+                if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) {
627
+                    return $event;
628
+                }
629
+
630
+                $iterator->next();
631
+            }
632
+        }
633
+
634
+        return null;
635
+    }
636
+
637
+    /**
638
+     * @param VEvent $vevent
639
+     * @return string
640
+     */
641
+    private function getStatusOfEvent(VEvent $vevent):string {
642
+        if ($vevent->STATUS) {
643
+            return (string) $vevent->STATUS;
644
+        }
645
+
646
+        // Doesn't say so in the standard,
647
+        // but we consider events without a status
648
+        // to be confirmed
649
+        return 'CONFIRMED';
650
+    }
651
+
652
+    /**
653
+     * @param VObject\Component\VEvent $vevent
654
+     * @return bool
655
+     */
656
+    private function wasEventCancelled(VObject\Component\VEvent $vevent):bool {
657
+        return $this->getStatusOfEvent($vevent) === 'CANCELLED';
658
+    }
659
+
660
+    /**
661
+     * @param string $calendarData
662
+     * @return VObject\Component\VCalendar|null
663
+     */
664
+    private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
665
+        try {
666
+            return VObject\Reader::read($calendarData,
667
+                VObject\Reader::OPTION_FORGIVING);
668
+        } catch(ParseException $ex) {
669
+            return null;
670
+        }
671
+    }
672
+
673
+    /**
674
+     * @param string $principalUri
675
+     * @return IUser|null
676
+     */
677
+    private function getUserFromPrincipalURI(string $principalUri):?IUser {
678
+        if (!$principalUri) {
679
+            return null;
680
+        }
681
+
682
+        if (stripos($principalUri, 'principals/users/') !== 0) {
683
+            return null;
684
+        }
685
+
686
+        $userId = substr($principalUri, 17);
687
+        return $this->userManager->get($userId);
688
+    }
689
+
690
+    /**
691
+     * @param VObject\Component\VCalendar $vcalendar
692
+     * @return VObject\Component\VEvent[]
693
+     */
694
+    private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
695
+        $vevents = [];
696
+
697
+        foreach($vcalendar->children() as $child) {
698
+            if (!($child instanceof VObject\Component)) {
699
+                continue;
700
+            }
701
+
702
+            if ($child->name !== 'VEVENT') {
703
+                continue;
704
+            }
705
+
706
+            $vevents[] = $child;
707
+        }
708
+
709
+        return $vevents;
710
+    }
711
+
712
+    /**
713
+     * @param array $vevents
714
+     * @return VObject\Component\VEvent[]
715
+     */
716
+    private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array {
717
+        return array_values(array_filter($vevents, function(VEvent $vevent) {
718
+            return $vevent->{'RECURRENCE-ID'} !== null;
719
+        }));
720
+    }
721
+
722
+    /**
723
+     * @param array $vevents
724
+     * @return VEvent|null
725
+     */
726
+    private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
727
+        $elements = array_values(array_filter($vevents, function(VEvent $vevent) {
728
+            return $vevent->{'RECURRENCE-ID'} === null;
729
+        }));
730
+
731
+        if (count($elements) === 0) {
732
+            return null;
733
+        }
734
+        if (count($elements) > 1) {
735
+            throw new \TypeError('Multiple master objects');
736
+        }
737
+
738
+        return $elements[0];
739
+    }
740
+
741
+    /**
742
+     * @param VAlarm $valarm
743
+     * @return bool
744
+     */
745
+    private function isAlarmRelative(VAlarm $valarm):bool {
746
+        $trigger = $valarm->TRIGGER;
747
+        return $trigger instanceof VObject\Property\ICalendar\Duration;
748
+    }
749
+
750
+    /**
751
+     * @param VEvent $vevent
752
+     * @return int
753
+     */
754
+    private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
755
+        if (isset($vevent->{'RECURRENCE-ID'})) {
756
+            return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp();
757
+        }
758
+
759
+        return $vevent->DTSTART->getDateTime()->getTimestamp();
760
+    }
761
+
762
+    /**
763
+     * @param VEvent $vevent
764
+     * @return bool
765
+     */
766
+    private function isRecurring(VEvent $vevent):bool {
767
+        return isset($vevent->RRULE) || isset($vevent->RDATE);
768
+    }
769 769
 }
Please login to merge, or discard this patch.
Spacing   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -106,7 +106,7 @@  discard block
 block discarded – undo
106 106
 	public function processReminders():void {
107 107
 		$reminders = $this->backend->getRemindersToProcess();
108 108
 
109
-		foreach($reminders as $reminder) {
109
+		foreach ($reminders as $reminder) {
110 110
 			$vcalendar = $this->parseCalendarData($reminder['calendardata']);
111 111
 			if (!$vcalendar) {
112 112
 				$this->backend->removeReminder($reminder['id']);
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
 			return;
155 155
 		}
156 156
 
157
-		switch($action) {
157
+		switch ($action) {
158 158
 			case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject':
159 159
 				$this->onCalendarObjectCreate($objectData);
160 160
 				break;
@@ -193,14 +193,14 @@  discard block
 block discarded – undo
193 193
 		$now = $this->timeFactory->getDateTime();
194 194
 		$isRecurring = $masterItem ? $this->isRecurring($masterItem) : false;
195 195
 
196
-		foreach($recurrenceExceptions as $recurrenceException) {
196
+		foreach ($recurrenceExceptions as $recurrenceException) {
197 197
 			$eventHash = $this->getEventHash($recurrenceException);
198 198
 
199 199
 			if (!isset($recurrenceException->VALARM)) {
200 200
 				continue;
201 201
 			}
202 202
 
203
-			foreach($recurrenceException->VALARM as $valarm) {
203
+			foreach ($recurrenceException->VALARM as $valarm) {
204 204
 				/** @var VAlarm $valarm */
205 205
 				$alarmHash = $this->getAlarmHash($valarm);
206 206
 				$triggerTime = $valarm->getEffectiveTriggerTime();
@@ -224,7 +224,7 @@  discard block
 block discarded – undo
224 224
 				return;
225 225
 			}
226 226
 
227
-			foreach($masterItem->VALARM as $valarm) {
227
+			foreach ($masterItem->VALARM as $valarm) {
228 228
 				$masterAlarms[] = $this->getAlarmHash($valarm);
229 229
 			}
230 230
 
@@ -237,7 +237,7 @@  discard block
 block discarded – undo
237 237
 				return;
238 238
 			}
239 239
 
240
-			while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
240
+			while ($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
241 241
 				$event = $iterator->getEventObject();
242 242
 
243 243
 				// Recurrence-exceptions are handled separately, so just ignore them here
@@ -246,7 +246,7 @@  discard block
 block discarded – undo
246 246
 					continue;
247 247
 				}
248 248
 
249
-				foreach($event->VALARM as $valarm) {
249
+				foreach ($event->VALARM as $valarm) {
250 250
 					/** @var VAlarm $valarm */
251 251
 					$alarmHash = $this->getAlarmHash($valarm);
252 252
 					if (\in_array($alarmHash, $processedAlarms, true)) {
@@ -316,10 +316,10 @@  discard block
 block discarded – undo
316 316
 	 */
317 317
 	private function getRemindersForVAlarm(VAlarm $valarm,
318 318
 										   array $objectData,
319
-										   string $eventHash=null,
320
-										   string $alarmHash=null,
321
-										   bool $isRecurring=false,
322
-										   bool $isRecurrenceException=false):array {
319
+										   string $eventHash = null,
320
+										   string $alarmHash = null,
321
+										   bool $isRecurring = false,
322
+										   bool $isRecurrenceException = false):array {
323 323
 		if ($eventHash === null) {
324 324
 			$eventHash = $this->getEventHash($valarm->parent);
325 325
 		}
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
 		];
353 353
 
354 354
 		$repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
355
-		for($i = 0; $i < $repeat; $i++) {
355
+		for ($i = 0; $i < $repeat; $i++) {
356 356
 			if ($valarm->DURATION === null) {
357 357
 				continue;
358 358
 			}
@@ -381,7 +381,7 @@  discard block
 block discarded – undo
381 381
 	 * @param array $reminders
382 382
 	 */
383 383
 	private function writeRemindersToDatabase(array $reminders): void {
384
-		foreach($reminders as $reminder) {
384
+		foreach ($reminders as $reminder) {
385 385
 			$this->backend->insertReminder(
386 386
 				(int) $reminder['calendar_id'],
387 387
 				(int) $reminder['object_id'],
@@ -427,7 +427,7 @@  discard block
 block discarded – undo
427 427
 			return;
428 428
 		}
429 429
 
430
-		while($iterator->valid()) {
430
+		while ($iterator->valid()) {
431 431
 			$event = $iterator->getEventObject();
432 432
 
433 433
 			// Recurrence-exceptions are handled separately, so just ignore them here
@@ -442,7 +442,7 @@  discard block
 block discarded – undo
442 442
 				continue;
443 443
 			}
444 444
 
445
-			foreach($event->VALARM as $valarm) {
445
+			foreach ($event->VALARM as $valarm) {
446 446
 				/** @var VAlarm $valarm */
447 447
 				$alarmHash = $this->getAlarmHash($valarm);
448 448
 				if ($alarmHash !== $reminder['alarm_hash']) {
@@ -583,7 +583,7 @@  discard block
 block discarded – undo
583 583
 	 */
584 584
 	private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
585 585
 											 int $recurrenceId,
586
-											 bool $isRecurrenceException):?VEvent {
586
+											 bool $isRecurrenceException): ?VEvent {
587 587
 		$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
588 588
 		if (count($vevents) === 0) {
589 589
 			return null;
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
 
596 596
 		// Handle recurrence-exceptions first, because recurrence-expansion is expensive
597 597
 		if ($isRecurrenceException) {
598
-			foreach($recurrenceExceptions as $recurrenceException) {
598
+			foreach ($recurrenceExceptions as $recurrenceException) {
599 599
 				if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
600 600
 					return $recurrenceException;
601 601
 				}
@@ -661,11 +661,11 @@  discard block
 block discarded – undo
661 661
 	 * @param string $calendarData
662 662
 	 * @return VObject\Component\VCalendar|null
663 663
 	 */
664
-	private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
664
+	private function parseCalendarData(string $calendarData): ?VObject\Component\VCalendar {
665 665
 		try {
666 666
 			return VObject\Reader::read($calendarData,
667 667
 				VObject\Reader::OPTION_FORGIVING);
668
-		} catch(ParseException $ex) {
668
+		} catch (ParseException $ex) {
669 669
 			return null;
670 670
 		}
671 671
 	}
@@ -674,7 +674,7 @@  discard block
 block discarded – undo
674 674
 	 * @param string $principalUri
675 675
 	 * @return IUser|null
676 676
 	 */
677
-	private function getUserFromPrincipalURI(string $principalUri):?IUser {
677
+	private function getUserFromPrincipalURI(string $principalUri): ?IUser {
678 678
 		if (!$principalUri) {
679 679
 			return null;
680 680
 		}
@@ -694,7 +694,7 @@  discard block
 block discarded – undo
694 694
 	private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
695 695
 		$vevents = [];
696 696
 
697
-		foreach($vcalendar->children() as $child) {
697
+		foreach ($vcalendar->children() as $child) {
698 698
 			if (!($child instanceof VObject\Component)) {
699 699
 				continue;
700 700
 			}
@@ -723,7 +723,7 @@  discard block
 block discarded – undo
723 723
 	 * @param array $vevents
724 724
 	 * @return VEvent|null
725 725
 	 */
726
-	private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
726
+	private function getMasterItemFromListOfVEvents(array $vevents): ?VEvent {
727 727
 		$elements = array_values(array_filter($vevents, function(VEvent $vevent) {
728 728
 			return $vevent->{'RECURRENCE-ID'} === null;
729 729
 		}));
Please login to merge, or discard this patch.