Api
Class Api
Source
File: src/Tribe/Meetings/Zoom/Api.php
class Api extends Account_API {
/**
* The base URL of the Zoom REST API, v2.
*
* @since 1.0.0
*
* @var string
*/
public static $api_base = 'https://api.zoom.us/v2/';
/**
* The current Zoom API access token.
*
* @since 1.0.0
* @deprecated
*
* @var string
*/
protected $token;
/**
* The Client ID, as defined in Settings > APIs.
*
* @since 1.0.0
* @deprecated
*
* @var string
*/
protected $client_id;
/**
* The Client secret, as defined in Settings > APIs.
*
* @since 1.0.0
* @deprecated
*
* @var string
*/
protected $client_secret;
/**
* The current Zoom API refresh token.
*
* @since 1.0.0
*
* @var string
*/
protected $refresh_token;
/**
* Expected response code for GET requests.
*
* @since 1.0.2
*
* @var integer
*/
const GET_RESPONSE_CODE = 200;
/**
* Expected response code for POST requests.
*
* @since 1.0.2
*
* @var integer
*/
const POST_RESPONSE_CODE = 201;
/**
* Expected response code for POST OAuth requests.
*
* @since 1.0.2
*
* @var integer
*/
const OAUTH_POST_RESPONSE_CODE = 200;
/**
* Expected response code for PATCH requests.
*
* @since 1.0.2
*
* @var integer
*/
const PATCH_RESPONSE_CODE = 204;
/**
* Api constructor.
*
* @since 1.0.0
* @since 1.4.0 - Add encryption handler.
* @since 1.5.0 - Add Account_API to support multiple accounts.
*
* @param Encryption $encryption An instance of the Encryption handler.
*/
public function __construct( Encryption $encryption ) {
$this->encryption = ( ! empty( $encryption ) ? $encryption : tribe( Encryption::class ) );
// Attempt to load an account.
$this->load_account();
}
/**
* Checks whether the current Zoom API integration is authorized or not.
*
* The check is made on the existence of the refresh token, with it the token can be fetched on demand when
* required.
*
* @since 1.0.0
*
* @return bool Whether the current Zoom API integration is authorized or not.
*/
public function is_authorized() {
return ! empty( $this->refresh_token );
}
/**
* Makes a request to the Zoom API.
*
* @since 1.0.2
*
* @param string $url The URL to make the request to.
* @param array $args An array of arguments for the request. Should include 'method' (POST/GET/PATCH, etc).
* @param int $expect_code The expected response code, if not met, then the request will be considered a failure.
* Set to `null` to avoid checking the status code.
*
* @return Api_Response An API response to act upon the response result.
*/
protected function request( $url, array $args, $expect_code = self::GET_RESPONSE_CODE ) {
/**
* Filters the response for a Zoom API request to prevent the response from actually happening.
*
* @since 1.0.0
*
* @param null|Api_Response|\WP_Error|mixed $response The response that will be returned. A non `null` value
* here will short-circuit the response.
* @param string $url The full URL this request is being made to.
* @param array<string,mixed> $args The request arguments.
* @param int $expect_code The HTTP response code expected for this request.
*/
$response = apply_filters( 'tribe_events_virtual_meetings_zoom_api_post_response', null, $url, $args, $expect_code );
if ( null !== $response ) {
return Api_Response::ensure_response( $response );
}
$response = wp_remote_request( $url, $args );
if ( $response instanceof \WP_Error ) {
$error_message = $response->get_error_message();
do_action(
'tribe_log',
'error',
__CLASS__,
[
'action' => __METHOD__,
'code' => $response->get_error_code(),
'message' => $error_message,
'method' => $args['method'],
]
);
$user_message = sprintf(
// translators: the placeholder is for the error as returned from Zoom API.
_x(
'Error while trying to communicate with Zoom API: %s. Please try again in a minute.',
'The prefix of a message reporting a Zoom API communication error, the placeholder is for the error.',
'events-virtual'
),
$error_message
);
tribe_transient_notice(
'events-virtual-zoom-request-error',
'<p>' . esc_html( $user_message ) . '</p>',
[ 'type' => 'error' ],
60
);
return new Api_Response( $response );
}
$response_code = wp_remote_retrieve_response_code( $response );
if ( null !== $expect_code && $expect_code !== $response_code ) {
$data = [
'action' => __METHOD__,
'message' => 'Response code is not the expected one.',
'expected_code' => $expect_code,
'response_code' => $response_code,
'api_method' => $args['method'],
];
do_action( 'tribe_log', 'error', __CLASS__, $data );
$user_message = sprintf(
// translators: the placeholders are, respectively, for the expected and actual response codes.
_x(
'Zoom API response is not the expected one, expected %1$s, received %2$s. Please, try again in a minute.',
'The message reporting a Zoom API unexpected response code, placeholders are the codes.',
'events-virtual'
),
$expect_code,
$response_code
);
tribe_transient_notice(
'events-virtual-zoom-response-error',
'<p>' . esc_html( $user_message ) . '</p>',
[ 'type' => 'error' ],
60
);
return new Api_Response( new \WP_Error( $response_code, 'Response code is not the expected one.', $data ) );
}
return new Api_Response( $response );
}
/**
* Makes a POST request to the Zoom API.
*
* @since 1.0.0
* @since 1.0.2 Change to a sugar function implementing $this->request().
*
* @param string $url The URL to make the request to.
* @param array $args An array of arguments for the request.
* @param int $expect_code The expected response code, if not met, then the request will be considered a failure.
* Set to `null` to avoid checking the status code.
*
* @return Api_Response An API response to act upon the response result.
*/
public function post( $url, array $args, $expect_code = self::POST_RESPONSE_CODE ) {
$args['method'] = 'POST';
return $this->request( $url, $args, $expect_code );
}
/**
* Makes a PATCH request to the Zoom API.
*
* @since 1.0.2
*
* @param string $url The URL to make the request to.
* @param array $args An array of arguments for the request.
* @param int $expect_code The expected response code, if not met, then the request will be considered a failure.
* Set to `null` to avoid checking the status code.
*
* @return Api_Response An API response to act upon the response result.
*/
public function patch( $url, array $args, $expect_code = self::PATCH_RESPONSE_CODE ) {
$args['method'] = 'PATCH';
return $this->request( $url, $args, $expect_code );
}
/**
* Makes a GET request to the Zoom API.
*
* @since 1.0.2
*
* @param string $url The URL to make the request to.
* @param array $args An array of arguments for the request.
* @param int $expect_code The expected response code, if not met, then the request will be considered a failure.
* Set to `null` to avoid checking the status code.
*
* @return Api_Response An API response to act upon the response result.
*/
public function get( $url, array $args, $expect_code = self::GET_RESPONSE_CODE ) {
$args['method'] = 'GET';
return $this->request( $url, $args, $expect_code );
}
/**
* {@inheritDoc}
*/
public function refresh_access_token( $id, $refresh_token ) {
$refreshed = false;
$this->post(
OAuth::$token_request_url,
[
'body' => [
'grant_type' => 'refresh_token',
'refresh_token' => $refresh_token,
],
],
200
)->then(
function ( array $response ) use ( &$id, &$refreshed ) {
if (
! (
isset( $response['body'] )
&& false !== ( $body = json_decode( $response['body'], true ) )
&& isset( $body['access_token'], $body['refresh_token'], $body['expires_in'] )
)
) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'message' => 'Zoom API access token refresh response is malformed.',
'response' => $body,
] );
return false;
}
$refreshed = $this->save_access_and_expiration( $id, $response );
return $refreshed;
}
);
return $refreshed;
}
/**
* Get the Meeting by ID from Zoom and Return the Data.
*
* @since 1.0.4
*
* @param int $zoom_meeting_id The Zoom meeting id.
* @param string $meeting_type The type of meeting (Meeting or Webinar) to fetch the information for.
*
* @return array An array of data from the Zoom API.
*/
public function fetch_meeting_data( $zoom_meeting_id, $meeting_type ) {
if ( ! $this->get_token_authorization_header() ) {
return [];
}
$data = [];
$api_endpoint = Meetings::$meeting_type === $meeting_type
? Meetings::$api_endpoint
: Webinars::$api_endpoint;
$this->get(
self::$api_base . "{$api_endpoint}/{$zoom_meeting_id}",
[
'headers' => [
'Authorization' => $this->get_token_authorization_header(),
'Content-Type' => 'application/json; charset=utf-8',
],
'body' => null,
],
200
)->then(
function ( array $response ) use ( &$data ) {
if (
! (
isset( $response['body'] )
&& false !== ( $body = json_decode( $response['body'], true ) )
&& isset( $body['join_url'] )
)
) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'message' => 'Zoom API meetings settings response is malformed.',
'response' => $body,
] );
return [];
}
$data = $body;
}
)->or_catch(
function ( \WP_Error $error ) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'code' => $error->get_error_code(),
'message' => $error->get_error_message(),
] );
}
);
return $data;
}
/**
* {@inheritDoc}
*/
public function fetch_user( $user_id = 'me', $settings = false, $access_token = '' ) {
if ( ! $this->get_token_authorization_header( $access_token ) ) {
return [];
}
// If both user id and settings, add settings to detect webinar support.
if ( $user_id && $settings ) {
$user_id = $user_id . '/settings';
}
$this->get(
self::$api_base . 'users/' . $user_id,
[
'headers' => [
'Authorization' => $this->get_token_authorization_header( $access_token ),
'Content-Type' => 'application/json; charset=utf-8',
],
'body' => null,
],
200
)->then(
static function ( array $response ) use ( &$data ) {
$body = json_decode( $response['body'] );
if (
! (
isset( $response['body'] )
&& false !== ( $body = json_decode( $response['body'], true ) )
)
) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'message' => 'Zoom API user response is malformed.',
'response' => $body,
] );
return [];
}
$data = $body;
}
)->or_catch(
static function ( \WP_Error $error ) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'code' => $error->get_error_code(),
'message' => $error->get_error_message(),
] );
}
);
return $data;
}
/**
* Get the List of Users
*
* @since 1.4.0
*
* @return array An array of data from the Zoom API.
*/
public function fetch_users() {
if ( ! $this->get_token_authorization_header() ) {
return [];
}
$data = [
'page_size' => 300,
'page_number' => 1,
];
$this->get(
self::$api_base . 'users',
[
'headers' => [
'Authorization' => $this->get_token_authorization_header(),
'Content-Type' => 'application/json; charset=utf-8',
],
'body' => ! empty( $data ) ? $data : null,
],
200
)->then(
static function ( array $response ) use ( &$data ) {
$body = json_decode( $response['body'] );
if (
! (
isset( $response['body'] )
&& false !== ( $body = json_decode( $response['body'], true ) )
&& isset( $body['users'] )
)
) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'message' => 'Zoom API users response is malformed.',
'response' => $body,
] );
return [];
}
$data = $body;
}
)->or_catch(
static function ( \WP_Error $error ) {
do_action( 'tribe_log', 'error', __CLASS__, [
'action' => __METHOD__,
'code' => $error->get_error_code(),
'message' => $error->get_error_message(),
] );
}
);
return $data;
}
/**
* Returns the current API access token.
*
* If not available, then a new token will be fetched.
*
* @since 1.0.0
* @since 1.1.1 Changed the method to use the new OAuth flow that is not handled by the plugin.
* @deprecated 1.5.0 - OAuth supports multiple accounts, see refresh_access_token().
*
* @return string The API access token, or an empty string if the token cannot be fetched.
*/
public function refresh_token( $refresh_token ) {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support, see refresh_access_token()' );
$token = $this->encryption->decrypt( get_transient( Settings::$option_prefix . 'access_token' ) );
if ( empty( $token ) ) {
$token_url = OAuth::$token_request_url;
if ( defined( 'TEC_VIRTUAL_EVENTS_ZOOM_API_TOKEN_URL' ) ) {
$token_url = TEC_VIRTUAL_EVENTS_ZOOM_API_TOKEN_URL;
}
// Check if this is a legacy authorization, if so, we need to refresh against Zoom directly.
$legacy_auth_code = tribe_get_option( Settings::$option_prefix . 'auth_code' );
if ( ! empty( $legacy_auth_code ) ) {
$token_url = OAuth::$legacy_token_request_url;
}
$this->post(
$token_url,
[
'headers' => [
'Authorization' => $this->authorization_header(),
],
'body' => [
'grant_type' => 'refresh_token',
'refresh_token' => $this->refresh_token,
],
],
200
)->then( [ $this, 'save_access_token' ] );
// Fetch it again, it should now be there.
$token = $this->encryption->decrypt( get_transient( Settings::$option_prefix . 'access_token' ) );
}
return (string) $token;
}
/**
* Returns the current API access token.
*
* If not available, then a new token will be fetched.
*
* @since 1.0.0
* @since 1.1.1 Changed the method to use the new OAuth flow that is not handled by the plugin.
* @deprecated 1.5.0 - OAuth supports multiple accounts, see refresh_access_token().
*
* @return string The API access token, or an empty string if the token cannot be fetched.
*/
protected function get_access_token() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support, see refresh_access_token()' );
$token = get_transient( Settings::$option_prefix . 'access_token' );
if ( empty( $token ) ) {
$url = OAuth::$token_request_url;
// Check if this is a legacy authorization, if so, we need to refresh against Zoom directly.
$legacy_auth_code = tribe_get_option( Settings::$option_prefix . 'auth_code' );
if ( ! empty( $legacy_auth_code ) ) {
$url = OAuth::$legacy_token_request_url;
}
$this->post(
$url,
[
'headers' => [
'Authorization' => $this->authorization_header(),
],
'body' => [
'grant_type' => 'refresh_token',
'refresh_token' => $this->refresh_token,
],
],
200
)->then( [ $this, 'save_access_token' ] );
// Fetch it again, it should now be there.
$token = get_transient( Settings::$option_prefix . 'access_token' );
}
return (string) $token;
}
/**
* Returns the access token based authorization header to send requests to the Zoom API.
*
* @since 1.0.0
* @deprecated 1.5.0 OAuth supports multiple accounts, see Account_API class.
*
* @return string The authorization header, to be used in the `headers` section of a request to Zoom API.
*/
public function token_authorization_header() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
return 'Bearer ' . $this->get_access_token();
}
/**
* Returns the Zoom Application Client ID as provided by the user.
*
* @since 1.0.0
* @deprecated 1.1.1 The OAuth flow is not handled by the plugin anymore.
*
* @return string The Zoom Application Client ID provided by the user.
*/
public function client_id() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated with no replacement.' );
return $this->client_id;
}
/**
* Returns the current Client Secret used to communicate with the Zoom API.
*
* @since 1.0.0
* @deprecated 1.1.1 The OAuth flow is not handled by the plugin anymore.
*
* @return string The current Client Secret used to communicate with the Zoom API.
*/
public function client_secret() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated with no replacement.' );
return $this->client_secret;
}
/**
* Builds the request authorization header as required by the Zoom API.
*
* @since 1.0.0
* @deprecated 1.1.1 The OAuth flow is not handled by the plugin anymore.
*
* @return string The authorization header, to be used in the `headers` section of a request to Zoom API.
*/
public function authorization_header() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated with no replacement.' );
return 'Basic ' . base64_encode( $this->client_id() . ':' . $this->client_secret() );
}
/**
* Checks whether all fields required to interact with the Zoom API are correctly set or not.
*
* @since 1.0.0
* @deprecated 1.1.1 The OAuth flow is not handled by the plugin anymore.
*
* @return bool Whether all fields required to interact with the Zoom API are correctly set or not.
*/
public function has_required_fields() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
foreach ( $this->required_fields() as $required_field ) {
if ( empty( tribe_get_option( Settings::$option_prefix . $required_field ) ) ) {
return false;
}
}
return true;
}
/**
* Returns a list of the fields required by the application to work.
*
* @since 1.0.0
* @deprecated 1.1.1 The OAuth flow is not handled by the plugin anymore.
*
* @return array<string> A list of the fields, tribe_option keys w/o the Zoom\Settings prefix, required by the
* integration to work.
*/
public function required_fields() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
return [];
}
/**
* Validates and saves the access token to the database if all the required data is provided.
*
* @since 1.0.0
* @deprecated 1.5.0 OAuth supports multiple accounts, see Account_API class.
*
* @param array<string,array> $response An array representing the access token request response, in the format
* returned by WordPress `wp_remote_` functions.
*
* @return bool Whether the access token data was saved or not.
*/
public function save_access_token( array $response ) {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
if ( ! (
isset( $response['body'] )
&& ( false !== $d = json_decode( $response['body'], true ) )
&& isset( $d['access_token'], $d['refresh_token'], $d['expires_in'] )
)
) {
do_action(
'tribe_log',
'error',
__CLASS__,
[
'action' => __METHOD__,
'code' => wp_remote_retrieve_response_code( $response ),
'message' => 'Response body missing or malformed',
]
);
return false;
}
$access_token = $d['access_token'];
$refresh_token = $d['refresh_token'];
/**
* Take the expiration in seconds as provided by the server and remove a minute to pad for save delays.
*/
$expiration = ( (int) $d['expires_in'] ) - 60;
// Save the refresh token.
$encrypted_refresh_token = $this->encryption->encrypt( $refresh_token );
tribe_update_option( Settings::$option_prefix . 'refresh_token', $encrypted_refresh_token );
// Since the access token is, by its own nature, transient, let's store it as that.
$encrypted_access_token = $this->encryption->encrypt( $access_token );
set_transient( Settings::$option_prefix . 'access_token', $encrypted_access_token, $expiration );
return $access_token;
}
/**
* Returns whether the generation and management of Zoom Webinars is allowed at the API level or not.
*
* The option value is initially set by checking whether the current Zoom API connection allows for the generation
* of Webinars or not; the check is done at connection time.
*
* @since 1.1.1
* @deprecated 1.5.0 - Webinar check is done per account with no global option.
*
* @return bool Whether the generation and management of the Zoom Webinars is allowed at the API level or not.
*/
public function allow_webinars() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
$allowed = tribe_is_truthy( tribe_get_option( Settings::$option_prefix . 'allow_webinars' ) );
/**
* Allows filtering whether the generation and management of Zoom Webinars is allowed at the API level or not.
*
* @since 1.1.1
*
* @param bool $allowed Whether the generation and handling of Zoom Webinars is allowed at the API level or not.
*/
return (bool) apply_filters( 'tribe_events_virtual_meetings_zoom_api_allow_webinars', $allowed );
}
/**
* Checks if the user is authorized to operate on Webinars.
*
* The check done based on if an account has any users that can be alternative hosts.
* Any account with those type of users supports webinars.
*
* @since 1.1.1
* @since 1.4.0 - Modify to check for alternative hosts as users that support alt hosts can generate webinars.
* @deprecated 1.5.0 - Accounts_API queries the user settings directly to determine webinar support.
*
* @see Api::allow_webinars() to get the value set by this method.
*/
public function check_webinar_cap() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
$alternative_hosts = tribe( Users::class )->get_alternative_users();
$option_value = 'no';
if ( ! empty( $alternative_hosts ) ) {
$option_value = 'yes';
}
tribe_update_option( Settings::$option_prefix . 'allow_webinars', $option_value );
}
/**
* Resets the Webinar API capability by setting the related option to a null/empty value.
*
* @since 1.1.1
* @deprecated 1.5.0 - Webinar check is done per account with no global option.
*
* @return bool Whether the Webinar capability was correctly reset or not.
*/
public function reset_webinar_cap() {
_deprecated_function( __FUNCTION__, '1.5.0', 'Deprecated for multiple account support with no replacement.' );
return tribe_update_option( Settings::$option_prefix . 'allow_webinars', '' );
}
}
Changelog
| Version | Description |
|---|---|
| 1.9.0 | - Extends Request_Api Class a shared class for connections to an API. |
| 1.5.0 | - Extends Account_API Class to support multiple accounts. |
| 1.0.0 | Introduced. |
Methods
- __construct — Api constructor.
- fetch_meeting_data — Get the Meeting by ID from Zoom and Return the Data.
- fetch_user — {@inheritDoc}
- fetch_users — Get the List of all Users
- fetch_users_with_args — Get the List of Users by arguments.
- filter_virtual_autodetect_zoom — Filter the autodetect source to detect if a Zoom link.
- get — Makes a GET request to the Zoom API.
- get_regex_meeting_id_url — Get the regex to get the Zoom meeting/webinar id from a url.
- is_authorized — Checks whether the current Zoom API integration is authorized or not.
- patch — Makes a PATCH request to the Zoom API.
- post — Makes a POST request to the Zoom API.
- refresh_access_token — {@inheritDoc}