Passed
Push — master ( 9c2d70...6ef7ba )
by Roeland
10:28
created

ReminderService::getMasterItemFromListOfVEvents()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 13
rs 10
1
<?php
2
declare(strict_types=1);
3
/**
4
 * @copyright Copyright (c) 2019, Thomas Citharel
5
 * @copyright Copyright (c) 2019, Georg Ehrke
6
 *
7
 * @author Thomas Citharel <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
namespace OCA\DAV\CalDAV\Reminder;
26
27
use \DateTimeImmutable;
28
use OCA\DAV\CalDAV\CalDavBackend;
29
use OCP\AppFramework\Utility\ITimeFactory;
30
use OCP\IGroup;
31
use OCP\IGroupManager;
32
use OCP\IUser;
33
use OCP\IUserManager;
34
use Sabre\VObject;
35
use Sabre\VObject\Component\VAlarm;
36
use Sabre\VObject\Component\VEvent;
37
use Sabre\VObject\ParseException;
38
use Sabre\VObject\Recur\EventIterator;
39
use Sabre\VObject\Recur\NoInstancesException;
40
41
class ReminderService {
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) {
0 ignored issues
show
introduced by
$vcalendar is of type Sabre\VObject\Component\VCalendar, thus it always evaluated to true.
Loading history...
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
			foreach($recurrenceException->VALARM as $valarm) {
200
				/** @var VAlarm $valarm */
201
				$alarmHash = $this->getAlarmHash($valarm);
202
				$triggerTime = $valarm->getEffectiveTriggerTime();
203
				$diff = $now->diff($triggerTime);
204
				if ($diff->invert === 1) {
205
					continue;
206
				}
207
208
				$alarms = $this->getRemindersForVAlarm($valarm, $objectData,
209
					$eventHash, $alarmHash, true, true);
210
				$this->writeRemindersToDatabase($alarms);
211
			}
212
		}
213
214
		if ($masterItem) {
215
			$processedAlarms = [];
216
			$masterAlarms = [];
217
			$masterHash = $this->getEventHash($masterItem);
218
219
			foreach($masterItem->VALARM as $valarm) {
220
				$masterAlarms[] = $this->getAlarmHash($valarm);
221
			}
222
223
			try {
224
				$iterator = new EventIterator($vevents, $uid);
225
			} catch (NoInstancesException $e) {
226
				// This event is recurring, but it doesn't have a single
227
				// instance. We are skipping this event from the output
228
				// entirely.
229
				return;
230
			}
231
232
			while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
233
				$event = $iterator->getEventObject();
234
235
				// Recurrence-exceptions are handled separately, so just ignore them here
236
				if (\in_array($event, $recurrenceExceptions, true)) {
237
					$iterator->next();
238
					continue;
239
				}
240
241
				foreach($event->VALARM as $valarm) {
242
					/** @var VAlarm $valarm */
243
					$alarmHash = $this->getAlarmHash($valarm);
244
					if (\in_array($alarmHash, $processedAlarms, true)) {
245
						continue;
246
					}
247
248
					if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) {
249
						// Action allows x-name, we don't insert reminders
250
						// into the database if they are not standard
251
						$processedAlarms[] = $alarmHash;
252
						continue;
253
					}
254
255
					$triggerTime = $valarm->getEffectiveTriggerTime();
256
257
					// If effective trigger time is in the past
258
					// just skip and generate for next event
259
					$diff = $now->diff($triggerTime);
260
					if ($diff->invert === 1) {
261
						// If an absolute alarm is in the past,
262
						// just add it to processedAlarms, so
263
						// we don't extend till eternity
264
						if (!$this->isAlarmRelative($valarm)) {
265
							$processedAlarms[] = $alarmHash;
266
						}
267
268
						continue;
269
					}
270
271
					$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
272
					$this->writeRemindersToDatabase($alarms);
273
					$processedAlarms[] = $alarmHash;
274
				}
275
276
				$iterator->next();
277
			}
278
		}
279
	}
280
281
	/**
282
	 * @param array $objectData
283
	 */
