Skip to content
Open
Show file tree
Hide file tree
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
1 change: 0 additions & 1 deletion apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\DefaultCalendarValidator' => $baseDir . '/../lib/CalDAV/DefaultCalendarValidator.php',
'OCA\\DAV\\CalDAV\\EmbeddedCalDavServer' => $baseDir . '/../lib/CalDAV/EmbeddedCalDavServer.php',
'OCA\\DAV\\CalDAV\\EventComparisonService' => $baseDir . '/../lib/CalDAV/EventComparisonService.php',
'OCA\\DAV\\CalDAV\\EventReader' => $baseDir . '/../lib/CalDAV/EventReader.php',
'OCA\\DAV\\CalDAV\\EventReaderRDate' => $baseDir . '/../lib/CalDAV/EventReaderRDate.php',
'OCA\\DAV\\CalDAV\\EventReaderRRule' => $baseDir . '/../lib/CalDAV/EventReaderRRule.php',
Expand Down
1 change: 0 additions & 1 deletion apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\DefaultCalendarValidator' => __DIR__ . '/..' . '/../lib/CalDAV/DefaultCalendarValidator.php',
'OCA\\DAV\\CalDAV\\EmbeddedCalDavServer' => __DIR__ . '/..' . '/../lib/CalDAV/EmbeddedCalDavServer.php',
'OCA\\DAV\\CalDAV\\EventComparisonService' => __DIR__ . '/..' . '/../lib/CalDAV/EventComparisonService.php',
'OCA\\DAV\\CalDAV\\EventReader' => __DIR__ . '/..' . '/../lib/CalDAV/EventReader.php',
'OCA\\DAV\\CalDAV\\EventReaderRDate' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRDate.php',
'OCA\\DAV\\CalDAV\\EventReaderRRule' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRRule.php',
Expand Down
100 changes: 0 additions & 100 deletions apps/dav/lib/CalDAV/EventComparisonService.php

This file was deleted.

92 changes: 70 additions & 22 deletions apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace OCA\DAV\CalDAV\Schedule;

