]> source.dussan.org Git - nextcloud-server.git/commitdiff
fix(userstatus): set user status to 'In a meeting' if calendar is busy 42309/head
authorAnna Larch <anna@nextcloud.com>
Fri, 15 Dec 2023 13:51:01 +0000 (14:51 +0100)
committerAnna Larch <anna@nextcloud.com>
Tue, 19 Dec 2023 13:59:00 +0000 (14:59 +0100)
Signed-off-by: Anna Larch <anna@nextcloud.com>
apps/dav/composer/composer/autoload_classmap.php
apps/dav/composer/composer/autoload_static.php
apps/dav/lib/CalDAV/Status/Status.php [deleted file]
apps/dav/lib/CalDAV/Status/StatusService.php
apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php
apps/user_status/lib/Controller/UserStatusController.php
apps/user_status/lib/Listener/UserLiveStatusListener.php
apps/user_status/lib/Service/StatusService.php
apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php
apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php
apps/user_status/tests/Unit/Service/StatusServiceTest.php

index 5d6b077ad532eb94c69ec4bad31c97877310f075..72455e6bf6779a36229a5e9f5c599be083abd89d 100644 (file)
@@ -99,7 +99,6 @@ return array(
     'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/PropFilter.php',
     'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => $baseDir . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
     'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => $baseDir . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
-    'OCA\\DAV\\CalDAV\\Status\\Status' => $baseDir . '/../lib/CalDAV/Status/Status.php',
     'OCA\\DAV\\CalDAV\\Status\\StatusService' => $baseDir . '/../lib/CalDAV/Status/StatusService.php',
     'OCA\\DAV\\CalDAV\\TimezoneService' => $baseDir . '/../lib/CalDAV/TimezoneService.php',
     'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => $baseDir . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',
index 2456604a9cbde72acd8afb606a1b4100ce1305c1..0d715f510f778602d7e1e6fe4efb7cd30ee289f7 100644 (file)
@@ -114,7 +114,6 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/PropFilter.php',
         'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Filter/SearchTermFilter.php',
         'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport' => __DIR__ . '/..' . '/../lib/CalDAV/Search/Xml/Request/CalendarSearchReport.php',
-        'OCA\\DAV\\CalDAV\\Status\\Status' => __DIR__ . '/..' . '/../lib/CalDAV/Status/Status.php',
         'OCA\\DAV\\CalDAV\\Status\\StatusService' => __DIR__ . '/..' . '/../lib/CalDAV/Status/StatusService.php',
         'OCA\\DAV\\CalDAV\\TimezoneService' => __DIR__ . '/..' . '/../lib/CalDAV/TimezoneService.php',
         'OCA\\DAV\\CalDAV\\Trashbin\\DeletedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Trashbin/DeletedCalendarObject.php',
diff --git a/apps/dav/lib/CalDAV/Status/Status.php b/apps/dav/lib/CalDAV/Status/Status.php
deleted file mode 100644 (file)
index 46ddf7e..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright 2023 Anna Larch <anna.larch@gmx.net>
- *
- * @author Anna Larch <anna.larch@gmx.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-namespace OCA\DAV\CalDAV\Status;
-
-class Status {
-       public function __construct(private string $status = '', private ?string $message = null, private ?string $customMessage = null, private ?int $timestamp = null, private ?string $customEmoji = null) {
-       }
-
-       public function getStatus(): string {
-               return $this->status;
-       }
-
-       public function setStatus(string $status): void {
-               $this->status = $status;
-       }
-
-       public function getMessage(): ?string {
-               return $this->message;
-       }
-
-       public function setMessage(?string $message): void {
-               $this->message = $message;
-       }
-
-       public function getCustomMessage(): ?string {
-               return $this->customMessage;
-       }
-
-       public function setCustomMessage(?string $customMessage): void {
-               $this->customMessage = $customMessage;
-       }
-
-       public function setEndTime(?int $timestamp): void {
-               $this->timestamp = $timestamp;
-       }
-
-       public function getEndTime(): ?int {
-               return $this->timestamp;
-       }
-
-       public function getCustomEmoji(): ?string {
-               return $this->customEmoji;
-       }
-
-       public function setCustomEmoji(?string $emoji): void {
-               $this->customEmoji = $emoji;
-       }
-}
index add8a668f7a41232b51735ea55fc9f7f66abe89d..11be9d8b2b809e9f862818e6ab4d02bf677e8ffc 100644 (file)
@@ -25,83 +25,123 @@ declare(strict_types=1);
  */
 namespace OCA\DAV\CalDAV\Status;
 
+use DateTimeImmutable;
 use OC\Calendar\CalendarQuery;
 use OCA\DAV\CalDAV\CalendarImpl;
-use OCA\DAV\CalDAV\FreeBusy\FreeBusyGenerator;
-use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
-use OCA\DAV\CalDAV\Schedule\Plugin as SchedulePlugin;
+use OCA\UserStatus\Service\StatusService as UserStatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
 use OCP\AppFramework\Utility\ITimeFactory;
 use OCP\Calendar\IManager;
-use OCP\IL10N;
+use OCP\ICache;
+use OCP\ICacheFactory;
 use OCP\IUser as User;
+use OCP\IUserManager;
+use OCP\User\IAvailabilityCoordinator;
 use OCP\UserStatus\IUserStatus;
+use Psr\Log\LoggerInterface;
 use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
-use Sabre\DAV\Exception\NotAuthenticated;
-use Sabre\DAVACL\Exception\NeedPrivileges;
-use Sabre\DAVACL\Plugin as AclPlugin;
-use Sabre\VObject\Component;
-use Sabre\VObject\Component\VEvent;
-use Sabre\VObject\Parameter;
-use Sabre\VObject\Property;
 
 class StatusService {
+       private ICache $cache;
        public function __construct(private ITimeFactory $timeFactory,
                private IManager $calendarManager,
-               private InvitationResponseServer $server,
-               private IL10N $l10n,
-               private FreeBusyGenerator $generator) {
+               private IUserManager $userManager,
+               private UserStatusService $userStatusService,
+               private IAvailabilityCoordinator $availabilityCoordinator,
+               private ICacheFactory $cacheFactory,
+               private LoggerInterface $logger) {
+               $this->cache = $cacheFactory->createLocal('CalendarStatusService');
        }
 
-       public function processCalendarAvailability(User $user): ?Status {
-               $userId = $user->getUID();
-               $email = $user->getEMailAddress();
-               if($email === null) {
-                       return null;
+       public function processCalendarStatus(string $userId): void {
+               $user = $this->userManager->get($userId);
+               if($user === null) {
+                       return;
                }
 
-               $server = $this->server->getServer();
+               $availability = $this->availabilityCoordinator->getCurrentOutOfOfficeData($user);
+               if($availability !== null && $this->availabilityCoordinator->isInEffect($availability)) {
+                       $this->logger->debug('An Absence is in effect, skipping calendar status check', ['user' => $userId]);
+                       return;
+               }
 
-               /** @var SchedulePlugin $schedulingPlugin */
-               $schedulingPlugin = $server->getPlugin('caldav-schedule');
-               $caldavNS = '{'.$schedulingPlugin::NS_CALDAV.'}';
+               $calendarEvents = $this->cache->get($userId);
+               if($calendarEvents === null) {
+                       $calendarEvents = $this->getCalendarEvents($user);
+                       $this->cache->set($userId, $calendarEvents, 300);
+               }
 
-               /** @var AclPlugin $aclPlugin */
-               $aclPlugin = $server->getPlugin('acl');
-               if ('mailto:' === substr($email, 0, 7)) {
-                       $email = substr($email, 7);
+               if(empty($calendarEvents)) {
+                       $this->userStatusService->revertUserStatus($userId, IUserStatus::MESSAGE_CALENDAR_BUSY);
+                       $this->logger->debug('No calendar events found for status check', ['user' => $userId]);
+                       return;
                }
 
-               $result = $aclPlugin->principalSearch(
-                       ['{http://sabredav.org/ns}email-address' => $email],
-                       [
-                               '{DAV:}principal-URL',
-                               $caldavNS.'calendar-home-set',
-                               $caldavNS.'schedule-inbox-URL',
-                               '{http://sabredav.org/ns}email-address',
-                       ]
-               );
+               $userStatusTimestamp = null;
+               $currentStatus = null;
+               try {
+                       $currentStatus = $this->userStatusService->findByUserId($userId);
+                       $userStatusTimestamp = $currentStatus->getIsUserDefined() ? $currentStatus->getStatusTimestamp() : null;
+               } catch (DoesNotExistException) {
+               }
 
-               if (!count($result) || !isset($result[0][200][$caldavNS.'schedule-inbox-URL'])) {
-                       return null;
+               if($currentStatus !== null && $currentStatus->getMessageId() === IUserStatus::MESSAGE_CALL
+                       || $currentStatus !== null && $currentStatus->getStatus() === IUserStatus::DND
+                       || $currentStatus !== null && $currentStatus->getStatus() === IUserStatus::INVISIBLE) {
+                       // We don't overwrite the call status, DND status or Invisible status
+                       $this->logger->debug('Higher priority status detected, skipping calendar status change', ['user' => $userId]);
+                       return;
                }
 
-               $inboxUrl = $result[0][200][$caldavNS.'schedule-inbox-URL']->getHref();
+               // Filter events to see if we have any that apply to the calendar status
+               $applicableEvents = array_filter($calendarEvents, function (array $calendarEvent) use ($userStatusTimestamp) {
+                       $component = $calendarEvent['objects'][0];
+                       if(isset($component['X-NEXTCLOUD-OUT-OF-OFFICE'])) {
+                               return false;
+                       }
+                       if(isset($component['DTSTART']) && $userStatusTimestamp !== null) {
+                               /** @var DateTimeImmutable $dateTime */
+                               $dateTime = $component['DTSTART'][0];
+                               $timestamp = $dateTime->getTimestamp();
+                               if($userStatusTimestamp > $timestamp) {
+                                       return false;
+                               }
+                       }
+                       // Ignore events that are transparent
+                       if(isset($component['TRANSP']) && strcasecmp($component['TRANSP'][0], 'TRANSPARENT') === 0) {
+                               return false;
+                       }
+                       return true;
+               });
 
-               // Do we have permission?
-               try {
-                       $aclPlugin->checkPrivileges($inboxUrl, $caldavNS.'schedule-query-freebusy');
-               } catch (NeedPrivileges | NotAuthenticated $exception) {
-                       return null;
+               if(empty($applicableEvents)) {
+                       $this->userStatusService->revertUserStatus($userId, IUserStatus::MESSAGE_CALENDAR_BUSY);
+                       $this->logger->debug('No status relevant events found, skipping calendar status change', ['user' => $userId]);
+                       return;
                }
 
-               $now = $this->timeFactory->now();
-               $calendarTimeZone = $now->getTimezone();
-               $calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $userId);
+               // One event that fulfills all status conditions is enough
+               // 1. Not an OOO event
+               // 2. Current user status was not set after the start of this event
+               // 3. Event is not set to be transparent
+               $count = count($applicableEvents);
+               $this->logger->debug("Found $count applicable event(s), changing user status", ['user' => $userId]);
+               $this->userStatusService->setUserStatus(
+                       $userId,
+                       IUserStatus::AWAY,
+                       IUserStatus::MESSAGE_CALENDAR_BUSY,
+                       true
+               );
+
+       }
+
+       private function getCalendarEvents(User $user): array {
+               $calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $user->getUID());
                if(empty($calendars)) {
-                       return null;
+                       return [];
                }
 
-               $query = $this->calendarManager->newQuery('principals/users/' . $userId);
+               $query = $this->calendarManager->newQuery('principals/users/' . $user->getUID());
                foreach ($calendars as $calendarObject) {
                        // We can only work with a calendar if it exposes its scheduling information
                        if (!$calendarObject instanceof CalendarImpl) {
@@ -114,83 +154,20 @@ class StatusService {
                                // ignore it for free-busy purposes.
                                continue;
                        }
-
-                       /** @var Component\VTimeZone|null $ctz */
-                       $ctz = $calendarObject->getSchedulingTimezone();
-                       if ($ctz !== null) {
-                               $calendarTimeZone = $ctz->getTimeZone();
-                       }
                        $query->addSearchCalendar($calendarObject->getUri());
                }
 
-               $calendarEvents = [];
-               $dtStart = $now;
-               $dtEnd = \DateTimeImmutable::createFromMutable($this->timeFactory->getDateTime('+10 minutes'));
+               $dtStart = DateTimeImmutable::createFromMutable($this->timeFactory->getDateTime());
+               $dtEnd = DateTimeImmutable::createFromMutable($this->timeFactory->getDateTime('+5 minutes'));
 
                // Only query the calendars when there's any to search
                if($query instanceof CalendarQuery && !empty($query->getCalendarUris())) {
                        // Query the next hour
                        $query->setTimerangeStart($dtStart);
                        $query->setTimerangeEnd($dtEnd);
-                       $calendarEvents = $this->calendarManager->searchForPrincipal($query);
+                       return $this->calendarManager->searchForPrincipal($query);
                }
 
-               // @todo we can cache that
-               if(empty($calendarEvents)) {
-                       return null;
-               }
-
-               $calendar = $this->generator->getVCalendar();
-               foreach ($calendarEvents as $calendarEvent) {
-                       $vEvent = new VEvent($calendar, 'VEVENT');
-                       foreach($calendarEvent['objects'] as $component) {
-                               foreach ($component as $key => $value) {
-                                       $vEvent->add($key, $value[0]);
-                               }
-                       }
-                       $calendar->add($vEvent);
-               }
-
-               $calendar->METHOD = 'REQUEST';
-
-               $this->generator->setObjects($calendar);
-               $this->generator->setTimeRange($dtStart, $dtEnd);
-               $this->generator->setTimeZone($calendarTimeZone);
-               $result = $this->generator->getResult();
-
-               if (!isset($result->VFREEBUSY)) {
-                       return null;
-               }
-
-               /** @var Component $freeBusyComponent */
-               $freeBusyComponent = $result->VFREEBUSY;
-               $freeBusyProperties = $freeBusyComponent->select('FREEBUSY');
-               // If there is no FreeBusy property, the time-range is empty and available
-               if (count($freeBusyProperties) === 0) {
-                       return null;
-               }
-
-               /** @var Property $freeBusyProperty */
-               $freeBusyProperty = $freeBusyProperties[0];
-               if (!$freeBusyProperty->offsetExists('FBTYPE')) {
-                       // If there is no FBTYPE, it means it's busy from a regular event
-                       return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY);
-               }
-
-               // If we can't deal with the FBTYPE (custom properties are a possibility)
-               // we should ignore it and leave the current status
-               $fbTypeParameter = $freeBusyProperty->offsetGet('FBTYPE');
-               if (!($fbTypeParameter instanceof Parameter)) {
-                       return null;
-               }
-               $fbType = $fbTypeParameter->getValue();
-               switch ($fbType) {
-                       // Ignore BUSY-UNAVAILABLE, that's for the automation
-                       case 'BUSY':
-                       case 'BUSY-TENTATIVE':
-                               return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY, $this->l10n->t('In a meeting'));
-                       default:
-                               return null;
-               }
+               return [];
        }
 }
index 705298de125ad4f7b01dbf540e329489f2afc21e..7073c02b8e42982d8e7e3adaa798ea5bf735e05b 100644 (file)
@@ -24,31 +24,31 @@ namespace OCA\DAV\Tests\unit\CalDAV\Status;
 
 use OC\Calendar\CalendarQuery;
 use OCA\DAV\CalDAV\CalendarImpl;
-use OCA\DAV\CalDAV\FreeBusy\FreeBusyGenerator;
-use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer;
-use OCA\DAV\CalDAV\Schedule\Plugin;
 use OCA\DAV\CalDAV\Status\StatusService;
-use OCA\DAV\Connector\Sabre\Server;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Service\StatusService as UserStatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
 use OCP\AppFramework\Utility\ITimeFactory;
 use OCP\Calendar\IManager;
-use OCP\IL10N;
+use OCP\ICache;
+use OCP\ICacheFactory;
 use OCP\IUser;
+use OCP\IUserManager;
+use OCP\User\IAvailabilityCoordinator;
+use OCP\User\IOutOfOfficeData;
+use OCP\UserStatus\IUserStatus;
 use PHPUnit\Framework\MockObject\MockObject;
-use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
-use Sabre\DAV\Exception\NotAuthenticated;
-use Sabre\DAV\Xml\Property\LocalHref;
-use Sabre\DAVACL\Exception\NeedPrivileges;
-use Sabre\VObject\Component\VCalendar;
-use Sabre\VObject\Component\VTimeZone;
-use Sabre\VObject\Reader;
+use Psr\Log\LoggerInterface;
 use Test\TestCase;
 
 class StatusServiceTest extends TestCase {
        private ITimeFactory|MockObject $timeFactory;
        private IManager|MockObject $calendarManager;
-       private InvitationResponseServer|MockObject $server;
-       private IL10N|MockObject $l10n;
-       private FreeBusyGenerator|MockObject $generator;
+       private IUserManager|MockObject $userManager;
+       private UserStatusService|MockObject $userStatusService;
+       private IAvailabilityCoordinator|MockObject $availabilityCoordinator;
+       private ICacheFactory|MockObject $cacheFactory;
+       private LoggerInterface|MockObject $logger;
        private StatusService $service;
 
        protected function setUp(): void {
@@ -56,644 +56,360 @@ class StatusServiceTest extends TestCase {
 
                $this->timeFactory = $this->createMock(ITimeFactory::class);
                $this->calendarManager = $this->createMock(IManager::class);
-               $this->server = $this->createMock(InvitationResponseServer::class);
-               $this->l10n = $this->createMock(IL10N::class);
-               $this->generator = $this->createMock(FreeBusyGenerator::class);
+               $this->userManager = $this->createMock(IUserManager::class);
+               $this->userStatusService = $this->createMock(UserStatusService::class);
+               $this->availabilityCoordinator = $this->createMock(IAvailabilityCoordinator::class);
+               $this->cacheFactory = $this->createMock(ICacheFactory::class);
+               $this->logger = $this->createMock(LoggerInterface::class);
+               $this->cache = $this->createMock(ICache::class);
+               $this->cacheFactory->expects(self::once())
+                       ->method('createLocal')
+                       ->with('CalendarStatusService')
+                       ->willReturn($this->cache);
 
                $this->service = new StatusService($this->timeFactory,
                        $this->calendarManager,
-                       $this->server,
-                       $this->l10n,
-                       $this->generator);
+                       $this->userManager,
+                       $this->userStatusService,
+                       $this->availabilityCoordinator,
+                       $this->cacheFactory,
+                       $this->logger,
+               );
        }
 
-       public function testNoEmail(): void {
-               $user = $this->createConfiguredMock(IUser::class, [
-                       'getUID' => 'admin',
-                       'getEMailAddress' => null,
-               ]);
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
+       public function testNoUser(): void {
+               $this->userManager->expects(self::once())
+                       ->method('get')
                        ->willReturn(null);
-               $this->server->expects(self::never())
-                       ->method('getServer');
-               $this->timeFactory->expects(self::never())
-                       ->method('now');
-               $this->timeFactory->expects(self::never())
-                       ->method('getDateTime');
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('getCurrentOutOfOfficeData');
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->logger->expects(self::never())
+                       ->method('debug');
+               $this->cache->expects(self::never())
+                       ->method('get');
+               $this->cache->expects(self::never())
+                       ->method('set');
                $this->calendarManager->expects(self::never())
                        ->method('getCalendarsForPrincipal');
                $this->calendarManager->expects(self::never())
                        ->method('newQuery');
+               $this->timeFactory->expects(self::never())
+                       ->method('getDateTime');
                $this->calendarManager->expects(self::never())
                        ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+               $this->userStatusService->expects(self::never())
+                       ->method('revertUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('findByUserId');
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testNoAcl(): void {
+       public function testOOOInEffect(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn([]);
-               $aclPlugin->expects(self::never())
-                       ->method('checkPrivileges');
-               $this->timeFactory->expects(self::never())
-                       ->method('now');
-               $this->timeFactory->expects(self::never())
-                       ->method('getDateTime');
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn($this->createMock(IOutOfOfficeData::class));
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('isInEffect')
+                       ->willReturn(true);
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->cache->expects(self::never())
+                       ->method('get');
+               $this->cache->expects(self::never())
+                       ->method('set');
                $this->calendarManager->expects(self::never())
                        ->method('getCalendarsForPrincipal');
                $this->calendarManager->expects(self::never())
                        ->method('newQuery');
+               $this->timeFactory->expects(self::never())
+                       ->method('getDateTime');
                $this->calendarManager->expects(self::never())
                        ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+               $this->userStatusService->expects(self::never())
+                       ->method('revertUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('findByUserId');
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testNoInbox(): void {
+       public function testNoCalendars(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn(null);
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->cache->expects(self::once())
+                       ->method('get')
+                       ->willReturn(null);
+               $this->cache->expects(self::once())
+                       ->method('set');
+               $this->calendarManager->expects(self::once())
+                       ->method('getCalendarsForPrincipal')
                        ->willReturn([]);
-               $aclPlugin->expects(self::never())
-                       ->method('checkPrivileges');
-               $this->timeFactory->expects(self::never())
-                       ->method('now');
-               $this->timeFactory->expects(self::never())
-                       ->method('getDateTime');
-               $this->calendarManager->expects(self::never())
-                       ->method('getCalendarsForPrincipal');
                $this->calendarManager->expects(self::never())
                        ->method('newQuery');
-               $this->calendarManager->expects(self::never())
-                       ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
-       }
-
-       public function testNoPrivilegesAcl(): void {
-               $user = $this->createConfiguredMock(IUser::class, [
-                       'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
-               ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-               $principal = 'principals/users/admin';
-               $calendarHome = $this->createMock(LocalHref::class);
-               $acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn($acl);
-               $calendarHome->expects(self::once())
-                       ->method('getHref')
-                       ->willReturn('calendars/admin/inbox/');
-               $aclPlugin->expects(self::once())
-                       ->method('checkPrivileges')
-                       ->willThrowException(new NeedPrivileges($principal, ['{DAV:}all']));
-               $this->timeFactory->expects(self::never())
-                       ->method('now');
                $this->timeFactory->expects(self::never())
                        ->method('getDateTime');
-               $this->calendarManager->expects(self::never())
-                       ->method('getCalendarsForPrincipal');
-               $this->calendarManager->expects(self::never())
-                       ->method('newQuery');
                $this->calendarManager->expects(self::never())
                        ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+               $this->userStatusService->expects(self::once())
+                       ->method('revertUserStatus');
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('findByUserId');
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testNotAuthenticated(): void {
+       public function testNoCalendarEvents(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-               $calendarHome = $this->createMock(LocalHref::class);
-               $acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn($acl);
-               $calendarHome->expects(self::once())
-                       ->method('getHref')
-                       ->willReturn('calendars/admin/inbox/');
-               $aclPlugin->expects(self::once())
-                       ->method('checkPrivileges')
-                       ->willThrowException(new NotAuthenticated());
-               $this->timeFactory->expects(self::never())
-                       ->method('now');
-               $this->timeFactory->expects(self::never())
-                       ->method('getDateTime');
-               $this->calendarManager->expects(self::never())
-                       ->method('getCalendarsForPrincipal');
-               $this->calendarManager->expects(self::never())
-                       ->method('newQuery');
-               $this->calendarManager->expects(self::never())
-                       ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn(null);
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->cache->expects(self::once())
+                       ->method('get')
+                       ->willReturn(null);
+               $this->cache->expects(self::once())
+                       ->method('set');
+               $this->calendarManager->expects(self::once())
+                       ->method('getCalendarsForPrincipal')
+                       ->willReturn([$this->createMock(CalendarImpl::class)]);
+               $this->calendarManager->expects(self::once())
+                       ->method('newQuery')
+                       ->willReturn(new CalendarQuery('admin'));
+               $this->timeFactory->expects(self::exactly(2))
+                       ->method('getDateTime')
+                       ->willReturn(new \DateTime());
+               $this->calendarManager->expects(self::once())
+                       ->method('searchForPrincipal')
+                       ->willReturn([]);
+               $this->userStatusService->expects(self::once())
+                       ->method('revertUserStatus');
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('findByUserId');
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testNoCalendars(): void {
+       public function testCalendarEvent(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-               $calendarHome = $this->createMock(LocalHref::class);
-               $acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
-               $now = new \DateTimeImmutable('1970-1-1', new \DateTimeZone('UTC'));
-               $principal = 'principals/users/admin';
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn($acl);
-               $calendarHome->expects(self::once())
-                       ->method('getHref')
-                       ->willReturn('calendars/admin/inbox/');
-               $aclPlugin->expects(self::once())
-                       ->method('checkPrivileges')
-                       ->willReturn(true);
-               $this->timeFactory->expects(self::once())
-                       ->method('now')
-                       ->willReturn($now);
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn(null);
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->cache->expects(self::once())
+                       ->method('get')
+                       ->willReturn(null);
+               $this->cache->expects(self::once())
+                       ->method('set');
                $this->calendarManager->expects(self::once())
                        ->method('getCalendarsForPrincipal')
-                       ->with($principal)
-                       ->willReturn([]);
-               $this->timeFactory->expects(self::never())
-                       ->method('getDateTime');
-               $this->calendarManager->expects(self::never())
-                       ->method('newQuery');
-               $this->calendarManager->expects(self::never())
-                       ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+                       ->willReturn([$this->createMock(CalendarImpl::class)]);
+               $this->calendarManager->expects(self::once())
+                       ->method('newQuery')
+                       ->willReturn(new CalendarQuery('admin'));
+               $this->timeFactory->expects(self::exactly(2))
+                       ->method('getDateTime')
+                       ->willReturn(new \DateTime());
+               $this->userStatusService->expects(self::once())
+                       ->method('findByUserId')
+                       ->willThrowException(new DoesNotExistException(''));
+               $this->calendarManager->expects(self::once())
+                       ->method('searchForPrincipal')
+                       ->willReturn([['objects' => [[]]]]);
+               $this->userStatusService->expects(self::never())
+                       ->method('revertUserStatus');
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->userStatusService->expects(self::once())
+                       ->method('setUserStatus');
+
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testEmptyAvailabilityAndNoSearchCalendars(): void {
+       public function testCallStatus(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-               $calendarHome = $this->createMock(LocalHref::class);
-               $now = new \DateTimeImmutable('1970-1-1', new \DateTimeZone('UTC'));
-               $inTenMinutes = new \DateTime('1970-1-1 01:00');
-               $acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
-               $principal = 'principals/users/admin';
-               $calendar = $this->createMock(CalendarImpl::class);
-               $query = $this->createMock(CalendarQuery::class);
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with([ '{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn($acl);
-               $calendarHome->expects(self::once())
-                       ->method('getHref')
-                       ->willReturn('calendars/admin/inbox/');
-               $aclPlugin->expects(self::once())
-                       ->method('checkPrivileges')
-                       ->willReturn(true);
-               $this->timeFactory->expects(self::once())
-                       ->method('now')
-                       ->willReturn($now);
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn(null);
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->cache->expects(self::once())
+                       ->method('get')
+                       ->willReturn(null);
+               $this->cache->expects(self::once())
+                       ->method('set');
                $this->calendarManager->expects(self::once())
                        ->method('getCalendarsForPrincipal')
-                       ->with($principal)
-                       ->willReturn([$calendar]);
+                       ->willReturn([$this->createMock(CalendarImpl::class)]);
                $this->calendarManager->expects(self::once())
                        ->method('newQuery')
-                       ->with($principal)
-                       ->willReturn($query);
-               $calendar->expects(self::once())
-                       ->method('getSchedulingTransparency')
-                       ->willReturn(new ScheduleCalendarTransp('transparent'));
-               $this->timeFactory->expects(self::once())
+                       ->willReturn(new CalendarQuery('admin'));
+               $this->timeFactory->expects(self::exactly(2))
                        ->method('getDateTime')
-                       ->with('+10 minutes')
-                       ->willReturn($inTenMinutes);
-               $this->calendarManager->expects(self::never())
-                       ->method('searchForPrincipal');
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+                       ->willReturn(new \DateTime());
+               $this->calendarManager->expects(self::once())
+                       ->method('searchForPrincipal')
+                       ->willReturn([['objects' => [[]]]]);
+               $userStatus = new UserStatus();
+               $userStatus->setMessageId(IUserStatus::MESSAGE_CALL);
+               $userStatus->setStatusTimestamp(123456);
+               $this->userStatusService->expects(self::once())
+                       ->method('findByUserId')
+                       ->willReturn($userStatus);
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->userStatusService->expects(self::never())
+                       ->method('revertUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testEmptyAvailabilityAndSearchCalendarsNoResults(): void {
+       public function testInvisibleStatus(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $availability = '';
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-               $calendarHome = $this->createMock(LocalHref::class);
-               $acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
-               $now = new \DateTimeImmutable('1970-1-1 00:00', new \DateTimeZone('UTC'));
-               $inTenMinutes = new \DateTime('1970-1-1 01:00');
-               $immutableInTenMinutes = \DateTimeImmutable::createFromMutable($inTenMinutes);
-               $principal = 'principals/users/admin';
-               $query = $this->createMock(CalendarQuery::class);
-               $timezone = new \DateTimeZone('UTC');
-               $timezoneObj = $this->createMock(VTimeZone::class);
-               $calendar = $this->createMock(CalendarImpl::class);
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with(['{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn($acl);
-               $calendarHome->expects(self::once())
-                       ->method('getHref')
-                       ->willReturn('calendars/admin/inbox/');
-               $aclPlugin->expects(self::once())
-                       ->method('checkPrivileges')
-                       ->willReturn(true);
-               $this->timeFactory->expects(self::once())
-                       ->method('now')
-                       ->willReturn($now);
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn(null);
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->cache->expects(self::once())
+                       ->method('get')
+                       ->willReturn(null);
+               $this->cache->expects(self::once())
+                       ->method('set');
                $this->calendarManager->expects(self::once())
                        ->method('getCalendarsForPrincipal')
-                       ->with($principal)
-                       ->willReturn([$calendar]);
+                       ->willReturn([$this->createMock(CalendarImpl::class)]);
                $this->calendarManager->expects(self::once())
                        ->method('newQuery')
-                       ->with($principal)
-                       ->willReturn($query);
-               $calendar->expects(self::once())
-                       ->method('getSchedulingTransparency')
-                       ->willReturn(new ScheduleCalendarTransp('opaque'));
-               $calendar->expects(self::once())
-                       ->method('getSchedulingTimezone')
-                       ->willReturn($timezoneObj);
-               $timezoneObj->expects(self::once())
-                       ->method('getTimeZone')
-                       ->willReturn($timezone);
-               $calendar->expects(self::once())
-                       ->method('getUri');
-               $query->expects(self::once())
-                       ->method('addSearchCalendar');
-               $query->expects(self::once())
-                       ->method('getCalendarUris')
-                       ->willReturn([$calendar]);
-               $this->timeFactory->expects(self::once())
+                       ->willReturn(new CalendarQuery('admin'));
+               $this->timeFactory->expects(self::exactly(2))
                        ->method('getDateTime')
-                       ->with('+10 minutes')
-                       ->willReturn($inTenMinutes);
-               $query->expects(self::once())
-                       ->method('setTimerangeStart')
-                       ->with($now);
-               $query->expects(self::once())
-                       ->method('setTimerangeEnd')
-                       ->with($immutableInTenMinutes);
+                       ->willReturn(new \DateTime());
                $this->calendarManager->expects(self::once())
                        ->method('searchForPrincipal')
-                       ->with($query)
-                       ->willReturn([]);
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+                       ->willReturn([['objects' => [[]]]]);
+               $userStatus = new UserStatus();
+               $userStatus->setStatus(IUserStatus::INVISIBLE);
+               $userStatus->setStatusTimestamp(123456);
+               $this->userStatusService->expects(self::once())
+                       ->method('findByUserId')
+                       ->willReturn($userStatus);
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->userStatusService->expects(self::never())
+                       ->method('revertUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+
+
+               $this->service->processCalendarStatus('admin');
        }
 
-       public function testSearchCalendarsNoResults(): void {
+       public function testDNDStatus(): void {
                $user = $this->createConfiguredMock(IUser::class, [
                        'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
                ]);
-               $server = $this->createMock(Server::class);
-               $schedulingPlugin = $this->createMock(Plugin::class);
-               $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class);
-               $calendarHome = $this->createMock(LocalHref::class);
-               $acl = [[200 => ['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL' => $calendarHome]]];
-               $now = new \DateTimeImmutable('1970-1-1 00:00', new \DateTimeZone('UTC'));
-               $inTenMinutes = new \DateTime('1970-1-1 01:00');
-               $immutableInTenMinutes = \DateTimeImmutable::createFromMutable($inTenMinutes);
-               $principal = 'principals/users/admin';
-               $query = $this->createMock(CalendarQuery::class);
-               $timezone = new \DateTimeZone('UTC');
-               $timezoneObj = $this->createMock(VTimeZone::class);
-               $calendar = $this->createMock(CalendarImpl::class);
-               $vCalendar = $this->createMock(VCalendar::class);
-               $result = Reader::read('BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Sabre//Sabre VObject 4.5.3//EN
-               CALSCALE:GREGORIAN
-METHOD:REQUEST
-END:VCALENDAR');
-
-               $user->expects(self::once())
-                       ->method('getUID')
-                       ->willReturn('admin');
-               $user->expects(self::once())
-                       ->method('getEMailAddress')
-                       ->willReturn('test@test.com');
-               $this->server->expects(self::once())
-                       ->method('getServer')
-                       ->willReturn($server);
-               $server->expects(self::exactly(2))
-                       ->method('getPlugin')
-                       ->withConsecutive(
-                               ['caldav-schedule'],
-                               ['acl'],
-                       )->willReturnOnConsecutiveCalls($schedulingPlugin, $aclPlugin);
-               $aclPlugin->expects(self::once())
-                       ->method('principalSearch')
-                       ->with(['{http://sabredav.org/ns}email-address' => 'test@test.com'])
-                       ->willReturn($acl);
-               $calendarHome->expects(self::once())
-                       ->method('getHref')
-                       ->willReturn('calendars/admin/inbox/');
-               $aclPlugin->expects(self::once())
-                       ->method('checkPrivileges')
-                       ->willReturn(true);
-               $this->timeFactory->expects(self::once())
-                       ->method('now')
-                       ->willReturn($now);
+
+               $this->userManager->expects(self::once())
+                       ->method('get')
+                       ->willReturn($user);
+               $this->availabilityCoordinator->expects(self::once())
+                       ->method('getCurrentOutOfOfficeData')
+                       ->willReturn(null);
+               $this->availabilityCoordinator->expects(self::never())
+                       ->method('isInEffect');
+               $this->cache->expects(self::once())
+                       ->method('get')
+                       ->willReturn(null);
+               $this->cache->expects(self::once())
+                       ->method('set');
                $this->calendarManager->expects(self::once())
                        ->method('getCalendarsForPrincipal')
-                       ->with($principal)
-                       ->willReturn([$calendar]);
+                       ->willReturn([$this->createMock(CalendarImpl::class)]);
                $this->calendarManager->expects(self::once())
                        ->method('newQuery')
-                       ->with($principal)
-                       ->willReturn($query);
-               $calendar->expects(self::once())
-                       ->method('getSchedulingTransparency')
-                       ->willReturn(new ScheduleCalendarTransp('opaque'));
-               $calendar->expects(self::once())
-                       ->method('getSchedulingTimezone')
-                       ->willReturn($timezoneObj);
-               $timezoneObj->expects(self::once())
-                       ->method('getTimeZone')
-                       ->willReturn($timezone);
-               $calendar->expects(self::once())
-                       ->method('getUri');
-               $query->expects(self::once())
-                       ->method('addSearchCalendar');
-               $query->expects(self::once())
-                       ->method('getCalendarUris')
-                       ->willReturn([$calendar]);
-               $this->timeFactory->expects(self::once())
+                       ->willReturn(new CalendarQuery('admin'));
+               $this->timeFactory->expects(self::exactly(2))
                        ->method('getDateTime')
-                       ->with('+10 minutes')
-                       ->willReturn($inTenMinutes);
-               $query->expects(self::once())
-                       ->method('setTimerangeStart')
-                       ->with($now);
-               $query->expects(self::once())
-                       ->method('setTimerangeEnd')
-                       ->with($immutableInTenMinutes);
+                       ->willReturn(new \DateTime());
                $this->calendarManager->expects(self::once())
                        ->method('searchForPrincipal')
-                       ->with($query)
-                       ->willReturn([]);
-               $this->generator->expects(self::never())
-                       ->method('getVCalendar');
-               $vCalendar->expects(self::never())
-                       ->method('add');
-               $this->generator->expects(self::never())
-                       ->method('setObjects');
-               $this->generator->expects(self::never())
-                       ->method('setTimeRange');
-               $this->generator->expects(self::never())
-                       ->method('setTimeZone');
-               $this->generator->expects(self::never())
-                       ->method('getResult');
-
-               $status = $this->service->processCalendarAvailability($user);
-               $this->assertNull($status);
+                       ->willReturn([['objects' => [[]]]]);
+               $userStatus = new UserStatus();
+               $userStatus->setStatus(IUserStatus::DND);
+               $userStatus->setStatusTimestamp(123456);
+               $this->userStatusService->expects(self::once())
+                       ->method('findByUserId')
+                       ->willReturn($userStatus);
+               $this->logger->expects(self::once())
+                       ->method('debug');
+               $this->userStatusService->expects(self::never())
+                       ->method('revertUserStatus');
+               $this->userStatusService->expects(self::never())
+                       ->method('setUserStatus');
+
+
+               $this->service->processCalendarStatus('admin');
        }
 }
index f2b1537e4f7a847a57287aa7b916f8c81e871433..3beb8abc3abd0db2e5865f2dc2d0b57119d53faf 100644 (file)
@@ -28,6 +28,7 @@ declare(strict_types=1);
  */
 namespace OCA\UserStatus\Controller;
 
+use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
 use OCA\UserStatus\Db\UserStatus;
 use OCA\UserStatus\Exception\InvalidClearAtException;
 use OCA\UserStatus\Exception\InvalidMessageIdException;
@@ -55,6 +56,7 @@ class UserStatusController extends OCSController {
                private string $userId,
                private LoggerInterface $logger,
                private StatusService $service,
+               private CalendarStatusService $calendarStatusService,
        ) {
                parent::__construct($appName, $request);
        }
@@ -71,6 +73,7 @@ class UserStatusController extends OCSController {
         */
        public function getStatus(): DataResponse {
                try {
+                       $this->calendarStatusService->processCalendarStatus($this->userId);
                        $userStatus = $this->service->findByUserId($this->userId);
                } catch (DoesNotExistException $ex) {
                        throw new OCSNotFoundException('No status for the current user');
index 0d0e6e3ebf02e601114feaf03cd83795474baa62..b999c51d72f210b9168cc3437ae2b1c845b72147 100644 (file)
@@ -25,6 +25,7 @@ declare(strict_types=1);
  */
 namespace OCA\UserStatus\Listener;
 
+use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
 use OCA\UserStatus\Connector\UserStatus as ConnectorUserStatus;
 use OCA\UserStatus\Db\UserStatus;
 use OCA\UserStatus\Db\UserStatusMapper;
@@ -48,7 +49,8 @@ class UserLiveStatusListener implements IEventListener {
 
        public function __construct(UserStatusMapper $mapper,
                StatusService $statusService,
-               ITimeFactory $timeFactory) {
+               ITimeFactory $timeFactory,
+               private CalendarStatusService $calendarStatusService) {
                $this->mapper = $mapper;
                $this->statusService = $statusService;
                $this->timeFactory = $timeFactory;
@@ -65,6 +67,7 @@ class UserLiveStatusListener implements IEventListener {
 
                $user = $event->getUser();
                try {
+                       $this->calendarStatusService->processCalendarStatus($user->getUID());
                        $userStatus = $this->statusService->findByUserId($user->getUID());
                } catch (DoesNotExistException $ex) {
                        $userStatus = new UserStatus();
@@ -81,6 +84,12 @@ class UserLiveStatusListener implements IEventListener {
                        return;
                }
 
+               // Don't overwrite the "away" calendar status if it's set
+               if($userStatus->getMessageId() === IUserStatus::MESSAGE_CALENDAR_BUSY) {
+                       $event->setUserStatus(new ConnectorUserStatus($userStatus));
+                       return;
+               }
+
                $needsUpdate = false;
 
                // If the current status is older than 5 minutes,
index 9582c403329cccc681097188a417df5be46b582a..c623262eec65d38ed5ca06542745625e92512e2d 100644 (file)
@@ -27,8 +27,6 @@ declare(strict_types=1);
  */
 namespace OCA\UserStatus\Service;
 
-use OCA\DAV\CalDAV\Status\Status as CalendarStatus;
-use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
 use OCA\UserStatus\Db\UserStatus;
 use OCA\UserStatus\Db\UserStatusMapper;
 use OCA\UserStatus\Exception\InvalidClearAtException;
@@ -89,8 +87,7 @@ class StatusService {
                private PredefinedStatusService $predefinedStatusService,
                private IEmojiHelper $emojiHelper,
                private IConfig $config,
-               private IUserManager $userManager,
-               private CalendarStatusService $calendarStatusService) {
+               private IUserManager $userManager) {
                $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
                $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
                $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
@@ -558,30 +555,4 @@ class StatusService {
                // For users that matched restore the previous status
                $this->mapper->restoreBackupStatuses($restoreIds);
        }
-
-       /**
-        * Calculate a users' status according to their calendar events
-        *
-        * There are 4 predefined types of FBTYPE - 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE',
-        * but 'X-' properties are possible
-        *
-        * @link https://icalendar.org/iCalendar-RFC-5545/3-2-9-free-busy-time-type.html
-        *
-        * The status will be changed for types
-        *  - 'BUSY'
-        *  - 'BUSY-TENTATIVE' (ex.: an event has been accepted tentatively)
-        * and all FREEBUSY components without a type (implicitly a 'BUSY' status)
-        *
-        * 'X-' properties and BUSY-UNAVAILABLE is not handled
-        *
-        * @param string $userId
-        * @return CalendarStatus|null
-        */
-       public function getCalendarStatus(string $userId): ?CalendarStatus {
-               $user = $this->userManager->get($userId);
-               if ($user === null) {
-                       return null;
-               }
-               return $this->calendarStatusService->processCalendarAvailability($user);
-       }
 }
index cabcc63b291dfea30e38c26530514f4ba3bf578a..6161eb100ec1808cdf5aec4470c17a93a3c7916b 100644 (file)
@@ -26,6 +26,7 @@ declare(strict_types=1);
  */
 namespace OCA\UserStatus\Tests\Controller;
 
+use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
 use OCA\UserStatus\Controller\UserStatusController;
 use OCA\UserStatus\Db\UserStatus;
 use OCA\UserStatus\Exception\InvalidClearAtException;
@@ -47,7 +48,10 @@ class UserStatusControllerTest extends TestCase {
        private $logger;
 
        /** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
-       private $service;
+       private $statusService;
+
+       /** @var CalendarStatusService|\PHPUnit\Framework\MockObject\MockObject $calendarStatusService */
+       private $calendarStatusService;
 
        /** @var UserStatusController */
        private $controller;
@@ -58,15 +62,23 @@ class UserStatusControllerTest extends TestCase {
                $request = $this->createMock(IRequest::class);
                $userId = 'john.doe';
                $this->logger = $this->createMock(LoggerInterface::class);
-               $this->service = $this->createMock(StatusService::class);
-
-               $this->controller = new UserStatusController('user_status', $request, $userId, $this->logger, $this->service);
+               $this->statusService = $this->createMock(StatusService::class);
+               $this->calendarStatusService = $this->createMock(CalendarStatusService::class);
+
+               $this->controller = new UserStatusController(
+                       'user_status',
+                       $request,
+                       $userId,
+                       $this->logger,
+                       $this->statusService,
+                       $this->calendarStatusService,
+               );
        }
 
        public function testGetStatus(): void {
                $userStatus = $this->getUserStatus();
 
-               $this->service->expects($this->once())
+               $this->statusService->expects($this->once())
                        ->method('findByUserId')
                        ->with('john.doe')
                        ->willReturn($userStatus);
@@ -85,7 +97,10 @@ class UserStatusControllerTest extends TestCase {
        }
 
        public function testGetStatusDoesNotExist(): void {
-               $this->service->expects($this->once())
+               $this->calendarStatusService->expects(self::once())
+                       ->method('processCalendarStatus')
+                       ->with('john.doe');
+               $this->statusService->expects($this->once())
                        ->method('findByUserId')
                        ->with('john.doe')
                        ->willThrowException(new DoesNotExistException(''));
@@ -121,12 +136,12 @@ class UserStatusControllerTest extends TestCase {
                $userStatus = $this->getUserStatus();
 
                if ($expectException) {
-                       $this->service->expects($this->once())
+                       $this->statusService->expects($this->once())
                                ->method('setStatus')
                                ->with('john.doe', $statusType, null, true)
                                ->willThrowException($exception);
                } else {
-                       $this->service->expects($this->once())
+                       $this->statusService->expects($this->once())
                                ->method('setStatus')
                                ->with('john.doe', $statusType, null, true)
                                ->willReturn($userStatus);
@@ -187,12 +202,12 @@ class UserStatusControllerTest extends TestCase {
                $userStatus = $this->getUserStatus();
 
                if ($expectException) {
-                       $this->service->expects($this->once())
+                       $this->statusService->expects($this->once())
                                ->method('setPredefinedMessage')
                                ->with('john.doe', $messageId, $clearAt)
                                ->willThrowException($exception);
                } else {
-                       $this->service->expects($this->once())
+                       $this->statusService->expects($this->once())
                                ->method('setPredefinedMessage')
                                ->with('john.doe', $messageId, $clearAt)
                                ->willReturn($userStatus);
@@ -259,28 +274,28 @@ class UserStatusControllerTest extends TestCase {
                $userStatus = $this->getUserStatus();
 
                if ($expectException) {
-                       $this->service->expects($this->once())
+                       $this->statusService->expects($this->once())
                                ->method('setCustomMessage')
                                ->with('john.doe', $statusIcon, $message, $clearAt)
                                ->willThrowException($exception);
                } else {
                        if ($expectSuccessAsReset) {
-                               $this->service->expects($this->never())
+                               $this->statusService->expects($this->never())
                                        ->method('setCustomMessage');
-                               $this->service->expects($this->once())
+                               $this->statusService->expects($this->once())
                                        ->method('clearMessage')
                                        ->with('john.doe');
-                               $this->service->expects($this->once())
+                               $this->statusService->expects($this->once())
                                        ->method('findByUserId')
                                        ->with('john.doe')
                                        ->willReturn($userStatus);
                        } else {
-                               $this->service->expects($this->once())
+                               $this->statusService->expects($this->once())
                                        ->method('setCustomMessage')
                                        ->with('john.doe', $statusIcon, $message, $clearAt)
                                        ->willReturn($userStatus);
 
-                               $this->service->expects($this->never())
+                               $this->statusService->expects($this->never())
                                        ->method('clearMessage');
                        }
                }
@@ -326,7 +341,7 @@ class UserStatusControllerTest extends TestCase {
        }
 
        public function testClearMessage(): void {
-               $this->service->expects($this->once())
+               $this->statusService->expects($this->once())
                        ->method('clearMessage')
                        ->with('john.doe');
 
index 5bc60ca71f92029747f2cf92e71671854d8ce60b..0f637b754111acccf5f58ce1ab3c9827d4ae216c 100644 (file)
@@ -26,6 +26,7 @@ declare(strict_types=1);
  */
 namespace OCA\UserStatus\Tests\Listener;
 
+use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
 use OCA\UserStatus\Db\UserStatus;
 use OCA\UserStatus\Db\UserStatusMapper;
 use OCA\UserStatus\Listener\UserDeletedListener;
@@ -36,27 +37,37 @@ use OCP\AppFramework\Utility\ITimeFactory;
 use OCP\EventDispatcher\GenericEvent;
 use OCP\IUser;
 use OCP\User\Events\UserLiveStatusEvent;
+use PHPUnit\Framework\MockObject\MockObject;
 use Test\TestCase;
 
 class UserLiveStatusListenerTest extends TestCase {
 
-       /** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */
+       /** @var UserStatusMapper|MockObject */
        private $mapper;
-       /** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
+       /** @var StatusService|MockObject */
        private $statusService;
-       /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
+       /** @var ITimeFactory|MockObject */
        private $timeFactory;
 
        /** @var UserDeletedListener */
        private $listener;
 
+       private CalendarStatusService|MockObject $calendarStatusService;
+
        protected function setUp(): void {
                parent::setUp();
 
                $this->mapper = $this->createMock(UserStatusMapper::class);
                $this->statusService = $this->createMock(StatusService::class);
                $this->timeFactory = $this->createMock(ITimeFactory::class);
-               $this->listener = new UserLiveStatusListener($this->mapper, $this->statusService, $this->timeFactory);
+               $this->calendarStatusService = $this->createMock(CalendarStatusService::class);
+
+               $this->listener = new UserLiveStatusListener(
+                       $this->mapper,
+                       $this->statusService,
+                       $this->timeFactory,
+                       $this->calendarStatusService,
+               );
        }
 
        /**
index 2de041712bd1c1d962977f51ef2653e916775a24..da11ec0943b41fe69a3d4c08fd358baae7896ff6 100644 (file)
@@ -28,9 +28,6 @@ namespace OCA\UserStatus\Tests\Service;
 
 use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
 use OC\DB\Exceptions\DbalException;
-use OC\User\User;
-use OCA\DAV\CalDAV\Status\Status;
-use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
 use OCA\UserStatus\Db\UserStatus;
 use OCA\UserStatus\Db\UserStatusMapper;
 use OCA\UserStatus\Exception\InvalidClearAtException;
@@ -45,7 +42,6 @@ use OCP\AppFramework\Utility\ITimeFactory;
 use OCP\DB\Exception;
 use OCP\IConfig;
 use OCP\IEmojiHelper;
-use OCP\IUser;
 use OCP\IUserManager;
 use OCP\UserStatus\IUserStatus;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -71,9 +67,6 @@ class StatusServiceTest extends TestCase {
        /** @var IUserManager|MockObject  */
        private $userManager;
 
-       /** @var CalendarStatusService|MockObject  */
-       private $calendarStatusService;
-
        private StatusService $service;
 
        protected function setUp(): void {
@@ -84,8 +77,6 @@ class StatusServiceTest extends TestCase {
                $this->predefinedStatusService = $this->createMock(PredefinedStatusService::class);
                $this->emojiHelper = $this->createMock(IEmojiHelper::class);
                $this->userManager = $this->createMock(IUserManager::class);
-               $this->calendarStatusService = $this->createMock(CalendarStatusService::class);
-
                $this->config = $this->createMock(IConfig::class);
 
                $this->config->method('getAppValue')
@@ -99,8 +90,7 @@ class StatusServiceTest extends TestCase {
                        $this->predefinedStatusService,
                        $this->emojiHelper,
                        $this->config,
-                       $this->userManager,
-                       $this->calendarStatusService,
+                       $this->userManager
                );
        }
 
@@ -156,8 +146,7 @@ class StatusServiceTest extends TestCase {
                        $this->predefinedStatusService,
                        $this->emojiHelper,
                        $this->config,
-                       $this->userManager,
-                       $this->calendarStatusService,
+                       $this->userManager
                );
 
                $this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
@@ -176,8 +165,7 @@ class StatusServiceTest extends TestCase {
                        $this->predefinedStatusService,
                        $this->emojiHelper,
                        $this->config,
-                       $this->userManager,
-                       $this->calendarStatusService,
+                       $this->userManager
                );
 
                $this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
@@ -837,35 +825,4 @@ class StatusServiceTest extends TestCase {
 
                $this->service->revertMultipleUserStatus(['john', 'nobackup', 'backuponly', 'nobackupanddnd'], 'call');
        }
-
-       public function testCalendarAvailabilityNoUser(): void {
-               $userId = 'admin';
-
-               $this->userManager->expects(self::once())
-                       ->method('get')
-                       ->with($userId)
-                       ->willReturn(null);
-               $this->calendarStatusService->expects(self::never())
-                       ->method('processCalendarAvailability');
-
-               $this->service->getCalendarStatus($userId);
-       }
-
-       public function testCalendarAvailabilityNoStatus(): void {
-               $user = $this->createConfiguredMock(IUser::class, [
-                       'getUID' => 'admin',
-                       'getEMailAddress' => 'test@test.com',
-               ]);
-
-               $this->userManager->expects(self::once())
-                       ->method('get')
-                       ->with($user->getUID())
-                       ->willReturn($user);
-               $this->calendarStatusService->expects(self::once())
-                       ->method('processCalendarAvailability')
-                       ->with($user)
-                       ->willReturn(null);
-
-               $this->service->getCalendarStatus($user->getUID());
-       }
 }