Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 67 additions & 86 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,10 @@
use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VTimeZone;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\ParseException;
use Sabre\VObject\Property;
use Sabre\VObject\Reader;
use Sabre\VObject\Recur\EventIterator;
use Sabre\VObject\Recur\MaxInstancesExceededException;
use Sabre\VObject\Recur\NoInstancesException;
use function array_column;
Expand Down Expand Up @@ -3417,99 +3415,82 @@
* @return array
*/
public function getDenormalizedData(string $calendarData): array {

$derived = [
'etag' => md5($calendarData),
'size' => strlen($calendarData),
];
// validate data and extract base component
/** @var VCalendar $vObject */
$vObject = Reader::read($calendarData);
$vEvents = [];
$componentType = null;
$component = null;
$firstOccurrence = null;
$lastOccurrence = null;
$uid = null;
$classification = self::CLASSIFICATION_PUBLIC;
$hasDTSTART = false;
foreach ($vObject->getComponents() as $component) {
if ($component->name !== 'VTIMEZONE') {
// Finding all VEVENTs, and track them
if ($component->name === 'VEVENT') {
$vEvents[] = $component;
if ($component->DTSTART) {
$hasDTSTART = true;
$components = $vObject->getBaseComponents();
if (count($components) !== 1) {
throw new BadRequest('A valid calendar object must contain at least one VJOURNAL, VEVENT, or VTODO component type');
}
$component = $components[0];
// extract basic information
$derived['componentType'] = $component->name;
$derived['uid'] = $component->UID ? $component->UID->getValue() : null;

Check failure on line 3433 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3433:21: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$UID is not defined (see https://psalm.dev/218)
$derived['classification'] = $component->CLASS ? match ($component->CLASS->getValue()) {

Check failure on line 3434 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3434:32: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$CLASS is not defined (see https://psalm.dev/218)
'PUBLIC' => self::CLASSIFICATION_PUBLIC,
'CONFIDENTIAL' => self::CLASSIFICATION_CONFIDENTIAL,
default => self::CLASSIFICATION_PRIVATE,
} : self::CLASSIFICATION_PUBLIC;
// extract start and end dates
// VTODO components can have no start date
/** @var */
$startDate = $component->DTSTART instanceof \Sabre\VObject\Property\ICalendar\DateTime ? $component->DTSTART->getDateTime() : null;

Check failure on line 3442 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3442:16: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$DTSTART is not defined (see https://psalm.dev/218)
$endDate = $startDate ? clone $startDate : null;
if ($startDate) {
// Recurring
if ($component->RRULE || $component->RDATE) {

Check failure on line 3446 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3446:29: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$RDATE is not defined (see https://psalm.dev/218)

Check failure on line 3446 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3446:8: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$RRULE is not defined (see https://psalm.dev/218)
// RDATE can have both instances and multiple values
// RDATE;TZID=America/Toronto:20250701T000000,20260701T000000
// RDATE;TZID=America/Toronto:20270701T000000
if ($component->RDATE) {

Check failure on line 3450 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3450:9: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$RDATE is not defined (see https://psalm.dev/218)
foreach ($component->RDATE as $instance) {
foreach ($instance->getDateTimes() as $entry) {
if ($entry > $endDate) {
$endDate = $entry;
}
}
}
}
// Track first component type and uid
if ($uid === null) {
$componentType = $component->name;
$uid = (string)$component->UID;
}
}
}
if (!$componentType) {
throw new BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
}

if ($hasDTSTART) {
$component = $vEvents[0];

// Finding the last occurrence is a bit harder
if (!isset($component->RRULE) && count($vEvents) === 1) {
$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
if (isset($component->DTEND)) {
$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
} elseif (isset($component->DURATION)) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
$lastOccurrence = $endDate->getTimeStamp();
} elseif (!$component->DTSTART->hasTime()) {
$endDate = clone $component->DTSTART->getDateTime();
$endDate->modify('+1 day');
$lastOccurrence = $endDate->getTimeStamp();
} else {
$lastOccurrence = $firstOccurrence;
// RRULE can be infinate or limited by a UNTIL or COUNT
if ($component->RRULE) {
try {
$rule = new EventReaderRRule($component->RRULE->getValue(), $startDate);
$endDate = $rule->isInfinite() ? new DateTime(self::MAX_DATE) : $rule->concludes();
} catch (NoInstancesException $e) {
$this->logger->debug('Caught no instance exception for calendar data. This usually indicates invalid calendar data.', [
'app' => 'dav',
'exception' => $e,
]);
throw new Forbidden($e->getMessage());
}
}
// Singleton
} else {
try {
$it = new EventIterator($vEvents);
} catch (NoInstancesException $e) {
$this->logger->debug('Caught no instance exception for calendar data. This usually indicates invalid calendar data.', [
'app' => 'dav',
'exception' => $e,
]);
throw new Forbidden($e->getMessage());
}
$maxDate = new DateTime(self::MAX_DATE);
$firstOccurrence = $it->getDtStart()->getTimestamp();
if ($it->isInfinite()) {
$lastOccurrence = $maxDate->getTimestamp();
} else {
$end = $it->getDtEnd();
while ($it->valid() && $end < $maxDate) {
$end = $it->getDtEnd();
$it->next();
}
$lastOccurrence = $end->getTimestamp();
if ($component->DTEND instanceof \Sabre\VObject\Property\ICalendar\DateTime) {

Check failure on line 3474 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3474:9: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$DTEND is not defined (see https://psalm.dev/218)
// VEVENT component types
$endDate = $component->DTEND->getDateTime();
} elseif ($component->DURATION instanceof \Sabre\VObject\Property\ICalendar\Duration) {

Check failure on line 3477 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3477:15: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$DURATION is not defined (see https://psalm.dev/218)
// VEVENT / VTODO component types
$endDate = $startDate->add($component->DURATION->getDateInterval());
} elseif ($component->DUE instanceof \Sabre\VObject\Property\ICalendar\DateTime) {

Check failure on line 3480 in apps/dav/lib/CalDAV/CalDavBackend.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMagicPropertyFetch

apps/dav/lib/CalDAV/CalDavBackend.php:3480:15: UndefinedMagicPropertyFetch: Magic instance property Sabre\VObject\Component::$DUE is not defined (see https://psalm.dev/218)
// VTODO component types
$endDate = $component->DUE->getDateTime();
} elseif ($component->name === 'VEVENT' && !$component->DTSTART->hasTime()) {
// VEVENT component type without time is automatically one day
$endDate = (clone $startDate)->modify('+1 day');
}
}
}
// convert dates to timestamp and prevent negative values
$derived['firstOccurence'] = $startDate ? max(0, $startDate->getTimestamp()) : 0;
$derived['lastOccurence'] = $endDate ? max(0, $endDate->getTimestamp()) : 0;

if ($component->CLASS) {
$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
switch ($component->CLASS->getValue()) {
case 'PUBLIC':
$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
break;
case 'CONFIDENTIAL':
$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
break;
}
}
return [
'etag' => md5($calendarData),
'size' => strlen($calendarData),
'componentType' => $componentType,
'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
'lastOccurence' => is_null($lastOccurrence) ? null : max(0, $lastOccurrence),
'uid' => $uid,
'classification' => $classification
];
return $derived;
}

/**
Expand Down
Loading