284
	private function onCalendarObjectEdit(array $objectData):void {
285
		// TODO - this can be vastly improved
286
		//  - get cached reminders
287
		//  - ...
288
289
		$this->onCalendarObjectDelete($objectData);
290
		$this->onCalendarObjectCreate($objectData);
291
	}
292
293
	/**
294
	 * @param array $objectData
295
	 */
296
	private function onCalendarObjectDelete(array $objectData):void {
297
		$this->backend->cleanRemindersForEvent((int) $objectData['id']);
298
	}
299
300
	/**
301
	 * @param VAlarm $valarm
302
	 * @param array $objectData
303
	 * @param string|null $eventHash
304
	 * @param string|null $alarmHash
305
	 * @param bool $isRecurring
306
	 * @param bool $isRecurrenceException
307
	 * @return array
308
	 */
309
	private function getRemindersForVAlarm(VAlarm $valarm,
310
										   array $objectData,
311
										   string $eventHash=null,
312
										   string $alarmHash=null,
313
										   bool $isRecurring=false,
314
										   bool $isRecurrenceException=false):array {
315
		if ($eventHash === null) {
316
			$eventHash = $this->getEventHash($valarm->parent);
317
		}
318
		if ($alarmHash === null) {
319
			$alarmHash = $this->getAlarmHash($valarm);
320
		}
321
322
		$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent);
323
		$isRelative = $this->isAlarmRelative($valarm);
324
		/** @var DateTimeImmutable $notificationDate */
325
		$notificationDate = $valarm->getEffectiveTriggerTime();
326
		$clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
327
		$clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
328
329
		$alarms = [];
330
331
		$alarms[] = [
332
			'calendar_id' => $objectData['calendarid'],
333
			'object_id' => $objectData['id'],
334
			'uid' => (string) $valarm->parent->UID,
335
			'is_recurring' => $isRecurring,
336
			'recurrence_id' => $recurrenceId,
337
			'is_recurrence_exception' => $isRecurrenceException,
338
			'event_hash' => $eventHash,
339
			'alarm_hash' => $alarmHash,
340
			'type' => (string) $valarm->ACTION,
341
			'is_relative' => $isRelative,
342
			'notification_date' => $notificationDate->getTimestamp(),
343
			'is_repeat_based' => false,
344
		];
345
346
		$repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
347
		for($i = 0; $i < $repeat; $i++) {
348
			if ($valarm->DURATION === null) {
349
				continue;
350
			}
351
352
			$clonedNotificationDate->add($valarm->DURATION->getDateInterval());
0 ignored issues
show
Bug introduced by
The method getDateInterval() does not exist on Sabre\VObject\Property. It seems like you code against a sub-type of Sabre\VObject\Property such as Sabre\VObject\Property\ICalendar\Duration. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

352
			$clonedNotificationDate->add($valarm->DURATION->/** @scrutinizer ignore-call */ getDateInterval());
Loading history...
353
			$alarms[] = [
354
				'calendar_id' => $objectData['calendarid'],
355
				'object_id' => $objectData['id'],
356
				'uid' => (string) $valarm->parent->UID,
357
				'is_recurring' => $isRecurring,
358
				'recurrence_id' => $recurrenceId,
359
				'is_recurrence_exception' => $isRecurrenceException,
360
				'event_hash' => $eventHash,
361
				'alarm_hash' => $alarmHash,
362
				'type' => (string) $valarm->ACTION,
363
				'is_relative' => $isRelative,
364
				'notification_date' => $clonedNotificationDate->getTimestamp(),
365
				'is_repeat_based' => true,
366
			];
367
		}
368
369
		return $alarms;
370
	}
371
372
	/**
373
	 * @param array $reminders
374
	 */
