Skip to content

Commit cb17057

Browse files
committed
Make rooms / resources automatically reply to invites
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
1 parent d013ff6 commit cb17057

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

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

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@
2626

2727
use OCA\DAV\CalDAV\CalDavBackend;
2828
use OCA\DAV\CalDAV\CalendarHome;
29+
use Sabre\CalDAV\ICalendar;
2930
use Sabre\DAV\INode;
3031
use Sabre\DAV\IProperties;
3132
use Sabre\DAV\PropFind;
3233
use Sabre\DAV\Server;
3334
use Sabre\DAV\Xml\Property\LocalHref;
3435
use Sabre\DAVACL\IPrincipal;
36+
use Sabre\VObject\Component\VEvent;
37+
use Sabre\VObject\ITip;
38+
use Sabre\VObject\Parameter;
39+
use Sabre\VObject\Property;
3540

3641
class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
3742

@@ -91,6 +96,73 @@ protected function getAddressesForPrincipal($principal) {
9196
return $result;
9297
}
9398

99+
/**
100+
* @inheritDoc
101+
*/
102+
public function scheduleLocalDelivery(ITip\Message $iTipMessage):void {
103+
parent::scheduleLocalDelivery($iTipMessage);
104+
105+
// We only care when the message was successfully delivered locally
106+
if ($iTipMessage->scheduleStatus !== '1.2;Message delivered locally') {
107+
return;
108+
}
109+
110+
// We only care about request. reply and cancel are properly handled
111+
// by parent::scheduleLocalDelivery already
112+
if (strcasecmp($iTipMessage->method, 'REQUEST') !== 0) {
113+
return;
114+
}
115+
116+
// If parent::scheduleLocalDelivery set scheduleStatus to 1.2,
117+
// it means that it was successfully delivered locally.
118+
// Meaning that the ACL plugin is loaded and that a principial
119+
// exists for the given recipient id, no need to double check
120+
/** @var \Sabre\DAVACL\Plugin $aclPlugin */
121+
$aclPlugin = $this->server->getPlugin('acl');
122+
$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
123+
$calendarUserType = $this->getCalendarUserTypeForPrincipal($principalUri);
124+
if (strcasecmp($calendarUserType, 'ROOM') !== 0 && strcasecmp($calendarUserType, 'RESOURCE') !== 0) {
125+
return;
126+
}
127+
128+
$attendee = $this->getCurrentAttendee($iTipMessage);
129+
if (!$attendee) {
130+
return;
131+
}
132+
133+
// We only respond when a response was actually requested
134+
$rsvp = $this->getAttendeeRSVP($attendee);
135+
if (!$rsvp) {
136+
return;
137+
}
138+
139+
// We only respond when there was no response so far
140+
$partStat = $this->getAttendeePartstat($attendee);
141+
if (strcasecmp($partStat, 'NEEDS-ACTION') !== 0) {
142+
return;
143+
}
144+
145+
// $homeSet = $this->getCalendarHome
146+
// $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
147+
148+
$homeSet = '';
149+
foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
150+
if (!$node instanceof ICalendar) {
151+
continue;
152+
}
153+
}
154+
155+
156+
157+
158+
// TODO:
159+
160+
161+
// TODO: figure out whether ROOM / RESOURCE is already busy
162+
163+
164+
}
165+
94166
/**
95167
* Always use the personal calendar as target for scheduled events
96168
*
@@ -140,4 +212,72 @@ function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) {
140212
});
141213
}
142214
}
215+
216+
/**
217+
* Returns a list of addresses that are associated with a principal.
218+
*
219+
* @param string $principal
220+
* @return string?
221+
*/
222+
protected function getCalendarUserTypeForPrincipal($principal):?string {
223+
$calendarUserType = '{' . self::NS_CALDAV . '}calendar-user-type';
224+
$properties = $this->server->getProperties(
225+
$principal,
226+
[$calendarUserType]
227+
);
228+
229+
// If we can't find this information, we'll stop processing
230+
if (!isset($properties[$calendarUserType])) {
231+
return null;
232+
}
233+
234+
return $properties[$calendarUserType];
235+
}
236+
237+
/**
238+
* @param ITip\Message $iTipMessage
239+
* @return null|Property
240+
*/
241+
private function getCurrentAttendee(ITip\Message $iTipMessage) {
242+
/** @var VEvent $vevent */
243+
$vevent = $iTipMessage->message->VEVENT;
244+
$attendees = $vevent->select('ATTENDEE');
245+
foreach ($attendees as $attendee) {
246+
/** @var Property $attendee */
247+
if (strcasecmp($attendee->getValue(), $iTipMessage->recipient) === 0) {
248+
return $attendee;
249+
}
250+
}
251+
return null;
252+
}
253+
254+
/**
255+
* @param Property|null $attendee
256+
* @return bool
257+
*/
258+
private function getAttendeeRSVP(Property $attendee = null):bool {
259+
if ($attendee !== null) {
260+
$rsvp = $attendee->offsetGet('RSVP');
261+
if (($rsvp instanceof Parameter) && (strcasecmp($rsvp->getValue(), 'TRUE') === 0)) {
262+
return true;
263+
}
264+
}
265+
// RFC 5545 3.2.17: default RSVP is false
266+
return false;
267+
}
268+
269+
/**
270+
* @param Property|null $attendee
271+
* @return string
272+
*/
273+
private function getAttendeePartstat(Property $attendee = null):string {
274+
if ($attendee !== null) {
275+
$partStat = $attendee->offsetGet('PARTSTAT');
276+
if ($partStat instanceof Parameter) {
277+
return $partStat->getValue();
278+
}
279+
}
280+
// RFC 5545 3.2.12: default PARTSTAT to NEEDS-ACTION
281+
return 'NEEDS-ACTION';
282+
}
143283
}

0 commit comments

Comments
 (0)