Skip to content

Commit 636d24a

Browse files
fix: override iTip Broker to fix several issues
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent defe57e commit 636d24a

File tree

6 files changed

+424
-0
lines changed

6 files changed

+424
-0
lines changed

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
'OCA\\DAV\\CalDAV\\Sharing\\Service' => $baseDir . '/../lib/CalDAV/Sharing/Service.php',
109109
'OCA\\DAV\\CalDAV\\Status\\StatusService' => $baseDir . '/../lib/CalDAV/Status/StatusService.php',
110110
'OCA\\DAV\\CalDAV\\TimezoneService' => $baseDir . '/../lib/CalDAV/TimezoneService.php',
111+
'OCA\\DAV\\CalDAV\\TipBroker' => $baseDir . '/../lib/CalDAV/TipBroker.php',
111112
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',
112113
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObjectsCollection' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php',
113114
'OCA\\DAV\\CalDAV\\Trashbin\\Plugin' => $baseDir . '/../lib/CalDAV/Trashbin/Plugin.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class ComposerStaticInitDAV
123123
'OCA\\DAV\\CalDAV\\Sharing\\Service' => __DIR__ . '/..' . '/../lib/CalDAV/Sharing/Service.php',
124124
'OCA\\DAV\\CalDAV\\Status\\StatusService' => __DIR__ . '/..' . '/../lib/CalDAV/Status/StatusService.php',
125125
'OCA\\DAV\\CalDAV\\TimezoneService' => __DIR__ . '/..' . '/../lib/CalDAV/TimezoneService.php',
126+
'OCA\\DAV\\CalDAV\\TipBroker' => __DIR__ . '/..' . '/../lib/CalDAV/TipBroker.php',
126127
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',
127128
'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObjectsCollection' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObjectsCollection.php',
128129
'OCA\\DAV\\CalDAV\\Trashbin\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/Plugin.php',

apps/dav/lib/CalDAV/Schedule/Plugin.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use OCA\DAV\CalDAV\Calendar;
3535
use OCA\DAV\CalDAV\CalendarHome;
3636
use OCA\DAV\CalDAV\DefaultCalendarValidator;
37+
use OCA\DAV\CalDAV\TipBroker;
3738
use OCP\IConfig;
3839
use Psr\Log\LoggerInterface;
3940
use Sabre\CalDAV\ICalendar;
@@ -108,6 +109,13 @@ public function initialize(Server $server) {
108109
);
109110
}
110111