375
	private function writeRemindersToDatabase(array $reminders): void {
376
		foreach($reminders as $reminder) {
377
			$this->backend->insertReminder(
378
				(int) $reminder['calendar_id'],
379
				(int) $reminder['object_id'],
380
				$reminder['uid'],
381
				$reminder['is_recurring'],
382
				(int) $reminder['recurrence_id'],
383
				$reminder['is_recurrence_exception'],
384
				$reminder['event_hash'],
385
				$reminder['alarm_hash'],
386
				$reminder['type'],
387
				$reminder['is_relative'],
388
				(int) $reminder['notification_date'],
389
				$reminder['is_repeat_based']
390
			);
391
		}
392
	}
393
394
	/**
395
	 * @param array $reminder
396
	 * @param VEvent $vevent
397
	 */
398
	private function deleteOrProcessNext(array $reminder,
399
										 VObject\Component\VEvent $vevent):void {
400
		if ($reminder['is_repeat_based'] ||
401
			!$reminder['is_recurring'] ||
402
			!$reminder['is_relative'] ||
403
			$reminder['is_recurrence_exception']) {
404
405
			$this->backend->removeReminder($reminder['id']);
406
			return;
407
		}
408
409
		$vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
410
		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
411
		$now = $this->timeFactory->getDateTime();
412
413
		try {
414
			$iterator = new EventIterator($vevents, $reminder['uid']);
415
		} catch (NoInstancesException $e) {
416
			// This event is recurring, but it doesn't have a single
417
			// instance. We are skipping this event from the output
418
			// entirely.
419
			return;
420
		}
421
422
		while($iterator->valid()) {
423
			$event = $iterator->getEventObject();
424
425
			// Recurrence-exceptions are handled separately, so just ignore them here
426
			if (\in_array($event, $recurrenceExceptions, true)) {
427
				$iterator->next();
428
				continue;
429
			}
430
431
			$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event);
432
			if ($reminder['recurrence_id'] >= $recurrenceId) {
433
				$iterator->next();
434
				continue;
435
			}
436
437
			foreach($event->VALARM as $valarm) {
438
				/** @var VAlarm $valarm */
439
				$alarmHash = $this->getAlarmHash($valarm);
440
				if ($alarmHash !== $reminder['alarm_hash']) {
441
					continue;
442
				}
443
444
				$triggerTime = $valarm->getEffectiveTriggerTime();
445
446
				// If effective trigger time is in the past
447
				// just skip and generate for next event
448
				$diff = $now->diff($triggerTime);
449
				if ($diff->invert === 1) {
450
					continue;
451
				}
452
453
				$this->backend->removeReminder($reminder['id']);
454
				$alarms = $this->getRemindersForVAlarm($valarm, [
455
					'calendarid' => $reminder['calendar_id'],
456
					'id' => $reminder['object_id'],
457
				], $reminder['event_hash'], $alarmHash, true, false);
458
				$this->writeRemindersToDatabase($alarms);
459
460
				// Abort generating reminders after creating one successfully
461
				return;
462
			}
463
464
			$iterator->next();
465
		}
466
467
		$this->backend->removeReminder($reminder['id']);
468
	}
469
470
	/**
471
	 * @param int $calendarId
472
	 * @return IUser[]
473
	 */
474
	private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array {
475
		$shares = $this->caldavBackend->getShares($calendarId);
476
477
		$users = [];
478
		$userIds = [];
479
		$groups = [];
480
		foreach ($shares as $share) {
481
			// Only consider writable shares
482
			if ($share['readOnly']) {
483
				continue;
484
			}
485
486
			$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
487
			if ($principal[1] === 'users') {
488
				$user = $this->userManager->get($principal[2]);
489
				if ($user) {
490
					$users[] = $user;
491
					$userIds[] = $principal[2];
492
				}
493
			} else if ($principal[1] === 'groups') {
494
				$groups[] = $principal[2];
495
			}
496
		}
497
498
		foreach ($groups as $gid) {
499
			$group = $this->groupManager->get($gid);
500
			if ($group instanceof IGroup) {
501
				foreach ($group->getUsers() as $user) {
502
					if (!\in_array($user->getUID(), $userIds, true)) {
503
						$users[] = $user;
504
						$userIds[] = $user->getUID();
505
					}
506
				}
507
			}
508
		}
509
510
		return $users;
511
	}
