Skip to content

Commit

Permalink
fixup! feat(ocp): add calendar api to retrieve availability of attendees
Browse files Browse the repository at this point in the history
  • Loading branch information
st3iny committed Jan 11, 2025
1 parent a08ea34 commit dcb17e3
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 74 deletions.
34 changes: 10 additions & 24 deletions lib/private/Calendar/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,17 @@ public function createEventBuilder(): ICalendarEventBuilder {
public function checkAvailability(
DateTimeInterface $start,
DateTimeInterface $end,
IUser $user,
IUser $organizer,
array $attendees,
): array {
$organizer = $user->getEMailAddress();

$organizerMailto = 'mailto:' . $organizer->getEMailAddress();
$request = new VCalendar();
$request->METHOD = 'REQUEST';
$request->add('VFREEBUSY', [
'DTSTART' => $start,
'DTEND' => $end,
'ORGANIZER' => "mailto:$organizer"
'ORGANIZER' => $organizerMailto,
'ATTENDEE' => $organizerMailto,
]);

$mailtoLen = strlen('mailto:');
Expand All @@ -513,15 +513,15 @@ public function checkAvailability(
$request->VFREEBUSY->add('ATTENDEE', "mailto:$attendee");
}

$userId = $user->getUID();
$organizerUid = $organizer->getUID();
$server = $this->serverFactory->createAttendeeAvailabilityServer();
/** @var CustomPrincipalPlugin $plugin */
$plugin = $server->getPlugin('auth');
$plugin->setCurrentPrincipal("principals/users/$userId");
$plugin->setCurrentPrincipal("principals/users/$organizerUid");

$request = new Request(
'POST',
"/calendars/$userId/outbox/",
"/calendars/$organizerUid/outbox/",
[
'Content-Type' => 'text/calendar',
'Depth' => 0,
Expand Down Expand Up @@ -554,27 +554,13 @@ public function checkAvailability(
$mailtoLen,
);

$vfreebusy = $freeBusyResponseData->VFREEBUSY;
if (!($vfreebusy instanceof VFreeBusy)) {
$vFreeBusy = $freeBusyResponseData->VFREEBUSY;
if (!($vFreeBusy instanceof VFreeBusy)) {
continue;
}

// TODO: actually check values of FREEBUSY properties to find a free slot
$isAvailable = true;
$freeBusyProps = $vfreebusy->FREEBUSY;
if ($freeBusyProps !== null) {
foreach ($freeBusyProps as $prop) {
// BUSY is the default, in case FBTYPE is not present
if (isset($prop['FBTYPE']) && $prop['FBTYPE'] === 'FREE') {
continue;
}

$isAvailable = false;
break;
}
}

$result[] = new AvailabilityResult($attendee, $isAvailable);
$result[] = new AvailabilityResult($attendee, $vFreeBusy->isFree($start, $end));
}

return $result;
Expand Down
6 changes: 3 additions & 3 deletions lib/public/Calendar/IManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ public function createEventBuilder(): ICalendarEventBuilder;
*
* @since 31.0.0
*
* @param IUser $user The organizing user as which to do the availability check.
* @param string[] $attendees Email addresses of attendees to check for (with or without a "mailto:" prefix). Only users on this instance can be checked. The rest will be ignored.
* @param IUser $organizer The organizing user from whose perspective to do the availability check.
* @param string[] $attendees Email addresses of attendees to check for (with or without a "mailto:" prefix). Only users on this instance can be checked. The rest will be silently ignored.
* @return IAvailabilityResult[] Availabilities of all attendees which are also users on this instance. As such, the array might not contain an entry for each given attendee.
*/
public function checkAvailability(
DateTimeInterface $start,
DateTimeInterface $end,
IUser $user,
IUser $organizer,
array $attendees,
): array;
}
11 changes: 6 additions & 5 deletions tests/data/ics/free-busy-request.ics
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ PRODID:-//Sabre//Sabre VObject 4.5.6//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VFREEBUSY
DTSTART:20250109T082136Z
DTEND:20250109T102136Z
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
DTSTART:20250116T060000Z
DTEND:20250117T060000Z
ORGANIZER:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
ATTENDEE:mailto:[email protected]
END:VFREEBUSY
END:VCALENDAR
121 changes: 79 additions & 42 deletions tests/lib/Calendar/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VFreeBusy;
use Sabre\VObject\Document;
use Sabre\VObject\Reader;
use Test\TestCase;
Expand Down Expand Up @@ -994,10 +995,10 @@ private function getVCalendarCancel(): Document {
private function getFreeBusyResponse(): string {
return <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<cal:schedule-response xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/">
<cal:schedule-response xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
<cal:response>
<cal:recipient>
<d:href>mailto:[email protected]</d:href>
<d:href>mailto:[email protected]</d:href>
</cal:recipient>
<cal:request-status>2.0;Success</cal:request-status>
<cal:calendar-data>BEGIN:VCALENDAR
Expand All @@ -1006,20 +1007,21 @@ private function getFreeBusyResponse(): string {
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VFREEBUSY
DTSTART:20250109T082136Z
DTEND:20250109T102136Z
DTSTAMP:20250109T082136Z
FREEBUSY::20250109T084500Z/20250109T090000Z
ATTENDEE:mailto:[email protected]
UID:
ORGANIZER:mailto:[email protected]
DTSTART:20250116T060000Z
DTEND:20250117T060000Z
DTSTAMP:20250111T125634Z
FREEBUSY:20250116T060000Z/20250116T230000Z
FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20250116T230000Z/20250117T060000Z
ATTENDEE:mailto:[email protected]
UID:6099eab3-9bf1-4c7a-809e-4d46957cc372
ORGANIZER;CN=admin:mailto:[email protected]
END:VFREEBUSY
END:VCALENDAR
</cal:calendar-data>
</cal:response>
<cal:response>
<cal:recipient>
<d:href>mailto:[email protected]</d:href>
<d:href>mailto:[email protected]</d:href>
</cal:recipient>
<cal:request-status>2.0;Success</cal:request-status>
<cal:calendar-data>BEGIN:VCALENDAR
Expand All @@ -1028,16 +1030,45 @@ private function getFreeBusyResponse(): string {
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VFREEBUSY
DTSTART:20250109T082136Z
DTEND:20250109T102136Z
DTSTAMP:20250109T082136Z
ATTENDEE:mailto:[email protected]
UID:
ORGANIZER:mailto:[email protected]
DTSTART:20250116T060000Z
DTEND:20250117T060000Z
DTSTAMP:20250111T125634Z
ATTENDEE:mailto:[email protected]
UID:6099eab3-9bf1-4c7a-809e-4d46957cc372
ORGANIZER;CN=admin:mailto:[email protected]
END:VFREEBUSY
END:VCALENDAR
</cal:calendar-data>
</cal:response>
<cal:response>
<cal:recipient>
<d:href>mailto:[email protected]</d:href>
</cal:recipient>
<cal:request-status>2.0;Success</cal:request-status>
<cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 4.5.6//EN
CALSCALE:GREGORIAN
METHOD:REPLY
BEGIN:VFREEBUSY
DTSTART:20250116T060000Z
DTEND:20250117T060000Z
DTSTAMP:20250111T125634Z
FREEBUSY:20250116T060000Z/20250116T230000Z
FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20250116T230000Z/20250117T060000Z
ATTENDEE:mailto:[email protected]
UID:6099eab3-9bf1-4c7a-809e-4d46957cc372
ORGANIZER;CN=admin:mailto:[email protected]
END:VFREEBUSY
END:VCALENDAR
</cal:calendar-data>
</cal:response>
<cal:response>
<cal:recipient>
<d:href>mailto:[email protected]</d:href>
</cal:recipient>
<cal:request-status>3.7;Could not find principal</cal:request-status>
</cal:response>
</cal:schedule-response>
EOF;
}
Expand All @@ -1046,25 +1077,26 @@ public function testCheckAvailability(): void {
$organizer = $this->createMock(IUser::class);
$organizer->expects(self::once())
->method('getUID')
->willReturn('organizer');
->willReturn('admin');
$organizer->expects(self::once())
->method('getEMailAddress')
->willReturn('[email protected]');
->willReturn('[email protected]');

$user1 = $this->createMock(IUser::class);
$user2 = $this->createMock(IUser::class);

$this->userManager->expects(self::exactly(2))
$this->userManager->expects(self::exactly(3))
->method('getByEmail')
->willReturnMap([
['[email protected]', $user1],
['[email protected]', $user2],
['[email protected]', [$user1]],
['[email protected]', [$user2]],
['[email protected]', []],
]);

$authPlugin = $this->createMock(CustomPrincipalPlugin::class);
$authPlugin->expects(self::once())
->method('setCurrentPrincipal')
->with('principals/users/organizer');
->with('principals/users/admin');

$server = $this->createMock(\OCA\DAV\Connector\Sabre\Server::class);
$server->expects(self::once())
Expand All @@ -1080,7 +1112,7 @@ public function testCheckAvailability(): void {
) {
$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
$this->assertEquals('POST', $request->getMethod());
$this->assertEquals('calendars/organizer/outbox', $request->getPath());
$this->assertEquals('calendars/admin/outbox', $request->getPath());
$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
$this->assertEquals('0', $request->getHeader('Depth'));
$this->assertEquals($requestBody, $request->getBodyAsString());
Expand All @@ -1093,15 +1125,17 @@ public function testCheckAvailability(): void {
->method('createAttendeeAvailabilityServer')
->willReturn($server);

$start = new DateTimeImmutable('2025-01-09T08:21:36Z');
$end = new DateTimeImmutable('2025-01-09T10:21:36Z');
$start = new DateTimeImmutable('2025-01-16T06:00:00Z');
$end = new DateTimeImmutable('2025-01-17T06:00:00Z');
$actual = $this->manager->checkAvailability($start, $end, $organizer, [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
]);
$expected = [
new AvailabilityResult('[email protected]', false),
new AvailabilityResult('[email protected]', true),
new AvailabilityResult('[email protected]', false),
new AvailabilityResult('[email protected]', true),
new AvailabilityResult('[email protected]', false),
];
$this->assertEquals($expected, $actual);
}
Expand All @@ -1110,25 +1144,26 @@ public function testCheckAvailabilityWithMailtoPrefix(): void {
$organizer = $this->createMock(IUser::class);
$organizer->expects(self::once())
->method('getUID')
->willReturn('organizer');
->willReturn('admin');
$organizer->expects(self::once())
->method('getEMailAddress')
->willReturn('[email protected]');
->willReturn('[email protected]');

$user1 = $this->createMock(IUser::class);
$user2 = $this->createMock(IUser::class);

$this->userManager->expects(self::exactly(2))
$this->userManager->expects(self::exactly(3))
->method('getByEmail')
->willReturnMap([
['[email protected]', $user1],
['[email protected]', $user2],
['[email protected]', [$user1]],
['[email protected]', [$user2]],
['[email protected]', []],
]);

$authPlugin = $this->createMock(CustomPrincipalPlugin::class);
$authPlugin->expects(self::once())
->method('setCurrentPrincipal')
->with('principals/users/organizer');
->with('principals/users/admin');

$server = $this->createMock(\OCA\DAV\Connector\Sabre\Server::class);
$server->expects(self::once())
Expand All @@ -1144,7 +1179,7 @@ public function testCheckAvailabilityWithMailtoPrefix(): void {
) {
$requestBody = file_get_contents(__DIR__ . '/../../data/ics/free-busy-request.ics');
$this->assertEquals('POST', $request->getMethod());
$this->assertEquals('calendars/organizer/outbox', $request->getPath());
$this->assertEquals('calendars/admin/outbox', $request->getPath());
$this->assertEquals('text/calendar', $request->getHeader('Content-Type'));
$this->assertEquals('0', $request->getHeader('Depth'));
$this->assertEquals($requestBody, $request->getBodyAsString());
Expand All @@ -1157,15 +1192,17 @@ public function testCheckAvailabilityWithMailtoPrefix(): void {
->method('createAttendeeAvailabilityServer')
->willReturn($server);

$start = new DateTimeImmutable('2025-01-09T08:21:36Z');
$end = new DateTimeImmutable('2025-01-09T10:21:36Z');
$start = new DateTimeImmutable('2025-01-16T06:00:00Z');
$end = new DateTimeImmutable('2025-01-17T06:00:00Z');
$actual = $this->manager->checkAvailability($start, $end, $organizer, [
'mailto:[email protected]',
'mailto:[email protected]',
'mailto:[email protected]',
'mailto:[email protected]',
'mailto:[email protected]',
]);
$expected = [
new AvailabilityResult('[email protected]', false),
new AvailabilityResult('[email protected]', true),
new AvailabilityResult('[email protected]', false),
new AvailabilityResult('[email protected]', true),
new AvailabilityResult('[email protected]', false),
];
$this->assertEquals($expected, $actual);
}
Expand Down

0 comments on commit dcb17e3

Please sign in to comment.