diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 751d2716..f1af9f18 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -1063,6 +1063,7 @@ $("#rcmfd_new_category").keypress(function(event) { $this->cleanup_event($event); $this->event_save_success($event, null, $action, true); + $this->talk_room_update($event); } $reload = $success && !empty($event['recurrence']) ? 2 : 1; @@ -1075,6 +1076,7 @@ $("#rcmfd_new_category").keypress(function(event) { else if ($success = $this->driver->edit_event($event)) { $this->cleanup_event($event); $this->event_save_success($event, $old, $action, $success); + $this->talk_room_update($event); } $reload = $success && (!empty($event['recurrence']) || !empty($event['_savemode']) || !empty($event['_fromcalendar'])) ? 2 : 1; @@ -3895,6 +3897,44 @@ $("#rcmfd_new_category").keypress(function(event) { } } + /** + * Update a Nextcould Talk room + */ + public function talk_room_update($event) + { + // If a room is assigned to the event... + if ( + ($talk_url = $this->rc->config->get('calendar_nextcloud_url')) + && isset($event['attendees']) + && !empty($event['location']) + && strpos($event['location'], unslashify($talk_url) . '/call/') === 0 + ) { + $participants = []; + $organizer = null; + + // ollect participants' and organizer's email addresses + foreach ($event['attendees'] as $attendee) { + if (!empty($attendee['email'])) { + if ($attendee['role'] == 'ORGANIZER') { + $organizer = $attendee['email']; + } + else if ($attendee['cutype'] == 'INDIVIDUAL') { + $participants[] = $attendee['email']; + } + } + } + + // If the event is owned by the current user update the room + if ($organizer && in_array($organizer, $this->get_user_emails())) { + require_once __DIR__ . '/lib/calendar_nextcloud_api.php'; + + $api = new calendar_nextcloud_api(); + + $api->talk_room_update($event['location'], $participants); + } + } + } + /** * Get a list of email addresses of the current user (from login and identities) */ diff --git a/plugins/calendar/lib/calendar_nextcloud_api.php b/plugins/calendar/lib/calendar_nextcloud_api.php index 90031ae7..fbd63688 100644 --- a/plugins/calendar/lib/calendar_nextcloud_api.php +++ b/plugins/calendar/lib/calendar_nextcloud_api.php @@ -23,6 +23,14 @@ class calendar_nextcloud_api { + const PARTICIPANT_OWNER = 1; + const PARTICIPANT_MODERATOR = 2; + const PARTICIPANT_USER = 3; + const PARTICIPANT_GUEST = 4; + const PARTICIPANT_PUBLIC = 5; + const PARTICIPANT_GUEST_MODERATOR = 6; + + /** * Make a request to the Nextcloud API * @@ -56,15 +64,24 @@ class calendar_nextcloud_api ]); if (!empty($params)) { - $request->addPostParameter($params); + if ($method == 'POST') { + $request->addPostParameter($params); + } + else { + $request->setUrl($url . '?' . http_build_query($params)); + } } + // rcube::console($method . ": " . (string) $request->getUrl()); + // Send the request $response = $request->send(); $body = $response->getBody(); $code = $response->getStatus(); + // rcube::console($code, $body); + if ($code < 400) { return json_decode($body, true); } @@ -88,9 +105,40 @@ class calendar_nextcloud_api return false; } + /** + * Find user by email address + */ + protected function findUserByEmail($email) + { + $email = strtolower($email); + $params = [ + 'search' => $email, + 'itemType' => 'call', + 'itemId' => ' ', + 'shareTypes' => [0, 1, 7, 4], + ]; + + // FIXME: Is this the only way to find a user by his email address? + $response = $this->request("core/autocomplete/get", 'GET', $params); + + if (!empty($response['ocs']['data'])) { + foreach ($response['ocs']['data'] as $user) { + // FIXME: This is the only field that contains email address? + // Note: A Nextcloud contact (the "emails" source) will have an email address in + // the 'id' attribute instead in 'shareWithDisplayNameUnique'. + // Another option might be to parse 'label' attribute + if (strtolower($user['shareWithDisplayNameUnique']) == $email) { + return $user; + } + } + } + } + /** * Create a Talk room * + * @param string $name Room name + * * @return string|false Room URL */ public function talk_room_create($name = '') @@ -105,10 +153,66 @@ class calendar_nextcloud_api $response = $this->request('apps/spreed/api/v4/room', 'POST', $params); if (is_array($response) && !empty($response['ocs']['data']['token'])) { + $token = $response['ocs']['data']['token']; $url = unslashify($rcmail->config->get('calendar_nextcloud_url')); - return $url . '/call/' . $response['ocs']['data']['token']; + + return $url . '/call/' . $token; } return false; } + + /** + * Update a Talk room + * + * @param string $room Room ID (or url) + * @param array $participants Room participants' email addresses (extept the owner) + * + * @return bool + */ + public function talk_room_update($room = '', $participants = []) + { + if (preg_match('|https?://|', $room)) { + $arr = explode('/', $room); + $room = $arr[count($arr) - 1]; + } + + // Get existing room participants + $response = $this->request("apps/spreed/api/v4/room/{$room}/participants", 'GET'); + + if ($response === false) { + return false; + } + + $attendees = []; + foreach ($response['ocs']['data'] as $attendee) { + if ($attendee['participantType'] != self::PARTICIPANT_OWNER) { + $attendees[$attendee['actorId']] = $attendee['attendeeId']; + } + } + + foreach ($participants as $email) { + if ($user = $this->findUserByEmail($email)) { + // Participant already exists, skip + // Note: We're dealing with 'users' source here for now, 'emails' source + // will have an email address in 'actorId' + if (isset($attendees[$user['id']])) { + unset($attendees[$user['id']]); + continue; + } + + // Register the participant + $params = ['newParticipant' => $user['id'], 'source' => $user['source']]; + $response = $this->request("apps/spreed/api/v4/room/{$room}/participants", 'POST', $params); + } + } + + // Remove participants not in the event anymore + foreach ($attendees as $attendeeId) { + $params = ['attendeeId' => $attendeeId]; + $response = $this->request("apps/spreed/api/v4/room/{$room}/attendees", 'DELETE', $params); + } + + return true; + } }