diff --git a/.patches/sabre-vobject-iTipBroker-replies.patch b/.patches/sabre-vobject-iTipBroker-replies.patch new file mode 100644 index 000000000..b335b84eb --- /dev/null +++ b/.patches/sabre-vobject-iTipBroker-replies.patch @@ -0,0 +1,51 @@ +From 49d53f187089ec9a67917e564a87c6dea89ea839 Mon Sep 17 00:00:00 2001 +From: SebastianKrupinski +Date: Tue, 30 Dec 2025 13:48:29 -0500 +Subject: [PATCH] fix: send participation reply on fresh event + +Signed-off-by: SebastianKrupinski +--- + lib/ITip/Broker.php | 25 ++++++--- + .../VObject/ITip/BrokerAttendeeReplyTest.php | 52 +++++++++++++++++++ + 2 files changed, 71 insertions(+), 6 deletions(-) + +diff --git a/lib/ITip/Broker.php b/lib/ITip/Broker.php +index 76ee0c71..e621c6e2 100644 +--- a/lib/ITip/Broker.php ++++ b/lib/ITip/Broker.php +@@ -246,16 +246,29 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null): array + $baseCalendar = $oldCalendar; + } + ++ // Check if the user is the organizer + if (in_array($eventInfo['organizer'], $userHref)) { + return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); +- } elseif ($oldCalendar) { +- // We need to figure out if the user is an attendee, but we're only +- // doing so if there's an oldCalendar, because we only want to +- // process updates, not creation of new events. +- foreach ($eventInfo['attendees'] as $attendee) { +- if (in_array($attendee['href'], $userHref)) { ++ } ++ ++ // Check if the user is an attendee ++ foreach ($eventInfo['attendees'] as $attendee) { ++ if (in_array($attendee['href'], $userHref)) { ++ // If this is a event update, we always generate a reply ++ if ($oldCalendar) { + return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); + } ++ ++ // If this is a new event, we only generate a reply if the participation status is set ++ foreach ($attendee['instances'] as $instance) { ++ if (isset($instance['partstat']) && 'NEEDS-ACTION' !== $instance['partstat']) { ++ // Attendee has responded (ACCEPTED/DECLINED/TENTATIVE) - generate REPLY ++ return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); ++ } ++ } ++ ++ // User is attendee but no response to process ++ break; + } + } + \ No newline at end of file diff --git a/composer.patches.json b/composer.patches.json index 73fb07c30..7a0dbe4cc 100644 --- a/composer.patches.json +++ b/composer.patches.json @@ -8,7 +8,8 @@ "Check for instanceof INode instead of Node": ".patches/check-for-instanceof-INode-instead-of-Node-1595.patch" }, "sabre/vobject": { - "fix use RDATE in time range check and use all instances": ".patches/sabre-vobject-rdate.patch" + "fix use RDATE in time range check and use all instances": ".patches/sabre-vobject-rdate.patch", + "fix send participation reply on fresh event": ".patches/sabre-vobject-iTipBroker-replies.patch" } } } diff --git a/sabre/vobject/PATCHES.txt b/sabre/vobject/PATCHES.txt index 9cc99e5bd..358e87a0c 100644 --- a/sabre/vobject/PATCHES.txt +++ b/sabre/vobject/PATCHES.txt @@ -5,3 +5,7 @@ fix use RDATE in time range check and use all instances Source: .patches/sabre-vobject-rdate.patch +fix send participation reply on fresh event +Source: .patches/sabre-vobject-iTipBroker-replies.patch + + diff --git a/sabre/vobject/lib/ITip/Broker.php b/sabre/vobject/lib/ITip/Broker.php index 9d68fc4c6..80be2a852 100644 --- a/sabre/vobject/lib/ITip/Broker.php +++ b/sabre/vobject/lib/ITip/Broker.php @@ -240,16 +240,29 @@ public function parseEvent($calendar, $userHref, $oldCalendar = null) $baseCalendar = $oldCalendar; } + // Check if the user is the organizer if (in_array($eventInfo['organizer'], $userHref)) { return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); - } elseif ($oldCalendar) { - // We need to figure out if the user is an attendee, but we're only - // doing so if there's an oldCalendar, because we only want to - // process updates, not creation of new events. - foreach ($eventInfo['attendees'] as $attendee) { - if (in_array($attendee['href'], $userHref)) { + } + + // Check if the user is an attendee + foreach ($eventInfo['attendees'] as $attendee) { + if (in_array($attendee['href'], $userHref)) { + // If this is a event update, we always generate a reply + if ($oldCalendar) { return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); } + + // If this is a new event, we only generate a reply if the participation status is set + foreach ($attendee['instances'] as $instance) { + if (isset($instance['partstat']) && 'NEEDS-ACTION' !== $instance['partstat']) { + // Attendee has responded (ACCEPTED/DECLINED/TENTATIVE) - generate REPLY + return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); + } + } + + // User is attendee but no response to process + break; } }