21 public const ID =
'zoom';
22 private const CONTROLLER_URL =
'https://www.bitrix24.com/controller';
23 private const LOGIN_PREFIX =
'zoom_';
38 [
'zoom_client_id', Loc::getMessage(
'SOCSERV_ZOOM_CLIENT_ID'),
'', [
'text', 40]],
39 [
'zoom_client_secret', Loc::getMessage(
'SOCSERV_ZOOM_CLIENT_SECRET'),
'', [
'text', 40]],
41 'note' => Loc::getMessage(
42 'SOCSERV_ZOOM_SETT_NOTE_2',
55 if (!$this->entityOAuth)
62 $this->entityOAuth->setCode(
$code);
78 : '<a href="javascript:void(0)
" onclick="BX.util.popup(\
'' .
htmlspecialcharsbx(CUtil::JSEscape(
$url)) .
'\', 700, 700)
" class="bx-ss-button zoom-button
"></a><span class="bx-spacer
"></span><span>' . $phrase . '</span>';
81 public function GetOnClickJs($arParams): string
83 $url = $this->getUrl($arParams);
84 return "BX.util.popup(
'" . CUtil::JSEscape($url) . "', 460, 420)
";
87 public function getUrl($arParams): string
91 if (defined('BX24_HOST_NAME') && IsModuleInstalled('bitrix24'))
93 $redirect_uri = static::CONTROLLER_URL . '/redirect.php';
94 $backurl = $APPLICATION->GetCurPageParam('', ['logout', 'auth_service_error', 'auth_service_id', 'backurl']);
95 $state = $this->getEntityOAuth()->GetRedirectURI() .
96 urlencode('?state=' . JWT::urlsafeB64Encode('backurl=' . $backurl . '&check_key=' . \CSocServAuthManager::getUniqueKey()));
100 $state = 'site_id=' . SITE_ID . '&backurl=' .
101 urlencode($APPLICATION->GetCurPageParam('check_key=' . \CSocServAuthManager::getUniqueKey(), ['logout', 'auth_service_error', 'auth_service_id', 'backurl'])) .
102 (isset($arParams['BACKURL']) ? '&redirect_url=' . urlencode($arParams['BACKURL']) : '');
104 $redirect_uri = $this->getEntityOAuth()->GetRedirectURI();
107 return $this->getEntityOAuth()->GetAuthUrl($redirect_uri, $state);
110 public function addScope($scope): CZoomInterface
112 return $this->getEntityOAuth()->addScope($scope);
115 public function prepareUser($arUser, $short = false): array
117 $entityOAuth = $this->getEntityOAuth();
119 'EXTERNAL_AUTH_ID' => self::ID,
120 'XML_ID' => $arUser["email
"],
121 'LOGIN' => self::LOGIN_PREFIX.$arUser["email
"],
122 'EMAIL' => $arUser["email
"],
123 'NAME' => $arUser["first_name
"],
124 'LAST_NAME' => $arUser["last_name
"],
125 'OATOKEN' => $entityOAuth->getToken(),
126 'OATOKEN_EXPIRES' => $entityOAuth->getAccessTokenExpires(),
129 if (!$short && isset($arUser['pic_url']))
131 $picture_url = $arUser['pic_url'];
132 $temp_path = CFile::GetTempName('', 'picture.jpg');
134 $ob = new HttpClient(array(
137 $ob->download($picture_url, $temp_path);
139 $arPic = CFile::MakeFileArray($temp_path);
142 $arFields["PERSONAL_PHOTO
"] = $arPic;
148 $arFields["SITE_ID"] = SITE_ID;
154 public static function CheckUniqueKey($bUnset = true): bool
158 if (isset($_REQUEST['state']))
160 parse_str(urldecode(JWT::urlsafeB64Decode($_REQUEST['state'])), $arState);
162 if (isset($arState['backurl']))
164 InitURLParam($arState['backurl']);
168 if (!isset($_REQUEST['check_key']) && isset($_REQUEST['backurl']))
170 InitURLParam($_REQUEST['backurl']);
174 if (isset($_REQUEST['check_key']))
176 $checkKey = $_REQUEST['check_key'];
178 elseif (isset($arState['check_key']))
180 $checkKey = $arState['check_key'];
183 if ($_SESSION['UNIQUE_KEY'] !== '' && $checkKey !== '' && ($checkKey === $_SESSION['UNIQUE_KEY']))
187 unset($_SESSION['UNIQUE_KEY']);
195 public function Authorize(): void
198 $APPLICATION->RestartBuffer();
200 $authError = SOCSERV_AUTHORISATION_ERROR;
203 isset($_REQUEST['code']) && $_REQUEST['code'] <> ''
204 && self::CheckUniqueKey()
207 if (defined('BX24_HOST_NAME') && IsModuleInstalled('bitrix24'))
209 $redirect_uri = static::CONTROLLER_URL . '/redirect.php';
213 $redirect_uri = $this->getEntityOAuth()->GetRedirectURI();
216 $entityOAuth = $this->getEntityOAuth($_REQUEST['code']);
217 if ($entityOAuth->GetAccessToken($redirect_uri) !== false)
219 $arUser = $entityOAuth->getCurrentUser();
220 if (is_array($arUser) && isset($arUser["email
"]))
222 $arFields = $this->prepareUser($arUser);
223 $authError = $this->AuthorizeUser($arFields);
226 'externalUserId' => $arUser['id'],
227 'externalAccountId' => $arUser['account_id'],
228 'socServLogin' => $arFields['LOGIN'],
230 $zc = new \Bitrix\SocialServices\Integration\Zoom\ZoomController();
231 $zc->registerZoomUser($userData);
236 $bSuccess = $authError === true;
238 $url = ($APPLICATION->GetCurDir() == "/login/
") ? "" : $APPLICATION->GetCurDir();
239 $aRemove = array("logout
", "auth_service_error
", "auth_service_id
", "code
", "error_reason
", "error
", "error_description
", "check_key
", "current_fieldset
");
241 if (isset($_REQUEST["state
"]))
245 $decodedState = urldecode(JWT::urlsafeB64Decode($_REQUEST["state
"]));
246 parse_str($decodedState, $arState);
248 if (isset($arState['backurl']) || isset($arState['redirect_url']))
250 $url = !empty($arState['redirect_url']) ? $arState['redirect_url'] : $arState['backurl'];
251 if (substr($url, 0, 1) !== "#
")
253 $parseUrl = parse_url($url);
255 $urlPath = $parseUrl["path"];
256 $arUrlQuery = explode('&', $parseUrl["query
"]);
258 foreach ($arUrlQuery as $key => $value)
260 foreach ($aRemove as $param)
262 if (strpos($value, $param . "=
") === 0)
264 unset($arUrlQuery[$key]);
270 $url = (!empty($arUrlQuery)) ? $urlPath . '?' . implode("&
", $arUrlQuery) : $urlPath;
275 if ($authError === SOCSERV_REGISTRATION_DENY)
277 $url = (preg_match("/\?/
", $url)) ? $url . '&' : $url . '?';
278 $url .= 'auth_service_id=' . self::ID . '&auth_service_error=' . $authError;
280 elseif ($bSuccess !== true)
282 $url = (isset($urlPath)) ? $urlPath . '?auth_service_id=' . self::ID . '&auth_service_error=' . $authError : $GLOBALS['APPLICATION']->GetCurPageParam(('auth_service_id=' . self::ID . '&auth_service_error=' . $authError), $aRemove);
285 if (CModule::IncludeModule("socialnetwork
") && strpos($url, "current_fieldset=
") === false)
287 $url .= ((strpos($url, "?
") === false) ? '?' : '&') . "current_fieldset=SOCSERV
";
292 window.opener.location.reload();
296 CMain::FinalActions();
299 public function setUser($userId)
301 $this->getEntityOAuth()->setUser($userId);
304 public function getStorageToken()
307 $userId = (int)$this->userId;
310 $dbSocservUser = \Bitrix\Socialservices\UserTable::getList([
311 'filter' => ['=USER_ID' => $userId, '=EXTERNAL_AUTH_ID' => static::ID],
312 'select' => ['OATOKEN', 'REFRESH_TOKEN', 'OATOKEN_EXPIRES']
314 if ($arOauth = $dbSocservUser->fetch())
316 $accessToken = $arOauth['OATOKEN'];
318 if (empty($accessToken) || (((int)$arOauth['OATOKEN_EXPIRES'] > 0) && ((int)($arOauth['OATOKEN_EXPIRES'] < (int)time()))))
320 if (isset($arOauth['REFRESH_TOKEN']))
322 $this->getEntityOAuth()->getNewAccessToken($arOauth['REFRESH_TOKEN'], $userId, true);
325 if (($accessToken = $this->getEntityOAuth()->getToken()) === false)
336 public function createConference($params)
338 $result = new Result();
339 $conferenceData = null;
340 if (!$this->getEntityOAuth()->GetAccessToken())
342 return $result->addError(new Error('Could not get oauth token'));
345 if (isset($params['ENTITY_TYPE_ID']))
347 $entityTypeId = $params['ENTITY_TYPE_ID'];
348 unset($params['ENTITY_TYPE_ID']);
351 if (isset($params['ENTITY_ID']))
353 $entityId = $params['ENTITY_ID'];
354 unset($params['ENTITY_ID']);
357 $requestConferenceResult = $this->getEntityOAuth()->requestConference($params);
358 if(!$requestConferenceResult->isSuccess())
360 return $result->addErrors($requestConferenceResult->getErrors());
362 $conferenceData = $requestConferenceResult->getData();
364 $userData = $this->getEntityOAuth()->getCurrentUser();
365 if(!is_array($userData))
367 return $result->addError(new Error('Cannot get user data'));
369 $conferenceData['externalUserId'] = $userData['id'];
370 $conferenceData['externalAccountId'] = $userData['account_id'];
372 $conference['join_url'] = $this->attachPasswordToUrl($conferenceData['join_url'], $conferenceData['encrypted_password']);
374 $startTimeStamp = \DateTime::createFromFormat(DATE_ATOM, $conferenceData['start_time'])->getTimestamp();
375 $startDateTime = DateTime::createFromTimestamp($startTimeStamp);
378 'ENTITY_TYPE_ID' => ($entityTypeId ?? self::EMPTY_TYPE),
379 'ENTITY_ID' => ($entityId ?? 0),
380 'CONFERENCE_EXTERNAL_ID' => $conferenceData['id'],
381 'CONFERENCE_URL' => $conferenceData['join_url'],
382 'CONFERENCE_PASSWORD' => $conferenceData['encrypted_password'],
383 'CONFERENCE_CREATED' => (new DateTime()),
384 'CONFERENCE_STARTED' => $startDateTime,
385 'DURATION' => $conferenceData['duration'],
386 'TITLE' => $conferenceData['topic'],
387 'SHORT_LINK' => $this->getShortLink($conferenceData['id']),
390 $addResult = ZoomMeetingTable::add($params);
391 if (!$addResult->isSuccess())
393 return $result->addErrors($addResult->getErrors());
395 $conferenceData['bitrix_internal_id'] = $addResult->getId();
397 return $result->setData($conferenceData);
400 private function getShortLink(int $conferenceId): string
402 $host = UrlManager::getInstance()->getHostUrl();
403 $controllerUrl = \Bitrix\Main\Engine\UrlManager::getInstance()->create(
404 'crm.api.zoomUser.registerJoinMeeting',
405 ['conferenceId' => $conferenceId]
408 return $host.\CBXShortUri::GetShortUri($controllerUrl);
418 public function updateConference(array $updateParams): Result
420 $result = new Result();
423 if (!$this->getEntityOAuth()->GetAccessToken())
425 return $result->addError(new Error('Could not get oauth token'));
428 $preparedData = $this->prepareDataToUpdate($updateParams);
429 if (empty($preparedData))
434 $externalConferenceId = $preparedData['id'];
435 unset($preparedData['id']);
437 $requestConferenceResult = $this->getEntityOAuth()->updateConference($externalConferenceId, $preparedData);
438 if (!$requestConferenceResult->isSuccess())
440 return $result->addErrors($requestConferenceResult->getErrors());
443 if (isset($updateParams['start_time']))
445 $params['CONFERENCE_STARTED'] = DateTime::createFromUserTime($updateParams['start_time']);
448 if (isset($preparedData['duration']))
450 $params['DURATION'] = $preparedData['duration'];
455 $addResult = ZoomMeetingTable::update($updateParams['meeting_id'], $params);
456 if (!$addResult->isSuccess())
458 return $result->addErrors($addResult->getErrors());
474 private function prepareDataToUpdate(array $updateParams): ?array
476 $preparedDataToUpdate = [];
478 //get activity start and end timestamps
479 $activityStartTimeStamp = DateTime::createFromUserTime($updateParams['start_time'])->getTimestamp();
480 $activityEndTimeStamp = DateTime::createFromUserTime($updateParams['end_time'])->getTimestamp();
482 $meetingData = ZoomMeetingTable::getRowById($updateParams['meeting_id']);
488 //Prepare start_time only if the activity start_time does not match the conference start_time.
489 $meetingStartTimeStamp = $meetingData['CONFERENCE_STARTED']->getTimestamp();
490 if ($meetingStartTimeStamp !== $activityStartTimeStamp)
492 $preparedDataToUpdate['start_time'] = DateTime::createFromUserTime($updateParams['start_time'])
493 ->setTimeZone(new \DateTimeZone('UTC'))
497 //Prepare duration only if the activity duration does not match the conference duration.
498 $currentActivityDuration = ($activityEndTimeStamp - $activityStartTimeStamp) / 60;
499 if ($currentActivityDuration !== (int)$meetingData['DURATION'])
501 $preparedDataToUpdate['duration'] = $currentActivityDuration;
504 //Continue only if duration or start_time has been changed ($preparedDataToUpdate is not empty).
505 if (!empty($preparedDataToUpdate))
507 $preparedDataToUpdate['id'] = $meetingData['CONFERENCE_EXTERNAL_ID'];
510 return $preparedDataToUpdate;
513 public function getConferenceById(int $confId): ?array
516 if ($this->getEntityOAuth()->GetAccessToken())
518 $conference = $this->getEntityOAuth()->getConferenceById($confId);
524 private function attachPasswordToUrl(string $conferenceUrl, string $password): string
526 $url = new \Bitrix\Main\Web\Uri($conferenceUrl);
527 $queryParams = $url->getQuery();
529 parse_str($queryParams, $parsedParams);
530 if (!isset($parsedParams['pwd']))
532 $url->addParams(['pwd' => $password]);
533 $conferenceUrl = $url->getUri();
536 return $conferenceUrl;
548 public function sendComplianceRequest(array $payload): Result
550 return $this->getEntityOAuth()->sendComplianceNotify($payload);
554class CZoomInterface extends CSocServOAuthTransport
556 const SERVICE_ID = "zoom
";
558 const AUTH_URL = 'https://zoom.us/oauth/authorize';
559 const TOKEN_URL = 'https://zoom.us/oauth/token';
560 const COMPLIANCE_URL = 'https://api.zoom.us/oauth/data/compliance';
562 private const API_ENDPOINT = 'https://api.zoom.us/v2/';
564 private const USER_INFO_URL = 'users/me';
565 private const CREATE_MEETING_ENDPOINT = 'users/me/meetings';
566 private const UPDATE_MEETING_ENDPOINT = 'meetings/';
568 private const CACHE_TIME_CONNECT_INFO = "86400
"; //One day
569 public const CACHE_DIR_CONNECT_INFO = "/socialservices/zoom/
";
571 protected $userId = false;
572 protected $responseData = array();
576 'meeting:write', 'user:read:admin', 'meeting:read', 'recording:read'
579 public function __construct($appID = false, $appSecret = false, $code = false)
581 if ($appID === false)
583 $appID = trim(CSocServAuth::GetOption("zoom_client_id
"));
586 if ($appSecret === false)
588 $appSecret = trim(CSocServAuth::GetOption("zoom_client_secret
"));
591 parent::__construct($appID, $appSecret, $code);
594 public function GetRedirectURI(): string
596 return \CHTTP::URN2URI('/bitrix/tools/oauth/zoom.php');
599 public function GetAuthUrl($redirect_uri, $state = ''): string
601 return self::AUTH_URL .
602 '?client_id=' . $this->appID .
603 '&redirect_uri=' . urlencode($redirect_uri) .
604 '&response_type=' . 'code' .
605 '&response_mode=' . 'form_post' .
606 ($state <> '' ? '&state=' . JWT::urlsafeB64Encode($state) : '');
609 public function getResult()
611 return $this->responseData;
614 public function GetAccessToken($redirect_uri = ''): bool
616 $token = $this->getStorageTokens();
617 if (is_array($token))
619 $this->access_token = $token['OATOKEN'];
620 $this->accessTokenExpires = $token['OATOKEN_EXPIRES'];
624 if ($this->checkAccessToken())
629 if (isset($token['REFRESH_TOKEN']) && $this->getNewAccessToken($token['REFRESH_TOKEN'], $this->userId, true))
635 $this->deleteStorageTokens();
638 if ($this->code === false)
644 'code' => $this->code,
645 'grant_type' => 'authorization_code',
646 'redirect_uri' => $redirect_uri,
649 $httpClient = new HttpClient([
650 'socketTimeout' => $this->httpTimeout,
651 'streamTimeout' => $this->httpTimeout,
653 $httpClient->setAuthorization($this->appID, $this->appSecret);
655 $result = $httpClient->post(self::TOKEN_URL, $query);
658 $result = \Bitrix\Main\Web\Json::decode($result);
660 catch (\Bitrix\Main\ArgumentException $e)
665 if ((isset($result['access_token']) && $result['access_token'] <> ''))
667 $this->access_token = $result['access_token'];
668 $this->accessTokenExpires = time() + $result['expires_in'];
669 $this->refresh_token = $result['refresh_token'];
671 $_SESSION["OAUTH_DATA
"] = [
672 "OATOKEN
" => $this->access_token,
673 "OATOKEN_EXPIRES
" => $this->accessTokenExpires,
674 "REFRESH_TOKEN
" => $this->refresh_token,
682 public function getNewAccessToken($refreshToken = false, $userId = 0, $save = false): bool
684 if (!$this->appID || !$this->appSecret)
691 $refreshToken = $this->refresh_token;
694 $httpClient = new HttpClient(array(
695 'socketTimeout' => $this->httpTimeout,
696 'streamTimeout' => $this->httpTimeout,
698 $httpClient->setAuthorization($this->appID, $this->appSecret);
699 $queryPrams = http_build_query([
700 'refresh_token' => $refreshToken,
701 'grant_type' => 'refresh_token',
703 $url = static::TOKEN_URL.'?'.$queryPrams;
704 $result = $httpClient->post($url);
708 $arResult = Json::decode($result);
710 catch (\Bitrix\Main\ArgumentException $e)
715 if (!empty($arResult['access_token']))
717 $this->access_token = $arResult['access_token'];
718 $this->accessTokenExpires = $arResult['expires_in'] + time();
719 $this->refresh_token = $arResult['refresh_token'];
721 if ($save && (int)$userId > 0)
723 $dbSocservUser = \Bitrix\Socialservices\UserTable::getList(array(
725 '=EXTERNAL_AUTH_ID' => static::SERVICE_ID,
726 '=USER_ID' => $userId,
728 'select' => array('ID')
730 if ($arOauth = $dbSocservUser->fetch())
732 \Bitrix\Socialservices\UserTable::update($arOauth['ID'], array(
733 'OATOKEN' => $this->access_token,
734 'REFRESH_TOKEN' => $this->refresh_token,
735 'OATOKEN_EXPIRES' => $this->accessTokenExpires,
747 public function getCurrentUser()
749 if ($this->access_token === false)
754 $endPoint = self::API_ENDPOINT . self::USER_INFO_URL;
755 $requestResult = $this->sendRequest(HttpClient::HTTP_GET, $endPoint);
757 if (!$requestResult->isSuccess())
762 return $requestResult->getData();
765 public function requestConference($params): Result
767 $result = new Result();
769 $endPoint = self::API_ENDPOINT . self::CREATE_MEETING_ENDPOINT;
770 $requestResult = $this->sendRequest(HttpClient::HTTP_POST, $endPoint, $params);
772 if (!$requestResult->isSuccess())
774 return $result->addErrors($requestResult->getErrors());
776 $response = $requestResult->getData();
778 if (isset($response['code']) && $response['code'] != 200)
781 return $result->addError(new Error($response['message'], $response['code']));
784 return $result->setData($response);
787 public function updateConference(int $conferenceId, array $params): Result
789 $endPoint = self::API_ENDPOINT . self::UPDATE_MEETING_ENDPOINT . $conferenceId;
791 return $this->sendRequest(HttpClient::HTTP_PATCH, $endPoint, $params);
794 public function getConferenceById($confId): ?array
796 $newMeetingEndpoint = self::API_ENDPOINT. 'meetings/' . $confId;
797 $conferenceDataResult = $this->sendRequest(HttpClient::HTTP_GET, $newMeetingEndpoint);
799 if (!$conferenceDataResult->isSuccess())
804 $conferenceData = $conferenceDataResult->getData();
805 if (!is_array($conferenceData) || $conferenceData['id'] <= 0)
810 return $conferenceData;
813 public function getConferenceFiles($confId): ?array
815 $endPoint = self::API_ENDPOINT . "/meetings/{$confId}/recordings
";
816 $requestResult = $this->sendRequest(HttpClient::HTTP_GET, $endPoint);
817 if (!$requestResult->isSuccess())
822 return $requestResult->getData();
832 public function sendComplianceNotify(array $params): Result
835 'client_id' => $this->appID,
836 'user_id' => $params['user_id'],
837 'account_id' => $params['account_id'],
838 'deauthorization_event_received' => $params,
839 'compliance_completed' => true,
842 $result = new Result();
843 $http = new HttpClient([
844 'socketTimeout' => $this->httpTimeout,
845 'streamTimeout' => $this->httpTimeout,
848 $http->setAuthorization($this->appID, $this->appSecret);
849 $http->setHeader('Content-type', 'application/json');
850 $requestResult = $http->post(self::COMPLIANCE_URL, Json::encode($requestParams));
854 $decodedData = Json::decode($requestResult);
855 $result->setData($decodedData);
857 catch (ArgumentException $e)
859 return $result->addError(new Error('Could not decode service response'));
865 private function sendRequest(string $method, string $endPoint, array $params = []): Result
867 $result = new Result();
869 $http = new HttpClient(array(
870 'socketTimeout' => $this->httpTimeout,
871 'streamTimeout' => $this->httpTimeout,
873 $http->setHeader('Authorization', 'Bearer '.$this->access_token);
877 case HttpClient::HTTP_PATCH:
878 $http->setHeader('Content-type', 'application/json');
879 $http->query(HttpClient::HTTP_PATCH, $endPoint, Json::encode($params));
880 if ($http->getStatus() != 204)
883 $requestResult = $http->getResult();
884 $response = Json::decode($requestResult);
885 return $result->addError(new Error($response['message'], $response['code']));
889 case HttpClient::HTTP_POST:
890 $http->setHeader('Content-type', 'application/json');
891 $requestResult = $http->post($endPoint, Json::encode($params));
894 case HttpClient::HTTP_GET:
895 $requestResult = $http->get($endPoint);
899 return $result->addError(new Error('Unsupported request method'));
904 $decodedData = Json::decode($requestResult);
905 $result->setData($decodedData);
907 catch (ArgumentException $e)
909 return $result->addError(new Error('Could not decode service response'));
924 public static function isConnected(int $userId): bool
926 $cache = \Bitrix\Main\Data\Cache::createInstance();
927 $cacheId = self::SERVICE_ID .'|'. $userId;
929 if ($cache->initCache(self::CACHE_TIME_CONNECT_INFO, $cacheId, self::CACHE_DIR_CONNECT_INFO))
931 $user = $cache->getVars()['user'];
933 elseif ($cache->startDataCache())
935 $user = UserTable::getRow([
937 '=USER_ID' => $userId,
938 '=EXTERNAL_AUTH_ID' => self::SERVICE_ID
942 $cache->endDataCache(['user' => $user]);
945 return $user !== null;
948 public function GetAppInfo(): bool
953 public function getScopeEncode(): string
955 return implode(' ', array_map('urlencode', array_unique($this->getScope())));