use OCA\DAV\CalDAV\CalendarObject;
use OCA\DAV\CalDAV\EventComparisonService;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Defaults;
Expand Down Expand Up @@ -65,7 +64,6 @@ public function __construct(
private Defaults $defaults,
private IUserSession $userSession,
private IMipService $imipService,
private EventComparisonService $eventComparisonService,
private IMailManager $mailManager,
private IEmailValidator $emailValidator,
private IAccountManager $accountManager,
Expand Down Expand Up @@ -146,29 +144,82 @@ public function schedule(Message $iTipMessage) {

$recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;

$newEvents = $iTipMessage->message;
$oldEvents = $this->getVCalendar();
$newObjects = $iTipMessage->message;
$oldObjects = $this->getVCalendar();

$method = match (strtolower($iTipMessage->method)) {
'reply' => self::METHOD_REPLY,
'cancel' => self::METHOD_CANCEL,
default => self::METHOD_REQUEST,
};

// For REQUEST method, we need to determine which instances have changed by comparing new and old events
if ($method === self::METHOD_REQUEST) {
$newEvents = $this->imipService->eventInstances($newObjects);
$oldEventsByInstance = [];
if ($oldObjects !== null) {
foreach ($this->imipService->eventInstances($oldObjects) as $oldEvent) {
$oldEventsByInstance[$this->imipService->instanceKey($oldEvent)] = $oldEvent;
}
}

$modified = $this->eventComparisonService->findModified($newEvents, $oldEvents);
/** @var VEvent $vEvent */
$vEvent = array_pop($modified['new']);
/** @var VEvent $oldVevent */
$oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
$isModified = isset($oldVevent);
$allInstances = array_map(
fn (VEvent $newEvent) => ['new' => $newEvent, 'old' => $oldEventsByInstance[$this->imipService->instanceKey($newEvent)] ?? null],
$newEvents,
);

// No changed events after all - this shouldn't happen if there is significant change yet here we are
$changedInstances = array_values(array_filter(
$allInstances,
fn (array $pair) => $pair['old'] === null || $this->imipService->diffInstance($pair['new'], $pair['old']) !== [],
));

$modifiedInstances = $changedInstances !== [] ? $changedInstances : $allInstances;
}
// For CANCEL/REPLY only ever consume the new instance, so there's no old instance to diff against or compare
else {
$modifiedInstances = array_map(
static fn (VEvent $event) => ['new' => $event, 'old' => null],
$this->imipService->eventInstances($newObjects),
);
}

// No VEvents in the message at all - this shouldn't happen if there is significant change yet here we are
// The scheduling status is debatable
if (empty($vEvent)) {
$this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents');
if (empty($modifiedInstances)) {
$this->logger->warning('iTip message said the change was significant but the message contained no VEvents');
$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
return;
}

// we (should) have one event component left
// as the ITip\Broker creates one iTip message per change
// and triggers the "schedule" event once per message
// we also might not have an old event as this could be a new
// invitation, or a new recurrence exception
if (count($modifiedInstances) > 1) {
$this->logger->debug('iTip message contains multiple instances; only the primary instance is reflected in the invitation email', [
'uid' => $iTipMessage->uid,
'instanceCount' => count($modifiedInstances),
]);
}

// we (should) have one instance per message, as the ITip\Broker creates
// one iTip message per attendee and triggers the "schedule" event once
// per message; a message can still bundle several instances (e.g.
// primary + overrides edited together), in which case only the primary
// instance is reflected in the email for now
$primaryInstance = null;
foreach ($modifiedInstances as $instance) {
if (!isset($instance['new']->{'RECURRENCE-ID'})) {
$primaryInstance = $instance;
break;
}
}
$primaryInstance ??= $modifiedInstances[0];

/** @var VEvent $vEvent */
$vEvent = $primaryInstance['new'];
/** @var VEvent|null $oldVevent */
$oldVevent = $primaryInstance['old'];
$isModified = $oldVevent !== null;

// we might not have an old event as this could be a new invitation,
// or a new recurrence exception
$attendee = $this->imipService->getCurrentAttendee($iTipMessage);
if ($attendee === null) {
$uid = $vEvent->UID ?? 'no UID found';
Expand Down Expand Up @@ -199,18 +250,15 @@ public function schedule(Message $iTipMessage) {
}

$replyingAttendee = null;
switch (strtolower($iTipMessage->method)) {
switch ($method) {
case self::METHOD_REPLY:
$method = self::METHOD_REPLY;
$data = $this->imipService->buildReplyBodyData($vEvent);
$replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage);
break;
case self::METHOD_CANCEL:
$method = self::METHOD_CANCEL;
$data = $this->imipService->buildCancelledBodyData($vEvent);
break;
default:
$method = self::METHOD_REQUEST;
$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
break;
}
Expand Down
58 changes: 58 additions & 0 deletions apps/dav/lib/CalDAV/Schedule/IMipService.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ class IMipService {
'meeting_location' => 'LOCATION'
];

/**
* Properties compared to build an instance's changed-property delta:
* everything the invitation email can show a diff for
* (SUMMARY/DESCRIPTION/LOCATION/URL via STRING_DIFF, DTSTART/DTEND/DURATION
* for the "when" string), plus SEQUENCE/LAST-MODIFIED and the rest of
* iTip's own significantChangeProperties (RRULE/RDATE/EXDATE/STATUS;
* DUE is VTODO-only and not relevant to VEVENT invites).
*
* @var string[]
*/
private const array INSTANCE_DIFF_PROPERTIES = [
Comment thread
odzhychko marked this conversation as resolved.
'SUMMARY',
'DESCRIPTION',
'LOCATION',
'URL',
'DTSTART',
'DTEND',
'DURATION',
'STATUS',
'RRULE',
'RDATE',
'EXDATE',
'SEQUENCE',
'LAST-MODIFIED',
];

public function __construct(
private URLGenerator $urlGenerator,
private IDBConnection $db,
Expand Down Expand Up @@ -149,6 +175,38 @@ private function linkify(?string $url): ?string {
return sprintf('<a href="%1$s">%1$s</a>', htmlspecialchars($url));
}

public function instanceKey(VEvent $event): string {
return self::readPropertyWithDefault($event, 'UID', '') . '#' . self::readPropertyWithDefault($event, 'RECURRENCE-ID', '');
}

/**
* Every VEVENT component directly in $calendar, in document order.
*
* @return list<VEvent>
*/
public function eventInstances(VCalendar $calendar): array {
return array_values(array_filter(
$calendar->getComponents(),
static fn ($component) => $component instanceof VEvent,
));
}

/**
* Names of the RFC5545 properties that differ between $newEvent and
* $oldEvent.
*
* @return string[]
*/
public function diffInstance(VEvent $newEvent, VEvent $oldEvent): array {
$changed = [];
foreach (self::INSTANCE_DIFF_PROPERTIES as $property) {
if (self::readPropertyWithDefault($newEvent, $property, '') !== self::readPropertyWithDefault($oldEvent, $property, '')) {
$changed[] = $property;
}
}
return $changed;
}

/**
* @param VEvent $vEvent
* @param VEvent|null $oldVEvent
Expand Down
2 changes: 0 additions & 2 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin;
use OCA\DAV\CalDAV\BirthdayService;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\CalDAV\EventComparisonService;
use OCA\DAV\CalDAV\ICSExportPlugin\ICSExportPlugin;
use OCA\DAV\CalDAV\Publishing\PublishPlugin;
use OCA\DAV\CalDAV\Schedule\IMipPlugin;
Expand Down Expand Up @@ -358,7 +357,6 @@ public function __construct(
\OCP\Server::get(Defaults::class),
$userSession,
\OCP\Server::get(IMipService::class),
\OCP\Server::get(EventComparisonService::class),
\OCP\Server::get(\OCP\Mail\Provider\IManager::class),
\OCP\Server::get(IEmailValidator::class),
\OCP\Server::get(IAccountManager::class),
Expand Down
Loading
Loading