512
513
	/**
514
	 * Gets a hash of the event.
515
	 * If the hash changes, we have to update all relative alarms.
516
	 *
517
	 * @param VEvent $vevent
518
	 * @return string
519
	 */
520
	private function getEventHash(VEvent $vevent):string {
521
		$properties = [
522
			(string) $vevent->DTSTART->serialize(),
523
		];
524
525
		if ($vevent->DTEND) {
526
			$properties[] = (string) $vevent->DTEND->serialize();
527
		}
528
		if ($vevent->DURATION) {
529
			$properties[] = (string) $vevent->DURATION->serialize();
530
		}
531
		if ($vevent->{'RECURRENCE-ID'}) {
532
			$properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize();
533
		}
534
		if ($vevent->RRULE) {
535
			$properties[] = (string) $vevent->RRULE->serialize();
536
		}
537
		if ($vevent->EXDATE) {
538
			$properties[] = (string) $vevent->EXDATE->serialize();
539
		}
540
		if ($vevent->RDATE) {
541
			$properties[] = (string) $vevent->RDATE->serialize();
542
		}
543
544
		return md5(implode('::', $properties));
545
	}
546
547
	/**
548
	 * Gets a hash of the alarm.
549
	 * If the hash changes, we have to update oc_dav_reminders.
550
	 *
551
	 * @param VAlarm $valarm
552
	 * @return string
553
	 */
554
	private function getAlarmHash(VAlarm $valarm):string {
555
		$properties = [
556
			(string) $valarm->ACTION->serialize(),
557
			(string) $valarm->TRIGGER->serialize(),
558
		];
559
560
		if ($valarm->DURATION) {
561
			$properties[] = (string) $valarm->DURATION->serialize();
562
		}
563
		if ($valarm->REPEAT) {
564
			$properties[] = (string) $valarm->REPEAT->serialize();
565
		}
566
567
		return md5(implode('::', $properties));
568
	}
569
570
	/**
571
	 * @param VObject\Component\VCalendar $vcalendar
572
	 * @param int $recurrenceId
573
	 * @param bool $isRecurrenceException
574
	 * @return VEvent|null
575
	 */
576
	private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
577
											 int $recurrenceId,
578
											 bool $isRecurrenceException):?VEvent {
579
		$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
580
		if (count($vevents) === 0) {
581
			return null;
582
		}
583
584
		$uid = (string) $vevents[0]->UID;
585
		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
586
		$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
587
588
		// Handle recurrence-exceptions first, because recurrence-expansion is expensive
589
		if ($isRecurrenceException) {
590
			foreach($recurrenceExceptions as $recurrenceException) {
591
				if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
592
					return $recurrenceException;
593
				}
594
			}
595
596
			return null;
597
		}
598
599
		if ($masterItem) {
600
			try {
601
				$iterator = new EventIterator($vevents, $uid);
602
			} catch (NoInstancesException $e) {
603
				// This event is recurring, but it doesn't have a single
604
				// instance. We are skipping this event from the output
605
				// entirely.
606
				return null;
607
			}
608
609
			while ($iterator->valid()) {
610
				$event = $iterator->getEventObject();
611
612
				// Recurrence-exceptions are handled separately, so just ignore them here
613
				if (\in_array($event, $recurrenceExceptions, true)) {
614
					$iterator->next();
615
					continue;
616
				}
617
618
				if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) {
619
					return $event;
620
				}
621
622
				$iterator->next();
623
			}
624
		}
625
626
		return null;
627
	}
628
629
	/**
630
	 * @param VEvent $vevent
631
	 * @return string
632
	 */
633
	private function getStatusOfEvent(VEvent $vevent):string {
634
		if ($vevent->STATUS) {
635
			return (string) $vevent->STATUS;
636
		}
637
638
		// Doesn't say so in the standard,
639
		// but we consider events without a status
640
		// to be confirmed
641
		return 'CONFIRMED';
642
	}
