aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRichard Steinmetz <richard@steinmetz.cloud>2025-01-08 21:08:15 +0100
committerRichard Steinmetz <richard@steinmetz.cloud>2025-01-13 10:12:31 +0100
commit3dbdf3266c888e5a7809334b8b07ae64ab0314ef (patch)
treece699e57dd743b5fb2bd50cb7ad495bc8c418cee /lib
parentdd0f7f0bbfb9c176fe4845458bdc62ec7b388d8f (diff)
downloadnextcloud-server-feat/ocp/attendee-availability-api.tar.gz
nextcloud-server-feat/ocp/attendee-availability-api.zip
feat(ocp): add calendar api to retrieve availability of attendeesfeat/ocp/attendee-availability-api
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php2
-rw-r--r--lib/composer/composer/autoload_static.php2
-rw-r--r--lib/private/Calendar/AvailabilityResult.php28
-rw-r--r--lib/private/Calendar/Manager.php93
-rw-r--r--lib/public/Calendar/IAvailabilityResult.php32
-rw-r--r--lib/public/Calendar/IManager.php19
6 files changed, 176 insertions, 0 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 200e2e75612..ffa8da43873 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -191,6 +191,7 @@ return array(
'OCP\\Cache\\CappedMemoryCache' => $baseDir . '/lib/public/Cache/CappedMemoryCache.php',
'OCP\\Calendar\\BackendTemporarilyUnavailableException' => $baseDir . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php',
'OCP\\Calendar\\Exceptions\\CalendarException' => $baseDir . '/lib/public/Calendar/Exceptions/CalendarException.php',
+ 'OCP\\Calendar\\IAvailabilityResult' => $baseDir . '/lib/public/Calendar/IAvailabilityResult.php',
'OCP\\Calendar\\ICalendar' => $baseDir . '/lib/public/Calendar/ICalendar.php',
'OCP\\Calendar\\ICalendarEventBuilder' => $baseDir . '/lib/public/Calendar/ICalendarEventBuilder.php',
'OCP\\Calendar\\ICalendarIsShared' => $baseDir . '/lib/public/Calendar/ICalendarIsShared.php',
@@ -1117,6 +1118,7 @@ return array(
'OC\\Broadcast\\Events\\BroadcastEvent' => $baseDir . '/lib/private/Broadcast/Events/BroadcastEvent.php',
'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php',
+ 'OC\\Calendar\\AvailabilityResult' => $baseDir . '/lib/private/Calendar/AvailabilityResult.php',
'OC\\Calendar\\CalendarEventBuilder' => $baseDir . '/lib/private/Calendar/CalendarEventBuilder.php',
'OC\\Calendar\\CalendarQuery' => $baseDir . '/lib/private/Calendar/CalendarQuery.php',
'OC\\Calendar\\Manager' => $baseDir . '/lib/private/Calendar/Manager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index bf9385c1741..89c0cd4395b 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -232,6 +232,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/public/Cache/CappedMemoryCache.php',
'OCP\\Calendar\\BackendTemporarilyUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Calendar/BackendTemporarilyUnavailableException.php',
'OCP\\Calendar\\Exceptions\\CalendarException' => __DIR__ . '/../../..' . '/lib/public/Calendar/Exceptions/CalendarException.php',
+ 'OCP\\Calendar\\IAvailabilityResult' => __DIR__ . '/../../..' . '/lib/public/Calendar/IAvailabilityResult.php',
'OCP\\Calendar\\ICalendar' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendar.php',
'OCP\\Calendar\\ICalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarEventBuilder.php',
'OCP\\Calendar\\ICalendarIsShared' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarIsShared.php',
@@ -1158,6 +1159,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Broadcast\\Events\\BroadcastEvent' => __DIR__ . '/../../..' . '/lib/private/Broadcast/Events/BroadcastEvent.php',
'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php',
'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php',
+ 'OC\\Calendar\\AvailabilityResult' => __DIR__ . '/../../..' . '/lib/private/Calendar/AvailabilityResult.php',
'OC\\Calendar\\CalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarEventBuilder.php',
'OC\\Calendar\\CalendarQuery' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarQuery.php',
'OC\\Calendar\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Manager.php',
diff --git a/lib/private/Calendar/AvailabilityResult.php b/lib/private/Calendar/AvailabilityResult.php
new file mode 100644
index 00000000000..8031758f64e
--- /dev/null
+++ b/lib/private/Calendar/AvailabilityResult.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Calendar;
+
+use OCP\Calendar\IAvailabilityResult;
+
+class AvailabilityResult implements IAvailabilityResult {
+ public function __construct(
+ private readonly string $attendee,
+ private readonly bool $available,
+ ) {
+ }
+
+ public function getAttendeeEmail(): string {
+ return $this->attendee;
+ }
+
+ public function isAvailable(): bool {
+ return $this->available;
+ }
+}
diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php
index 3469193a364..e86e0e1d410 100644
--- a/lib/private/Calendar/Manager.php
+++ b/lib/private/Calendar/Manager.php
@@ -8,7 +8,10 @@ declare(strict_types=1);
*/
namespace OC\Calendar;
+use DateTimeInterface;
use OC\AppFramework\Bootstrap\Coordinator;
+use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
+use OCA\DAV\ServerFactory;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Calendar\Exceptions\CalendarException;
use OCP\Calendar\ICalendar;
@@ -20,11 +23,16 @@ use OCP\Calendar\ICalendarQuery;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IHandleImipMessage;
use OCP\Calendar\IManager;
+use OCP\IUser;
+use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use Sabre\HTTP\Request;
+use Sabre\HTTP\Response;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
+use Sabre\VObject\Component\VFreeBusy;
use Sabre\VObject\Property\VCard\DateTime;
use Sabre\VObject\Reader;
use Throwable;
@@ -48,6 +56,8 @@ class Manager implements IManager {
private LoggerInterface $logger,
private ITimeFactory $timeFactory,
private ISecureRandom $random,
+ private IUserManager $userManager,
+ private ServerFactory $serverFactory,
) {
}
@@ -472,4 +482,87 @@ class Manager implements IManager {
$uid = $this->random->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);
return new CalendarEventBuilder($uid, $this->timeFactory);
}
+
+ public function checkAvailability(
+ DateTimeInterface $start,
+ DateTimeInterface $end,
+ IUser $organizer,
+ array $attendees,
+ ): array {
+ $organizerMailto = 'mailto:' . $organizer->getEMailAddress();
+ $request = new VCalendar();
+ $request->METHOD = 'REQUEST';
+ $request->add('VFREEBUSY', [
+ 'DTSTART' => $start,
+ 'DTEND' => $end,
+ 'ORGANIZER' => $organizerMailto,
+ 'ATTENDEE' => $organizerMailto,
+ ]);
+
+ $mailtoLen = strlen('mailto:');
+ foreach ($attendees as $attendee) {
+ if (str_starts_with($attendee, 'mailto:')) {
+ $attendee = substr($attendee, $mailtoLen);
+ }
+
+ $attendeeUsers = $this->userManager->getByEmail($attendee);
+ if ($attendeeUsers === []) {
+ continue;
+ }
+
+ $request->VFREEBUSY->add('ATTENDEE', "mailto:$attendee");
+ }
+
+ $organizerUid = $organizer->getUID();
+ $server = $this->serverFactory->createAttendeeAvailabilityServer();
+ /** @var CustomPrincipalPlugin $plugin */
+ $plugin = $server->getPlugin('auth');
+ $plugin->setCurrentPrincipal("principals/users/$organizerUid");
+
+ $request = new Request(
+ 'POST',
+ "/calendars/$organizerUid/outbox/",
+ [
+ 'Content-Type' => 'text/calendar',
+ 'Depth' => 0,
+ ],
+ $request->serialize(),
+ );
+ $response = new Response();
+ $server->invokeMethod($request, $response, false);
+
+ $xmlService = new \Sabre\Xml\Service();
+ $xmlService->elementMap = [
+ '{urn:ietf:params:xml:ns:caldav}response' => 'Sabre\Xml\Deserializer\keyValue',
+ '{urn:ietf:params:xml:ns:caldav}recipient' => 'Sabre\Xml\Deserializer\keyValue',
+ ];
+ $parsedResponse = $xmlService->parse($response->getBodyAsString());
+
+ $result = [];
+ foreach ($parsedResponse as $freeBusyResponse) {
+ $freeBusyResponse = $freeBusyResponse['value'];
+ if ($freeBusyResponse['{urn:ietf:params:xml:ns:caldav}request-status'] !== '2.0;Success') {
+ continue;
+ }
+
+ $freeBusyResponseData = \Sabre\VObject\Reader::read(
+ $freeBusyResponse['{urn:ietf:params:xml:ns:caldav}calendar-data']
+ );
+
+ $attendee = substr(
+ $freeBusyResponse['{urn:ietf:params:xml:ns:caldav}recipient']['{DAV:}href'],
+ $mailtoLen,
+ );
+
+ $vFreeBusy = $freeBusyResponseData->VFREEBUSY;
+ if (!($vFreeBusy instanceof VFreeBusy)) {
+ continue;
+ }
+
+ // TODO: actually check values of FREEBUSY properties to find a free slot
+ $result[] = new AvailabilityResult($attendee, $vFreeBusy->isFree($start, $end));
+ }
+
+ return $result;
+ }
}
diff --git a/lib/public/Calendar/IAvailabilityResult.php b/lib/public/Calendar/IAvailabilityResult.php
new file mode 100644
index 00000000000..d437a5da047
--- /dev/null
+++ b/lib/public/Calendar/IAvailabilityResult.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCP\Calendar;
+
+/**
+ * DTO for the availability check results.
+ * Holds information about whether an attendee is available or not during the request time slot.
+ *
+ * @since 31.0.0
+ */
+interface IAvailabilityResult {
+ /**
+ * Get the attendee's email address.
+ *
+ * @since 31.0.0
+ */
+ public function getAttendeeEmail(): string;
+
+ /**
+ * Whether the attendee is available during the requested time slot.
+ *
+ * @since 31.0.0
+ */
+ public function isAvailable(): bool;
+}
diff --git a/lib/public/Calendar/IManager.php b/lib/public/Calendar/IManager.php
index 8056d57d859..124dc65f5f6 100644
--- a/lib/public/Calendar/IManager.php
+++ b/lib/public/Calendar/IManager.php
@@ -8,6 +8,9 @@ declare(strict_types=1);
*/
namespace OCP\Calendar;
+use DateTimeInterface;
+use OCP\IUser;
+
/**
* This class provides access to the Nextcloud CalDAV backend.
* Use this class exclusively if you want to access calendars.
@@ -165,4 +168,20 @@ interface IManager {
* @since 31.0.0
*/
public function createEventBuilder(): ICalendarEventBuilder;
+
+ /**
+ * Check the availability of the given organizer and attendees in the given time range.
+ *
+ * @since 31.0.0
+ *
+ * @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 the organizer and 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 $organizer,
+ array $attendees,
+ ): array;
}