123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- <?php
- /*
- * *
- * * Dav App
- * *
- * * @copyright 2023 Anna Larch <anna.larch@gmx.net>
- * *
- * * @author Anna Larch <anna.larch@gmx.net>
- * *
- * * This library is free software; you can redistribute it and/or
- * * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * * License as published by the Free Software Foundation; either
- * * version 3 of the License, or any later version.
- * *
- * * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
- * *
- *
- */
-
- 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;
-
- 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 OCP\AppFramework\Utility\ITimeFactory;
- use OCP\Calendar\IManager;
- use OCP\IL10N;
- use OCP\IUser as User;
- use OCP\UserStatus\IUserStatus;
- 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;
- use Sabre\VObject\Reader;
-
- class StatusService {
- public function __construct(private ITimeFactory $timeFactory,
- private IManager $calendarManager,
- private InvitationResponseServer $server,
- private IL10N $l10n,
- private FreeBusyGenerator $generator) {
- }
-
- public function processCalendarAvailability(User $user, ?string $availability): ?Status {
- $userId = $user->getUID();
- $email = $user->getEMailAddress();
- if($email === null) {
- return null;
- }
-
- $server = $this->server->getServer();
-
- /** @var SchedulePlugin $schedulingPlugin */
- $schedulingPlugin = $server->getPlugin('caldav-schedule');
- $caldavNS = '{'.$schedulingPlugin::NS_CALDAV.'}';
-
- /** @var AclPlugin $aclPlugin */
- $aclPlugin = $server->getPlugin('acl');
- if ('mailto:' === substr($email, 0, 7)) {
- $email = substr($email, 7);
- }
-
- $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',
- ]
- );
-
- if (!count($result) || !isset($result[0][200][$caldavNS.'schedule-inbox-URL'])) {
- return null;
- }
-
- $inboxUrl = $result[0][200][$caldavNS.'schedule-inbox-URL']->getHref();
-
- // Do we have permission?
- try {
- $aclPlugin->checkPrivileges($inboxUrl, $caldavNS.'schedule-query-freebusy');
- } catch (NeedPrivileges | NotAuthenticated $exception) {
- return null;
- }
-
- $now = $this->timeFactory->now();
- $calendarTimeZone = $now->getTimezone();
- $calendars = $this->calendarManager->getCalendarsForPrincipal('principals/users/' . $userId);
- if(empty($calendars)) {
- return null;
- }
-
- $query = $this->calendarManager->newQuery('principals/users/' . $userId);
- foreach ($calendars as $calendarObject) {
- // We can only work with a calendar if it exposes its scheduling information
- if (!$calendarObject instanceof CalendarImpl) {
- continue;
- }
-
- $sct = $calendarObject->getSchedulingTransparency();
- if ($sct !== null && ScheduleCalendarTransp::TRANSPARENT == strtolower($sct->getValue())) {
- // If a calendar is marked as 'transparent', it means we must
- // 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'));
-
- // 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);
- }
-
- // @todo we can cache that
- if(empty($availability) && empty($calendarEvents)) {
- // No availability settings and no calendar events, we can stop here
- 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);
-
- if (!empty($availability)) {
- $this->generator->setVAvailability(
- Reader::read(
- $availability
- )
- );
- }
- // Generate the intersection of VAVILABILITY and all VEVENTS in all calendars
- $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
- // so set the status to online as otherwise we will never recover from a BUSY status
- if (count($freeBusyProperties) === 0) {
- return new Status(IUserStatus::ONLINE);
- }
-
- /** @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) {
- case 'BUSY':
- return new Status(IUserStatus::BUSY, IUserStatus::MESSAGE_CALENDAR_BUSY, $this->l10n->t('In a meeting'));
- case 'BUSY-UNAVAILABLE':
- return new Status(IUserStatus::AWAY, IUserStatus::MESSAGE_AVAILABILITY);
- case 'BUSY-TENTATIVE':
- return new Status(IUserStatus::AWAY, IUserStatus::MESSAGE_CALENDAR_BUSY_TENTATIVE);
- default:
- return null;
- }
- }
- }
|