Passed
Push — master ( 6725ad...8514a2 )
by Roeland
10:48 queued 14s
created

ReminderService::deleteOrProcessNext()   C

Complexity

Conditions 12
Paths 9

Size

Total Lines 70
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 39
c 0
b 0
f 0
nc 9
nop 2
dl 0
loc 70
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
			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());
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

360
			$clonedNotificationDate->add($valarm->DURATION->/** @scrutinizer ignore-call */ getDateInterval());
Loading history...
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,
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...
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();
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

759
		return $vevent->DTSTART->/** @scrutinizer ignore-call */ getDateTime()->getTimestamp();
Loading history...
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
}
770