112+
/**
113+
* Returns an instance of the iTip\Broker.
114+
*/
115+
protected function createITipBroker(): TipBroker {
116+
return new TipBroker();
117+
}
118+
111119
/**
112120
* Allow manual setting of the object change URL
113121
* to support public write

apps/dav/lib/CalDAV/TipBroker.php

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\DAV\CalDAV;
11+
12+
use Sabre\VObject\Component\VCalendar;
13+
use Sabre\VObject\ITip\Broker;
14+
use Sabre\VObject\ITip\Message;
15+
16+
class TipBroker extends Broker {
17+
18+
public $significantChangeProperties = [
19+
'DTSTART',
20+
'DTEND',
21+
'DURATION',
22+
'DUE',
23+
'RRULE',
24+
'RDATE',
25+
'EXDATE',
26+
'STATUS',
27+
'SUMMARY',
28+
'DESCRIPTION',
29+
'LOCATION',
30+
31+
];
32+
33+
/**
34+
* This method is used in cases where an event got updated, and we
35+
* potentially need to send emails to attendees to let them know of updates
36+
* in the events.
37+
*
38+
* We will detect which attendees got added, which got removed and create
39+
* specific messages for these situations.
40+
*
41+
* @return array
42+
*/
43+
protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {
44+
// Merging attendee lists.
45+
$attendees = [];
46+
foreach ($oldEventInfo['attendees'] as $attendee) {
47+
$attendees[$attendee['href']] = [
48+
'href' => $attendee['href'],
49+
'oldInstances' => $attendee['instances'],
50+
'newInstances' => [],
51+
'name' => $attendee['name'],
52+
'forceSend' => null,
53+
];
54+
}
55+
foreach ($eventInfo['attendees'] as $attendee) {
56+
if (isset($attendees[$attendee['href']])) {
57+
$attendees[$attendee['href']]['name'] = $attendee['name'];
58+
$attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
59+
$attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
60+
} else {
61+
$attendees[$attendee['href']] = [
62+
'href' => $attendee['href'],
63+
'oldInstances' => [],
64+
'newInstances' => $attendee['instances'],
65+
'name' => $attendee['name'],
66+
'forceSend' => $attendee['forceSend'],
67+
];
68+
}
69+
}
70+
71+
$messages = [];
72+
73+
foreach ($attendees as $attendee) {
74+
// An organizer can also be an attendee. We should not generate any
75+
// messages for those.
76+
if ($attendee['href'] === $eventInfo['organizer']) {
77+
continue;
78+
}
79+
80+
$message = new Message();
81+
$message->uid = $eventInfo['uid'];
82+
$message->component = 'VEVENT';
83+
$message->sequence = $eventInfo['sequence'];
84+
$message->sender = $eventInfo['organizer'];
85+
$message->senderName = $eventInfo['organizerName'];
86+
$message->recipient = $attendee['href'];
87+
$message->recipientName = $attendee['name'];
88+
89+
// Creating the new iCalendar body.
90+
$icalMsg = new VCalendar();
91+
92+
foreach ($calendar->select('VTIMEZONE') as $timezone) {
93+
$icalMsg->add(clone $timezone);
94+
}
95+
// If there are no instances the attendee is a part of, it means
96+
// the attendee was removed and we need to send them a CANCEL message.
97+
// Also If the meeting STATUS property was changed to CANCELLED
98+
// we need to send the attendee a CANCEL message.
99+
if (!$attendee['newInstances'] || $eventInfo['status'] === 'CANCELLED') {
100+
101+
$message->method = $icalMsg->METHOD = 'CANCEL';
102+
$message->significantChange = true;
103+
// clone base event
104+
$event = clone $eventInfo['instances']['master'];
105+
// alter some properties
106+
unset($event->ATTENDEE);
107+
$event->add('ATTENDEE', $attendee['href'], ['CN' => $attendee['name'],]);
108+
$event->DTSTAMP = gmdate('Ymd\\THis\\Z');
109+
$event->SEQUENCE = $message->sequence;
110+
$icalMsg->add($event);
111+
112+
} else {
113+
// The attendee gets the updated event body
114+
$message->method = $icalMsg->METHOD = 'REQUEST';
115+
116+
// We need to find out that this change is significant. If it's
117+
// not, systems may opt to not send messages.
118+
//
119+
// We do this based on the 'significantChangeHash' which is
120+
// some value that changes if there's a certain set of
121+
// properties changed in the event, or simply if there's a
122+
// difference in instances that the attendee is invited to.
123+
124+
$oldAttendeeInstances = array_keys($attendee['oldInstances']);
125+
$newAttendeeInstances = array_keys($attendee['newInstances']);
126+
127+
$message->significantChange =
128+
$attendee['forceSend'] === 'REQUEST' ||
129+
count($oldAttendeeInstances) !== count($newAttendeeInstances) ||
130+
count(array_diff($oldAttendeeInstances, $newAttendeeInstances)) > 0 ||
131+
$oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
132+
133+
foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
134+
$currentEvent = clone $eventInfo['instances'][$instanceId];
135+
if ($instanceId === 'master') {
136+
// We need to find a list of events that the attendee
137+
// is not a part of to add to the list of exceptions.
138+
$exceptions = [];
139+
foreach ($eventInfo['instances'] as $instanceId => $vevent) {
140+
if (!isset($attendee['newInstances'][$instanceId])) {
141+
$exceptions[] = $instanceId;
142+
}
143+
}
144+
145+
// If there were exceptions, we need to add it to an
146+
// existing EXDATE property, if it exists.
147+
if ($exceptions) {
148+
if (isset($currentEvent->EXDATE)) {
149+
$currentEvent->EXDATE->setParts(array_merge(
150+
$currentEvent->EXDATE->getParts(),
151+
$exceptions
152+
));
153+
} else {
154+
$currentEvent->EXDATE = $exceptions;
155+
}
156+
}
157+
158+
// Cleaning up any scheduling information that
159+
// shouldn't be sent along.
160+
unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
161+
unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
162+
163+
foreach ($currentEvent->ATTENDEE as $attendee) {
164+
unset($attendee['SCHEDULE-FORCE-SEND']);
165+
unset($attendee['SCHEDULE-STATUS']);
166+
167+
// We're adding PARTSTAT=NEEDS-ACTION to ensure that
168+
// iOS shows an "Inbox Item"
169+
if (!isset($attendee['PARTSTAT'])) {
170+
$attendee['PARTSTAT'] = 'NEEDS-ACTION';
171+
}
172+
}
173+
}
174+
175+
$currentEvent->DTSTAMP = gmdate('Ymd\\THis\\Z');
176+
$icalMsg->add($currentEvent);
177+
}
178+
}
179+
180+
$message->message = $icalMsg;
181+
$messages[] = $message;
182+
}
183+
184+
return $messages;
185+
}
186+
187+
}

0 commit comments

Comments
 (0)