643
644
	/**
645
	 * @param VObject\Component\VEvent $vevent
646
	 * @return bool
647
	 */
648
	private function wasEventCancelled(VObject\Component\VEvent $vevent):bool {
649
		return $this->getStatusOfEvent($vevent) === 'CANCELLED';
650
	}
651
652
	/**
653
	 * @param string $calendarData
654
	 * @return VObject\Component\VCalendar|null
655
	 */
656
	private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
657
		try {
658
			return VObject\Reader::read($calendarData,
0 ignored issues
show
Bug Best Practice introduced by
The expression return Sabre\VObject\Rea...ader::OPTION_FORGIVING) returns the type Sabre\VObject\Document which includes types incompatible with the type-hinted return Sabre\VObject\Component\VCalendar|null.
Loading history...
659
				VObject\Reader::OPTION_FORGIVING);
660
		} catch(ParseException $ex) {
661
			return null;
662
		}
663
	}
664
665
	/**
666
	 * @param string $principalUri
667
	 * @return IUser|null
668
	 */
669
	private function getUserFromPrincipalURI(string $principalUri):?IUser {
670
		if (!$principalUri) {
671
			return null;
672
		}
673
674
		if (stripos($principalUri, 'principals/users/') !== 0) {
675
			return null;
676
		}
677
678
		$userId = substr($principalUri, 17);
679
		return $this->userManager->get($userId);
680
	}
681
682
	/**
683
	 * @param VObject\Component\VCalendar $vcalendar
684
	 * @return VObject\Component\VEvent[]
685
	 */
686
	private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
687
		$vevents = [];
688
689
		foreach($vcalendar->children() as $child) {
690
			if (!($child instanceof VObject\Component)) {
691
				continue;
692
			}
693
694
			if ($child->name !== 'VEVENT') {
695
				continue;
696
			}
697
698
			$vevents[] = $child;
699
		}
700
701
		return $vevents;
702
	}
703
704
	/**
705
	 * @param array $vevents
706
	 * @return VObject\Component\VEvent[]
707
	 */
708
	private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array {
709
		return array_values(array_filter($vevents, function(VEvent $vevent) {
710
			return $vevent->{'RECURRENCE-ID'} !== null;
711
		}));
712
	}
713
714
	/**
715
	 * @param array $vevents
716
	 * @return VEvent|null
717
	 */
718
	private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
719
		$elements = array_values(array_filter($vevents, function(VEvent $vevent) {
720
			return $vevent->{'RECURRENCE-ID'} === null;
721
		}));
722
723
		if (count($elements) === 0) {
724
			return null;
725
		}
726
		if (count($elements) > 1) {
727
			throw new \TypeError('Multiple master objects');
728
		}
729
730
		return $elements[0];
731
	}
732
733
	/**
734
	 * @param VAlarm $valarm
735
	 * @return bool
736
	 */
737
	private function isAlarmRelative(VAlarm $valarm):bool {
738
		$trigger = $valarm->TRIGGER;
739
		return $trigger instanceof VObject\Property\ICalendar\Duration;
740
	}
741
742
	/**
743
	 * @param VEvent $vevent
744
	 * @return int
745
	 */
746
	private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
747
		if (isset($vevent->{'RECURRENCE-ID'})) {
748
			return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp();
749
		}
750
751
		return $vevent->DTSTART->getDateTime()->getTimestamp();
0 ignored issues
show
Bug introduced by
The method getDateTime() does not exist on Sabre\VObject\Property. It seems like you code against a sub-type of Sabre\VObject\Property such as Sabre\VObject\Property\ICalendar\DateTime or Sabre\VObject\Property\VCard\DateAndOrTime. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

751
		return $vevent->DTSTART->/** @scrutinizer ignore-call */ getDateTime()->getTimestamp();
Loading history...
752
	}
753
754
	/**
755
	 * @param VEvent $vevent
756
	 * @return bool
757
	 */
758
	private function isRecurring(VEvent $vevent):bool {
759
		return isset($vevent->RRULE) || isset($vevent->RDATE);
760
	}
761
}
762