From e32f32bf3a75e961893ac59cac05ae400b37b046 Mon Sep 17 00:00:00 2001 From: SebastianKrupinski Date: Sat, 5 Jul 2025 14:56:01 -0400 Subject: [PATCH] feat: meeting proposals Signed-off-by: SebastianKrupinski Signed-off-by: Richard Steinmetz --- lib/private/Calendar/Manager.php | 55 ++++++++++++++++++++++++++++++ tests/lib/Calendar/ManagerTest.php | 11 ++++++ 2 files changed, 66 insertions(+) diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php index a506a66990ba5..65d1558abc9bf 100644 --- a/lib/private/Calendar/Manager.php +++ b/lib/private/Calendar/Manager.php @@ -11,6 +11,7 @@ use DateTimeInterface; use OC\AppFramework\Bootstrap\Coordinator; use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin; +use OCA\DAV\Db\PropertyMapper; use OCA\DAV\ServerFactory; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Calendar\Exceptions\CalendarException; @@ -58,6 +59,7 @@ public function __construct( private ISecureRandom $random, private IUserManager $userManager, private ServerFactory $serverFactory, + private PropertyMapper $propertyMapper, ) { } @@ -227,6 +229,7 @@ public function newQuery(string $principalUri): ICalendarQuery { protected function handleIMip( string $userId, string $message, + array $options = [], ): bool { $userUri = 'principals/users/' . $userId; @@ -287,6 +290,30 @@ protected function handleIMip( } } + if ($options['absent'] === 'create') { + // retrieve the primary calendar for the user + $calendar = $this->getPrimaryCalendar($userId); + if ($calendar !== null && ( + !$calendar instanceof IHandleImipMessage || !$calendar instanceof ICalendarIsWritable || $calendar->isDeleted() || !$calendar->isWritable() + )) { + $calendar = null; + } + // if no primary calendar is set, use the first writable calendar + if ($calendar === null) { + foreach ($userCalendars as $userCalendar) { + if ($userCalendar instanceof IHandleImipMessage && $userCalendar instanceof ICalendarIsWritable && !$userCalendar->isDeleted() && $userCalendar->isWritable()) { + $calendar = $userCalendar; + break; + } + } + } + if ($calendar === null) { + $this->logger->warning('iMip message could not be processed because no writable calendar was found'); + return false; + } + $calendar->handleIMipMessage($userId, $vObject->serialize()); + } + $this->logger->warning('iMip message could not be processed because no corresponding event was found in any calendar'); return false; @@ -437,4 +464,32 @@ public function checkAvailability( return $result; } + + public function getPrimaryCalendar(string $userId): ?ICalendar { + // determine if the principal has a default calendar configured + $properties = $this->propertyMapper->findPropertyByPathAndName( + $userId, + 'principals/users/' . $userId, + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' + ); + if ($properties === []) { + return null; + } + // extract the calendar URI from the property value + $propertyValue = $properties[0]->getPropertyvalue() ?? null; + if (str_starts_with($propertyValue, 'calendars/' . $userId)) { + $calendarUri = rtrim(str_replace('calendars/' . $userId . '/', '', $propertyValue), '/'); + } + if (empty($calendarUri)) { + return null; + } + // retrieve the calendar by URI + $calendars = $this->getCalendarsForPrincipal('principals/users/' . $userId, [$calendarUri]); + if ($calendars === []) { + return null; + } + + return $calendars[0]; + } + } diff --git a/tests/lib/Calendar/ManagerTest.php b/tests/lib/Calendar/ManagerTest.php index 8d258b5f27ddd..fadcad320ef7f 100644 --- a/tests/lib/Calendar/ManagerTest.php +++ b/tests/lib/Calendar/ManagerTest.php @@ -58,6 +58,7 @@ class ManagerTest extends TestCase { private IUserManager&MockObject $userManager; private ServerFactory&MockObject $serverFactory; + private PropertyMapper&MockObject $propertyMapper; private VCalendar $vCalendar1a; private VCalendar $vCalendar2a; @@ -73,6 +74,7 @@ protected function setUp(): void { $this->secureRandom = $this->createMock(ISecureRandom::class); $this->userManager = $this->createMock(IUserManager::class); $this->serverFactory = $this->createMock(ServerFactory::class); + $this->propertyMapper = $this->createMock(PropertyMapper::class); $this->manager = new Manager( $this->coordinator, @@ -82,6 +84,7 @@ protected function setUp(): void { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ); // construct calendar with a 1 hour event and same start/end time zones @@ -329,6 +332,7 @@ public function testHandleImipWithNoCalendars(): void { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['getCalendarsForPrincipal']) ->getMock(); @@ -362,6 +366,7 @@ public function testHandleImipWithNoEvent(): void { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['getCalendarsForPrincipal']) ->getMock(); @@ -396,6 +401,7 @@ public function testHandleImipWithNoUid(): void { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['getCalendarsForPrincipal']) ->getMock(); @@ -439,6 +445,7 @@ public function testHandleImipWithNoMatch(): void { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['getCalendarsForPrincipal']) ->getMock(); @@ -481,6 +488,7 @@ public function testHandleImip(): void { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['getCalendarsForPrincipal']) ->getMock(); @@ -528,6 +536,7 @@ public function testhandleIMipRequest() { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['handleIMip']) ->getMock(); @@ -570,6 +579,7 @@ public function testhandleIMipReply() { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['handleIMip']) ->getMock(); @@ -614,6 +624,7 @@ public function testhandleIMipCancel() { $this->secureRandom, $this->userManager, $this->serverFactory, + $this->propertyMapper, ]) ->onlyMethods(['handleIMip']) ->getMock();