2017-10-31 12:06:01 -04:00
< ? php
2021-09-27 08:58:16 -04:00
declare ( strict_types = 1 );
2017-10-31 12:06:01 -04:00
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2017-10-31 12:06:01 -04:00
*/
namespace OC\Calendar ;
2025-01-08 15:08:15 -05:00
use DateTimeInterface ;
2021-09-27 08:58:16 -04:00
use OC\AppFramework\Bootstrap\Coordinator ;
2025-01-08 15:08:15 -05:00
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin ;
use OCA\DAV\ServerFactory ;
2022-06-23 16:17:53 -04:00
use OCP\AppFramework\Utility\ITimeFactory ;
use OCP\Calendar\Exceptions\CalendarException ;
2017-10-31 12:06:01 -04:00
use OCP\Calendar\ICalendar ;
2024-12-16 15:20:50 -05:00
use OCP\Calendar\ICalendarEventBuilder ;
2024-09-07 18:28:50 -04:00
use OCP\Calendar\ICalendarIsShared ;
use OCP\Calendar\ICalendarIsWritable ;
2021-09-27 08:58:16 -04:00
use OCP\Calendar\ICalendarProvider ;
use OCP\Calendar\ICalendarQuery ;
2022-06-23 16:17:53 -04:00
use OCP\Calendar\ICreateFromString ;
2022-10-31 06:16:45 -04:00
use OCP\Calendar\IHandleImipMessage ;
2021-09-27 08:58:16 -04:00
use OCP\Calendar\IManager ;
2025-01-08 15:08:15 -05:00
use OCP\IUser ;
use OCP\IUserManager ;
2024-12-16 15:20:50 -05:00
use OCP\Security\ISecureRandom ;
2021-10-14 05:12:55 -04:00
use Psr\Container\ContainerInterface ;
use Psr\Log\LoggerInterface ;
2025-01-08 15:08:15 -05:00
use Sabre\HTTP\Request ;
use Sabre\HTTP\Response ;
2022-06-23 16:17:53 -04:00
use Sabre\VObject\Component\VCalendar ;
use Sabre\VObject\Component\VEvent ;
2025-01-08 15:08:15 -05:00
use Sabre\VObject\Component\VFreeBusy ;
2025-05-12 14:43:35 -04:00
use Sabre\VObject\ParseException ;
2022-06-23 16:17:53 -04:00
use Sabre\VObject\Property\VCard\DateTime ;
use Sabre\VObject\Reader ;
2021-10-14 05:12:55 -04:00
use Throwable ;
use function array_map ;
use function array_merge ;
2017-10-31 12:06:01 -04:00
2021-09-27 08:58:16 -04:00
class Manager implements IManager {
2017-10-31 12:06:01 -04:00
/**
* @ var ICalendar [] holds all registered calendars
*/
2023-06-25 06:28:30 -04:00
private array $calendars = [];
2017-10-31 12:06:01 -04:00
/**
* @ var \Closure [] to call to load / register calendar providers
*/
2023-06-25 06:28:30 -04:00
private array $calendarLoaders = [];
public function __construct (
private Coordinator $coordinator ,
private ContainerInterface $container ,
private LoggerInterface $logger ,
private ITimeFactory $timeFactory ,
2024-12-16 15:20:50 -05:00
private ISecureRandom $random ,
2025-01-08 15:08:15 -05:00
private IUserManager $userManager ,
private ServerFactory $serverFactory ,
2023-06-25 06:28:30 -04:00
) {
2021-09-27 08:58:16 -04:00
}
2017-10-31 12:06:01 -04:00
/**
* This function is used to search and find objects within the user ' s calendars .
* In case $pattern is empty all events / journals / todos will be returned .
*
* @ param string $pattern which should match within the $searchProperties
* @ param array $searchProperties defines the properties within the query pattern should match
* @ param array $options - optional parameters :
* [ 'timerange' => [ 'start' => new DateTime ( ... ), 'end' => new DateTime ( ... )]]
* @ param integer | null $limit - limit number of search results
* @ param integer | null $offset - offset for paging of search results
* @ return array an array of events / journals / todos which are arrays of arrays of key - value - pairs
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function search (
$pattern ,
array $searchProperties = [],
array $options = [],
$limit = null ,
$offset = null ,
) : array {
2017-10-31 12:06:01 -04:00
$this -> loadCalendars ();
$result = [];
foreach ( $this -> calendars as $calendar ) {
$r = $calendar -> search ( $pattern , $searchProperties , $options , $limit , $offset );
foreach ( $r as $o ) {
$o [ 'calendar-key' ] = $calendar -> getKey ();
$result [] = $o ;
}
}
return $result ;
}
/**
* Check if calendars are available
*
* @ return bool true if enabled , false if not
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function isEnabled () : bool {
2017-10-31 12:06:01 -04:00
return ! empty ( $this -> calendars ) || ! empty ( $this -> calendarLoaders );
}
/**
* Registers a calendar
*
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function registerCalendar ( ICalendar $calendar ) : void {
2017-10-31 12:06:01 -04:00
$this -> calendars [ $calendar -> getKey ()] = $calendar ;
}
/**
* Unregisters a calendar
*
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function unregisterCalendar ( ICalendar $calendar ) : void {
2017-10-31 12:06:01 -04:00
unset ( $this -> calendars [ $calendar -> getKey ()]);
}
/**
* In order to improve lazy loading a closure can be registered which will be called in case
* calendars are actually requested
*
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function register ( \Closure $callable ) : void {
2017-10-31 12:06:01 -04:00
$this -> calendarLoaders [] = $callable ;
}
/**
* @ return ICalendar []
2023-06-25 06:28:30 -04:00
*
2017-10-31 12:06:01 -04:00
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function getCalendars () : array {
2017-10-31 12:06:01 -04:00
$this -> loadCalendars ();
return array_values ( $this -> calendars );
}
/**
* removes all registered calendar instances
2023-06-25 06:28:30 -04:00
*
2017-10-31 12:06:01 -04:00
* @ since 13.0 . 0
*/
2023-06-25 06:28:30 -04:00
public function clear () : void {
2017-10-31 12:06:01 -04:00
$this -> calendars = [];
$this -> calendarLoaders = [];
}
/**
* loads all calendars
*/
2023-06-25 06:28:30 -04:00
private function loadCalendars () : void {
2017-10-31 12:06:01 -04:00
foreach ( $this -> calendarLoaders as $callable ) {
$callable ( $this );
}
$this -> calendarLoaders = [];
}
2021-09-27 08:58:16 -04:00
2022-06-23 16:17:53 -04:00
/**
* @ return ICreateFromString []
*/
2021-11-03 07:19:47 -04:00
public function getCalendarsForPrincipal ( string $principalUri , array $calendarUris = []) : array {
2021-09-27 08:58:16 -04:00
$context = $this -> coordinator -> getRegistrationContext ();
if ( $context === null ) {
return [];
}
2021-11-03 07:19:47 -04:00
return array_merge (
... array_map ( function ( $registration ) use ( $principalUri , $calendarUris ) {
2021-10-14 05:12:55 -04:00
try {
/** @var ICalendarProvider $provider */
$provider = $this -> container -> get ( $registration -> getService ());
} catch ( Throwable $e ) {
$this -> logger -> error ( 'Could not load calendar provider ' . $registration -> getService () . ': ' . $e -> getMessage (), [
'exception' => $e ,
]);
return [];
}
2021-11-03 07:19:47 -04:00
return $provider -> getCalendars ( $principalUri , $calendarUris );
2021-10-14 05:12:55 -04:00
}, $context -> getCalendarProviders ())
);
2021-11-03 07:19:47 -04:00
}
public function searchForPrincipal ( ICalendarQuery $query ) : array {
/** @var CalendarQuery $query */
$calendars = $this -> getCalendarsForPrincipal (
$query -> getPrincipalUri (),
$query -> getCalendarUris (),
);
2021-09-27 08:58:16 -04:00
$results = [];
foreach ( $calendars as $calendar ) {
$r = $calendar -> search (
$query -> getSearchPattern () ? ? '' ,
$query -> getSearchProperties (),
$query -> getOptions (),
$query -> getLimit (),
$query -> getOffset ()
);
foreach ( $r as $o ) {
$o [ 'calendar-key' ] = $calendar -> getKey ();
2024-05-22 03:10:24 -04:00
$o [ 'calendar-uri' ] = $calendar -> getUri ();
2021-09-27 08:58:16 -04:00
$results [] = $o ;
}
}
return $results ;
}
public function newQuery ( string $principalUri ) : ICalendarQuery {
return new CalendarQuery ( $principalUri );
}
2022-06-23 16:17:53 -04:00
2024-09-07 18:28:50 -04:00
/**
* @ since 31.0 . 0
* @ throws \OCP\DB\Exception
*/
public function handleIMipRequest (
string $principalUri ,
string $sender ,
string $recipient ,
string $calendarData ,
) : bool {
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
$userCalendars = $this -> getCalendarsForPrincipal ( $principalUri );
if ( empty ( $userCalendars )) {
$this -> logger -> warning ( 'iMip message could not be processed because user has no calendars' );
return false ;
}
2025-05-12 14:43:35 -04:00
try {
/** @var VCalendar $vObject|null */
$calendarObject = Reader :: read ( $calendarData );
} catch ( ParseException $e ) {
$this -> logger -> error ( 'iMip message could not be processed because an error occurred while parsing the iMip message' , [ 'exception' => $e ]);
return false ;
}
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
if ( ! isset ( $calendarObject -> METHOD ) || $calendarObject -> METHOD -> getValue () !== 'REQUEST' ) {
$this -> logger -> warning ( 'iMip message contains an incorrect or invalid method' );
return false ;
}
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
if ( ! isset ( $calendarObject -> VEVENT )) {
$this -> logger -> warning ( 'iMip message contains no event' );
return false ;
}
2025-05-12 14:43:35 -04:00
/** @var VEvent|null $vEvent */
2024-09-07 18:28:50 -04:00
$eventObject = $calendarObject -> VEVENT ;
if ( ! isset ( $eventObject -> UID )) {
$this -> logger -> warning ( 'iMip message event dose not contains a UID' );
return false ;
}
2024-12-16 15:20:50 -05:00
2025-05-12 14:43:35 -04:00
if ( ! isset ( $eventObject -> ORGANIZER )) {
$this -> logger -> warning ( 'iMip message event dose not contains an organizer' );
return false ;
}
2024-09-07 18:28:50 -04:00
if ( ! isset ( $eventObject -> ATTENDEE )) {
$this -> logger -> warning ( 'iMip message event dose not contains any attendees' );
return false ;
}
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
foreach ( $eventObject -> ATTENDEE as $entry ) {
$address = trim ( str_replace ( 'mailto:' , '' , $entry -> getValue ()));
if ( $address === $recipient ) {
$attendee = $address ;
break ;
}
}
if ( ! isset ( $attendee )) {
$this -> logger -> warning ( 'iMip message event does not contain a attendee that matches the recipient' );
return false ;
}
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
foreach ( $userCalendars as $calendar ) {
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
if ( ! $calendar instanceof ICalendarIsWritable && ! $calendar instanceof ICalendarIsShared ) {
continue ;
}
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
if ( $calendar -> isDeleted () || ! $calendar -> isWritable () || $calendar -> isShared ()) {
continue ;
}
2024-12-16 15:20:50 -05:00
2024-09-07 18:28:50 -04:00
if ( ! empty ( $calendar -> search ( $recipient , [ 'ATTENDEE' ], [ 'uid' => $eventObject -> UID -> getValue ()]))) {
try {
if ( $calendar instanceof IHandleImipMessage ) {
$calendar -> handleIMipMessage ( '' , $calendarData );
}
return true ;
} catch ( CalendarException $e ) {
$this -> logger -> error ( 'An error occurred while processing the iMip message event' , [ 'exception' => $e ]);
return false ;
}
}
}
2024-12-16 15:20:50 -05:00
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because no corresponding event was found in any calendar' );
2024-09-07 18:28:50 -04:00
return false ;
}
2022-06-23 16:17:53 -04:00
/**
* @ throws \OCP\DB\Exception
*/
2023-06-25 06:28:30 -04:00
public function handleIMipReply (
string $principalUri ,
string $sender ,
string $recipient ,
string $calendarData ,
) : bool {
2025-05-12 14:43:35 -04:00
$calendars = $this -> getCalendarsForPrincipal ( $principalUri );
if ( empty ( $calendars )) {
$this -> logger -> warning ( 'iMip message could not be processed because user has no calendars' );
return false ;
}
try {
/** @var VCalendar $vObject|null */
$vObject = Reader :: read ( $calendarData );
} catch ( ParseException $e ) {
$this -> logger -> error ( 'iMip message could not be processed because an error occurred while parsing the iMip message' , [ 'exception' => $e ]);
return false ;
}
2023-06-30 05:01:22 -04:00
if ( $vObject === null ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message contains an invalid calendar object' );
return false ;
}
if ( ! isset ( $vObject -> METHOD ) || $vObject -> METHOD -> getValue () !== 'REPLY' ) {
$this -> logger -> warning ( 'iMip message contains an incorrect or invalid method' );
return false ;
}
if ( ! isset ( $vObject -> VEVENT )) {
$this -> logger -> warning ( 'iMip message contains no event' );
2023-06-30 05:01:22 -04:00
return false ;
}
/** @var VEvent|null $vEvent */
2025-05-12 14:43:35 -04:00
$vEvent = $vObject -> VEVENT ;
if ( ! isset ( $vEvent -> UID )) {
$this -> logger -> warning ( 'iMip message event dose not contains a UID' );
return false ;
}
2022-06-23 16:17:53 -04:00
2025-05-12 14:43:35 -04:00
if ( ! isset ( $vEvent -> ORGANIZER )) {
$this -> logger -> warning ( 'iMip message event dose not contains an organizer' );
2023-06-30 05:01:22 -04:00
return false ;
}
2025-05-12 14:43:35 -04:00
if ( ! isset ( $vEvent -> ATTENDEE )) {
$this -> logger -> warning ( 'iMip message event dose not contains any attendees' );
2022-06-23 16:17:53 -04:00
return false ;
}
// check if mail recipient and organizer are one and the same
$organizer = substr ( $vEvent -> { 'ORGANIZER' } -> getValue (), 7 );
if ( strcasecmp ( $recipient , $organizer ) !== 0 ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because recipient and ORGANIZER must be identical' );
2022-06-23 16:17:53 -04:00
return false ;
}
//check if the event is in the future
/** @var DateTime $eventTime */
$eventTime = $vEvent -> { 'DTSTART' };
if ( $eventTime -> getDateTime () -> getTimeStamp () < $this -> timeFactory -> getTime ()) { // this might cause issues with recurrences
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because the event is in the past' );
2022-06-23 16:17:53 -04:00
return false ;
}
$found = null ;
// if the attendee has been found in at least one calendar event with the UID of the iMIP event
// we process it.
// Benefit: no attendee lost
// Drawback: attendees that have been deleted will still be able to update their partstat
foreach ( $calendars as $calendar ) {
// We should not search in writable calendars
2022-10-31 06:16:45 -04:00
if ( $calendar instanceof IHandleImipMessage ) {
2022-06-23 16:17:53 -04:00
$o = $calendar -> search ( $sender , [ 'ATTENDEE' ], [ 'uid' => $vEvent -> { 'UID' } -> getValue ()]);
if ( ! empty ( $o )) {
$found = $calendar ;
$name = $o [ 0 ][ 'uri' ];
break ;
}
}
}
if ( empty ( $found )) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because no corresponding event was found in any calendar ' . $principalUri . 'and UID' . $vEvent -> { 'UID' } -> getValue ());
2022-06-23 16:17:53 -04:00
return false ;
}
try {
$found -> handleIMipMessage ( $name , $calendarData ); // sabre will handle the scheduling behind the scenes
} catch ( CalendarException $e ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> error ( 'An error occurred while processing the iMip message event' , [ 'exception' => $e ]);
2022-06-23 16:17:53 -04:00
return false ;
}
return true ;
}
/**
* @ since 25.0 . 0
* @ throws \OCP\DB\Exception
*/
2023-06-25 06:28:30 -04:00
public function handleIMipCancel (
string $principalUri ,
string $sender ,
? string $replyTo ,
string $recipient ,
string $calendarData ,
) : bool {
2025-05-12 14:43:35 -04:00
$calendars = $this -> getCalendarsForPrincipal ( $principalUri );
if ( empty ( $calendars )) {
$this -> logger -> warning ( 'iMip message could not be processed because user has no calendars' );
return false ;
}
try {
/** @var VCalendar $vObject|null */
$vObject = Reader :: read ( $calendarData );
} catch ( ParseException $e ) {
$this -> logger -> error ( 'iMip message could not be processed because an error occurred while parsing the iMip message' , [ 'exception' => $e ]);
return false ;
}
2023-06-30 05:01:22 -04:00
if ( $vObject === null ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message contains an invalid calendar object' );
return false ;
}
if ( ! isset ( $vObject -> METHOD ) || $vObject -> METHOD -> getValue () !== 'CANCEL' ) {
$this -> logger -> warning ( 'iMip message contains an incorrect or invalid method' );
return false ;
}
if ( ! isset ( $vObject -> VEVENT )) {
$this -> logger -> warning ( 'iMip message contains no event' );
2023-06-30 05:01:22 -04:00
return false ;
}
/** @var VEvent|null $vEvent */
2022-06-23 16:17:53 -04:00
$vEvent = $vObject -> { 'VEVENT' };
2025-05-12 14:43:35 -04:00
if ( ! isset ( $vEvent -> UID )) {
$this -> logger -> warning ( 'iMip message event dose not contains a UID' );
return false ;
}
if ( ! isset ( $vEvent -> ORGANIZER )) {
$this -> logger -> warning ( 'iMip message event dose not contains an organizer' );
2023-06-30 05:01:22 -04:00
return false ;
}
2025-05-12 14:43:35 -04:00
if ( ! isset ( $vEvent -> ATTENDEE )) {
$this -> logger -> warning ( 'iMip message event dose not contains any attendees' );
2022-06-23 16:17:53 -04:00
return false ;
}
$attendee = substr ( $vEvent -> { 'ATTENDEE' } -> getValue (), 7 );
if ( strcasecmp ( $recipient , $attendee ) !== 0 ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because recipient must be an ATTENDEE of this event' );
2022-06-23 16:17:53 -04:00
return false ;
}
// Thirdly, we need to compare the email address the CANCEL is coming from (in Mail)
// or the Reply- To Address submitted with the CANCEL email
// to the email address in the ORGANIZER.
// We don't want to accept a CANCEL request from just anyone
$organizer = substr ( $vEvent -> { 'ORGANIZER' } -> getValue (), 7 );
2022-09-07 20:26:10 -04:00
$isNotOrganizer = ( $replyTo !== null ) ? ( strcasecmp ( $sender , $organizer ) !== 0 && strcasecmp ( $replyTo , $organizer ) !== 0 ) : ( strcasecmp ( $sender , $organizer ) !== 0 );
if ( $isNotOrganizer ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because sender must be the ORGANIZER of this event' );
2022-06-23 16:17:53 -04:00
return false ;
}
2022-09-07 20:26:10 -04:00
//check if the event is in the future
/** @var DateTime $eventTime */
$eventTime = $vEvent -> { 'DTSTART' };
if ( $eventTime -> getDateTime () -> getTimeStamp () < $this -> timeFactory -> getTime ()) { // this might cause issues with recurrences
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because the event is in the past' );
2022-09-07 20:26:10 -04:00
return false ;
}
2022-06-23 16:17:53 -04:00
$found = null ;
// if the attendee has been found in at least one calendar event with the UID of the iMIP event
// we process it.
// Benefit: no attendee lost
// Drawback: attendees that have been deleted will still be able to update their partstat
foreach ( $calendars as $calendar ) {
// We should not search in writable calendars
2022-10-31 06:16:45 -04:00
if ( $calendar instanceof IHandleImipMessage ) {
2022-06-23 16:17:53 -04:00
$o = $calendar -> search ( $recipient , [ 'ATTENDEE' ], [ 'uid' => $vEvent -> { 'UID' } -> getValue ()]);
if ( ! empty ( $o )) {
$found = $calendar ;
$name = $o [ 0 ][ 'uri' ];
break ;
}
}
}
if ( empty ( $found )) {
2025-05-12 14:43:35 -04:00
$this -> logger -> warning ( 'iMip message event could not be processed because no corresponding event was found in any calendar ' . $principalUri . 'and UID' . $vEvent -> { 'UID' } -> getValue ());
return false ;
2022-06-23 16:17:53 -04:00
}
try {
$found -> handleIMipMessage ( $name , $calendarData ); // sabre will handle the scheduling behind the scenes
return true ;
} catch ( CalendarException $e ) {
2025-05-12 14:43:35 -04:00
$this -> logger -> error ( 'An error occurred while processing the iMip message event' , [ 'exception' => $e ]);
2022-06-23 16:17:53 -04:00
return false ;
}
}
2024-12-16 15:20:50 -05:00
public function createEventBuilder () : ICalendarEventBuilder {
$uid = $this -> random -> generate ( 32 , ISecureRandom :: CHAR_ALPHANUMERIC );
return new CalendarEventBuilder ( $uid , $this -> timeFactory );
}
2025-01-08 15:08:15 -05:00
public function checkAvailability (
DateTimeInterface $start ,
DateTimeInterface $end ,
IUser $organizer ,
array $attendees ,
) : array {
$organizerMailto = 'mailto:' . $organizer -> getEMailAddress ();
$request = new VCalendar ();
$request -> METHOD = 'REQUEST' ;
$request -> add ( 'VFREEBUSY' , [
'DTSTART' => $start ,
'DTEND' => $end ,
'ORGANIZER' => $organizerMailto ,
'ATTENDEE' => $organizerMailto ,
]);
$mailtoLen = strlen ( 'mailto:' );
foreach ( $attendees as $attendee ) {
if ( str_starts_with ( $attendee , 'mailto:' )) {
$attendee = substr ( $attendee , $mailtoLen );
}
$attendeeUsers = $this -> userManager -> getByEmail ( $attendee );
if ( $attendeeUsers === []) {
continue ;
}
$request -> VFREEBUSY -> add ( 'ATTENDEE' , " mailto: $attendee " );
}
$organizerUid = $organizer -> getUID ();
$server = $this -> serverFactory -> createAttendeeAvailabilityServer ();
/** @var CustomPrincipalPlugin $plugin */
$plugin = $server -> getPlugin ( 'auth' );
$plugin -> setCurrentPrincipal ( " principals/users/ $organizerUid " );
$request = new Request (
'POST' ,
" /calendars/ $organizerUid /outbox/ " ,
[
'Content-Type' => 'text/calendar' ,
'Depth' => 0 ,
],
$request -> serialize (),
);
$response = new Response ();
$server -> invokeMethod ( $request , $response , false );
$xmlService = new \Sabre\Xml\Service ();
$xmlService -> elementMap = [
'{urn:ietf:params:xml:ns:caldav}response' => 'Sabre\Xml\Deserializer\keyValue' ,
'{urn:ietf:params:xml:ns:caldav}recipient' => 'Sabre\Xml\Deserializer\keyValue' ,
];
$parsedResponse = $xmlService -> parse ( $response -> getBodyAsString ());
$result = [];
foreach ( $parsedResponse as $freeBusyResponse ) {
$freeBusyResponse = $freeBusyResponse [ 'value' ];
if ( $freeBusyResponse [ '{urn:ietf:params:xml:ns:caldav}request-status' ] !== '2.0;Success' ) {
continue ;
}
$freeBusyResponseData = \Sabre\VObject\Reader :: read (
$freeBusyResponse [ '{urn:ietf:params:xml:ns:caldav}calendar-data' ]
);
$attendee = substr (
$freeBusyResponse [ '{urn:ietf:params:xml:ns:caldav}recipient' ][ '{DAV:}href' ],
$mailtoLen ,
);
$vFreeBusy = $freeBusyResponseData -> VFREEBUSY ;
if ( ! ( $vFreeBusy instanceof VFreeBusy )) {
continue ;
}
// TODO: actually check values of FREEBUSY properties to find a free slot
$result [] = new AvailabilityResult ( $attendee , $vFreeBusy -> isFree ( $start , $end ));
}
return $result ;
}
2017-10-31 12:06:01 -04:00
}