Skip to content
Draft
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.

45 changes: 31 additions & 14 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 @@ -149,26 +147,45 @@ public function schedule(Message $iTipMessage) {
$newEvents = $iTipMessage->message;
$oldEvents = $this->getVCalendar();

$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);
$modifiedInstances = $this->imipService->findModifiedInstances($newEvents, $oldEvents);

// No changed events after all - this shouldn't happen if there is significant change yet here we are
// The scheduling status is debatable
if (empty($vEvent)) {
if (empty($modifiedInstances)) {
$this->logger->warning('iTip message said the change was significant but comparison did not detect any updated 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
// we (should) have one changed 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 changed
// instances (e.g. master + 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];

if (count($modifiedInstances) > 1) {
$this->logger->debug('iTip message contains multiple changed instances; only the master/primary instance is reflected in the invitation email', [
'uid' => $iTipMessage->uid,
'instanceCount' => count($modifiedInstances),
]);
}

/** @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
71 changes: 71 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,18 @@ class IMipService {
'meeting_location' => 'LOCATION'
];

/**
* Properties that, besides RECURRENCE-ID, decide whether a matched
* instance pair counts as "changed".
*
* @var string[]
*/
private const array INSTANCE_DIFF_PROPERTIES = [
'RRULE',
'SEQUENCE',
'LAST-MODIFIED',
];

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

/**
* Compares $new against the previously stored $old calendar and returns
* every VEVENT that changed, paired with its previous version.
*
* Instances are matched by UID and RECURRENCE-ID (the empty string
* standing in for the master event), never by array position, so a
* message carrying several instances (master plus overrides) pairs each
* one with its own prior version rather than an arbitrary old entry.
* A new instance with no matching old one is paired with null.
*
* @return list<array{new: VEvent, old: ?VEvent}>
*/
public function findModifiedInstances(VCalendar $new, ?VCalendar $old): array {
$newEvents = array_values(array_filter(
$new->getComponents(),
static fn ($component) => $component instanceof VEvent,
));

if ($old === null) {
return array_map(
static fn (VEvent $event) => ['new' => $event, 'old' => null],
$newEvents,
);
}

$oldEventsByInstance = [];
foreach ($old->getComponents() as $component) {
if ($component instanceof VEvent) {
$oldEventsByInstance[$this->instanceKey($component)] = $component;
}
}

$modified = [];
foreach ($newEvents as $newEvent) {
$oldEvent = $oldEventsByInstance[$this->instanceKey($newEvent)] ?? null;

if ($oldEvent !== null && $this->isInstanceUnchanged($newEvent, $oldEvent)) {
continue;
}

$modified[] = ['new' => $newEvent, 'old' => $oldEvent];
}

return $modified;
}

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

private function isInstanceUnchanged(VEvent $newEvent, VEvent $oldEvent): bool {
foreach (self::INSTANCE_DIFF_PROPERTIES as $property) {
if (self::readPropertyWithDefault($newEvent, $property, '') !== self::readPropertyWithDefault($oldEvent, $property, '')) {
return false;
}
}
return true;
}

/**
* @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