Skip to content

Commit f4cfa4d

Browse files
committed
fixup! feat(user status): automate user status for events
1 parent b334893 commit f4cfa4d

4 files changed

Lines changed: 73 additions & 33 deletions

File tree

apps/user_status/lib/Db/UserStatusMapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public function getAvailabilityFromPropertiesTable(string $userId): ?string {
216216
$propertyPath = 'calendars/' . $userId . '/inbox';
217217
$propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability';
218218

219-
$query = $this->connection->getQueryBuilder();
219+
$query = $this->db->getQueryBuilder();
220220
$query->select('propertyvalue')
221221
->from('properties')
222222
->where($query->expr()->eq('userid', $query->createNamedParameter($userId)))

apps/user_status/lib/Service/PredefinedStatusService.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ public function isValidId(string $id): bool {
202202
self::REMOTE_WORK,
203203
IUserStatus::MESSAGE_CALL,
204204
IUserStatus::MESSAGE_AVAILABILITY,
205+
IUserStatus::MESSAGE_CALENDAR_BUSY,
206+
IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE,
205207
], true);
206208
}
207209
}

apps/user_status/lib/Service/StatusService.php

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -563,15 +563,34 @@ public function revertMultipleUserStatus(array $userIds, string $messageId): voi
563563
$this->mapper->restoreBackupStatuses($restoreIds);
564564
}
565565

566-
private function processCalendarAvailability(string $userId) {
566+
/**
567+
* Calculate a users' status according to their availabilit settings and their calendar
568+
* events
569+
*
570+
* There are 4 predefined types of FBTYPE - 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE',
571+
* but 'X-' properties are possible
572+
* @link https://icalendar.org/iCalendar-RFC-5545/3-2-9-free-busy-time-type.html
573+
*
574+
* The status will be changed for types
575+
* - 'BUSY'
576+
* - 'BUSY-UNAVAILABLE' (ex.: when a VAVILABILITY setting is in effect)
577+
* - 'BUSY-TENTATIVE' (ex.: an event has been accepted tentatively)
578+
* and all FREEBUSY components without a type (implicitly a 'BUSY' status)
579+
*
580+
* 'X-' properties are not handled for now
581+
*
582+
* @param string $userId
583+
* @return void
584+
*/
585+
private function processCalendarAvailability(string $userId): void {
567586
$user = $this->userManager->get($userId);
568587
if($user === null) {
569-
return false;
588+
return;
570589
}
571590

572591
$email = $user->getEMailAddress();
573592
if($email === null) {
574-
return false;
593+
return;
575594
}
576595

577596
$server = new InvitationResponseServer();
@@ -598,7 +617,7 @@ private function processCalendarAvailability(string $userId) {
598617
);
599618

600619
if (!count($result) || !isset($result[0][200][$caldavNS.'schedule-inbox-URL'])) {
601-
return false;
620+
return;
602621
}
603622

604623
$inboxUrl = $result[0][200][$caldavNS.'schedule-inbox-URL']->getHref();
@@ -607,13 +626,13 @@ private function processCalendarAvailability(string $userId) {
607626
try {
608627
$aclPlugin->checkPrivileges($inboxUrl, $caldavNS.'schedule-query-freebusy');
609628
} catch (NeedPrivileges | NotAuthenticated $exception) {
610-
return false;
629+
return;
611630
}
612631

613632
$calendarTimeZone = new DateTimeZone('UTC');
614633
$calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $userId);
615634
if(empty($calendars)) {
616-
return false;
635+
return;
617636
}
618637

619638
$query = new CalendarQuery('principals/users/' . $userId);
@@ -624,7 +643,6 @@ private function processCalendarAvailability(string $userId) {
624643
}
625644

626645
$sct = $calendarObjects->getSchedulingTransparency();
627-
628646
if (!empty($sct) && ScheduleCalendarTransp::TRANSPARENT == $sct->getValue()) {
629647
// If a calendar is marked as 'transparent', it means we must
630648
// ignore it for free-busy purposes.
@@ -635,7 +653,6 @@ private function processCalendarAvailability(string $userId) {
635653
if (!empty($ctz)) {
636654
$vtimezoneObj = Reader::read($ctz);
637655
$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
638-
639656
// Destroy circular references so PHP can garbage collect the object.
640657
$vtimezoneObj->destroy();
641658
}
@@ -647,15 +664,18 @@ private function processCalendarAvailability(string $userId) {
647664
$dtEnd = new \DateTimeImmutable('+1 hours');
648665
$query->setTimerangeStart($dtStart);
649666
$query->setTimerangeEnd($dtEnd);
650-
$results = $this->calendarManager->searchForPrincipal($query);
651-
if(empty($results)) {
652-
return false;
667+
$calendarEvents = $this->calendarManager->searchForPrincipal($query);
668+
// @todo we can cache that
669+
$vavilability = $this->mapper->getAvailabilityFromPropertiesTable($userId);
670+
if(empty($vavilability) && empty($calendarEvents)) {
671+
// No availability settings and no calendar events, we can stop here
672+
return;
653673
}
654674

655675
$calendarObjects = new VCalendar();
656-
foreach ($results as $objectInfo) {
676+
foreach ($calendarEvents as $calendarEvent) {
657677
$vEvent = new VEvent($calendarObjects, 'VEVENT');
658-
foreach($objectInfo['objects'] as $component) {
678+
foreach($calendarEvent['objects'] as $component) {
659679
foreach ($component as $key => $value) {
660680
$vEvent->add($key, $value[0]);
661681
}
@@ -672,50 +692,56 @@ private function processCalendarAvailability(string $userId) {
672692
$generator->setBaseObject($vcalendar);
673693
$generator->setTimeZone($calendarTimeZone);
674694

675-
$vavilability = $this->mapper->getAvailabilityFromPropertiesTable($userId);
676695
if (!empty($vavilability)) {
677696
$generator->setVAvailability(
678697
Reader::read(
679698
$vavilability
680699
)
681700
);
682701
}
702+
// Generate the intersection of VAVILABILITY and all VEVENTS in all calendars
683703
$result = $generator->getResult();
684704

685-
// We have the intersection of VAVILABILITY and all VEVENTS in all calendars now
686-
// We only need to handle the first result.
687705
if (!isset($result->VFREEBUSY)) {
688-
return false;
706+
return;
689707
}
690708

691709
/** @var Component $freeBusyComponent */
692710
$freeBusyComponent = $result->VFREEBUSY;
693711
$freeBusyProperties = $freeBusyComponent->select('FREEBUSY');
694-
// If there is no Free-busy property at all, the time-range is empty and available
712+
// If there is no FreeBusy property, the time-range is empty and available
713+
// so there is no need to influence the current status
695714
if (count($freeBusyProperties) === 0) {
696-
return false;
697-
}
698-
699-
// If more than one Free-Busy property was returned, it means that an event
700-
// starts or ends inside this time-range, so it's not available and we return false
701-
if (count($freeBusyProperties) > 1) {
702-
return true;
715+
return;
703716
}
704717

705718
/** @var Property $freeBusyProperty */
706719
$freeBusyProperty = $freeBusyProperties[0];
707720
if (!$freeBusyProperty->offsetExists('FBTYPE')) {
708-
// If there is no FBTYPE, it means it's busy
709-
return true;
721+
// If there is no FBTYPE, it means it's busy from a regular event
722+
// @todo check if it would be better to set the timestamp to the start of the event
723+
$this->setUserStatus($userId, IUserStatus::AWAY, IUserStatus::MESSAGE_CALENDAR_BUSY, false);
724+
return;
710725
}
711726

727+
// If we can't deal with the FBTYPE (custom properties are a possibility)
728+
// we should ignore it and leave the current status
712729
$fbTypeParameter = $freeBusyProperty->offsetGet('FBTYPE');
713730
if (!($fbTypeParameter instanceof Parameter)) {
714-
return false;
731+
return;
732+
}
733+
$fbType = $fbTypeParameter->getValue();
734+
switch ($fbType) {
735+
case 'BUSY':
736+
$this->setUserStatus($userId, IUserStatus::AWAY, IUserStatus::MESSAGE_CALENDAR_BUSY, false);
737+
return;
738+
case 'BUSY-UNAVAILABLE':
739+
$this->setUserStatus($userId, IUserStatus::AWAY, IUserStatus::MESSAGE_AVAILABILITY, false);
740+
return;
741+
case 'BUSY-TENTATIVE':
742+
$this->setUserStatus($userId, IUserStatus::AWAY, IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE, false);
743+
return;
744+
default:
715745
}
716-
717-
$free = (strcasecmp($fbTypeParameter->getValue(), 'FREE') === 0);
718-
719-
return $free;
720746
}
721747
}

lib/public/UserStatus/IUserStatus.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ interface IUserStatus {
7575
*/
7676
public const MESSAGE_AVAILABILITY = 'availability';
7777

78+
/**
79+
* @var string
80+
* @since 28.0.0
81+
*/
82+
public const MESSAGE_CALENDAR_BUSY = 'busy';
83+
84+
/**
85+
* @var string
86+
* @since 28.0.0
87+
*/
88+
public const MESSAGE_CALENDAR_BUSY_TENTATIVE = 'busy-tentative';
89+
7890
/**
7991
* Get the user this status is connected to
8092
*

0 commit comments

Comments
 (0)