<?php
/*
 * Jetpack CRM
 * https://jetpackcrm.com
 *
 * OAuth connection handler.
 *
 */
namespace Automattic\JetpackCRM;

use Google\Client;
use League\OAuth2\Client\Grant\RefreshToken;
use League\OAuth2\Client\Provider\Google;

// block direct access
defined( 'ZEROBSCRM_PATH' ) || exit( 0 );

class Oauth_Handler {

	/*
	* Enabled.
	*/
	private $enabled = true;

	/*
	* Debug output?
	*/
	private $debug = true;

	/**
	 * Callback url base (url which oauth bounces back to) (has provider attached via `get_callback_url()`)
	 */
	private $providers = array(

		'google_mail' => array(
			'name'                       => 'Google Mail',
			'provider'                   => 'google_mail', // means we'll use 'provider_google_mail()' instead of 'provider_generic()'
			'docs_url'                   => 'https://kb.jetpackcrm.com/knowledge-base/using-gmail-with-jetpack-crm-mail-delivery-system/#setting-up-gmail-oauth-connection-and-mail-delivery-method',

			// required for refreshing tokens generically:
			// https://github.com/thephpleague/oauth2-google/blob/main/src/Provider/Google.php#L39
			'url_authorize'              => 'https://accounts.google.com/o/oauth2/v2/auth',
			'url_access_token'           => 'https://oauth2.googleapis.com/token',
			'url_resource_owner_details' => 'https://openidconnect.googleapis.com/v1/userinfo',

			// required scopes
			/*
			Scopes: https://developers.google.com/gmail/api/auth/scopes

				https://www.googleapis.com/auth/gmail.compose
				https://www.googleapis.com/auth/gmail.send
				Google_Service_Gmail::GMAIL_READONLY
			*/
			'scopes'                     => array( 'https://www.googleapis.com/auth/gmail.send' ),
		),

	);

	/**
	 * Init
	 */
	public function __construct() {
		// Add our action to the stack
		add_filter( 'jpcrm_listener_actions', array( $this, 'add_listener_action' ), 1 );

		// set action for endpoint listener to fire, so we can catch oauth requests (if any)
		add_action( 'jpcrm_listener_oauth', array( $this, 'catch_oauth_request' ), 10 );
	}

	/**
	 * Uses package installer to ensure packages are installed
	 */
	public function ensure_packages_installed() {

		global $zbs;

		$zbs->load_package_installer();
		$zbs->ensure_package_installed( 'oauth_dependencies', 1.0 );
	}

	/**
	 * Return enabled state
	 */
	public function enabled() {

		return $this->enabled;
	}

	/**
	 * Include league/oauth2-client, (well, in fact, autoload /vendor)
	 * Either: league/oauth2-client
	 * https://oauth2-client.thephpleague.com/usage/
	 * Or: league/oauth2-google
	 * https://github.com/thephpleague/oauth2-google
	 */

	/**
	 * Returns a provider class (typically generated by `provider_generic()`, but allows exceptions, (e.g. `provider_google_mail()`))
	 */
	public function provider( $provider_key, $args = array() ) {

		// see if we have a unique provider class (e.g. google_mail)
		$provider_info = $this->get_provider( $provider_key );

		if ( isset( $provider_info['provider'] ) && method_exists( $this, 'provider_' . $provider_info['provider'] ) ) {

			// use custom provider
			return call_user_func_array( array( $this, 'provider_' . $provider_info['provider'] ), $args );

		} else {

			// use generic provider
			return $this->provider_generic( $args );

		}
	}

	/**
	 * Returns a generic provider instance
	 */
	public function provider_generic( $args = array() ) {

		global $zbs;

		// ============ LOAD ARGS =============
		$default_args = array(

			'id'                      => '',
			'secret'                  => '',
			'redirect_uri'            => '',

			'urlAuthorize'            => '',
			'urlAccessToken'          => '',
			'urlResourceOwnerDetails' => '',

		);
		foreach ( $default_args as $argK => $argV ) {
			$$argK = $argV;
			if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
				if ( is_array( $args[ $argK ] ) ) {
					$newData = $$argK;
					if ( ! is_array( $newData ) ) {
						$newData = array();
					} foreach ( $args[ $argK ] as $subK => $subV ) {
						$newData[ $subK ] = $subV;
					}$$argK = $newData;
				} else {
					$$argK = $args[ $argK ]; }
			}
		}
		// ============ / LOAD ARGS =============

		// make sure our dependencies are installed
		$this->ensure_packages_installed();

		// build provider
		return new \League\OAuth2\Client\Provider\GenericProvider(
			array(
				'clientId'                => $id,                     // The client ID assigned to you by the provider
				'clientSecret'            => $secret,                 // The client password assigned to you by the provider
				'redirectUri'             => $redirect_uri,           // 'https://my.example.com/your-redirect-url/',
				'urlAuthorize'            => $urlAuthorize,           // 'https://service.example.com/authorize',
				'urlAccessToken'          => $urlAccessToken,         // 'https://service.example.com/token',
				'urlResourceOwnerDetails' => $urlResourceOwnerDetails, // 'https://service.example.com/resource'
			)
		);
	}

	/**
	 * Returns Google Mail provider instance
	 */
	public function provider_google_mail( $args = array() ) {

		// ============ LOAD ARGS =============
		$default_args = array(

			'id'     => '',
			'secret' => '',

		);
		foreach ( $default_args as $argK => $argV ) {
			$$argK = $argV;
			if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
				if ( is_array( $args[ $argK ] ) ) {
					$newData = $$argK;
					if ( ! is_array( $newData ) ) {
						$newData = array();
					} foreach ( $args[ $argK ] as $subK => $subV ) {
						$newData[ $subK ] = $subV;
					}$$argK = $newData;
				} else {
					$$argK = $args[ $argK ]; }
			}
		}
		// ============ / LOAD ARGS =============

		// make sure our dependencies are installed
		$this->ensure_packages_installed();

		// args
		$args = array(
			'clientId'     => $id,
			'clientSecret' => $secret,
			'redirectUri'  => $this->get_callback_url( 'google_mail' ),
			'accessType'   => 'offline', // Required to get a refreshToken
			'scopes'       => $this->get_provider_detail( 'google_mail', 'scopes' ),
		);

		// Hosted Domain, optional; used to restrict access to users on your G Suite/Google Apps for Business accounts
		/*
		We could later expose this via setting, but leaving unrestricted is more useful for now
		$domain = $zbs->get_domain();
		if ( $domain ){
			$args['hostedDomain'] = $domain;
		}
		*/

		// other params: https://github.com/thephpleague/oauth2-google
		// prompt, scopes, accessType

		// return provider
		return new Google( $args );
	}

	/*
	 * Add our listener action to the stack
	 *
	*/
	public function add_listener_action( $actions = array() ) {

		$actions[] = 'oauth';

		return $actions;
	}

	/*
	 * Catch OAuth requests
	 * Fired on load, catches any oauth responses inbound
	*/
	public function catch_oauth_request() {

		// at this point the Endpoint_Listener layer has verified the call via `is_listener_request()`

		// split by oauth providers, retrieve provider
		$provider = sanitize_text_field( $_GET['jpcrm_oauth_provider'] );

		if ( $this->legitimate_provider( $provider ) ) {

			// got auth function? (e.g. `catch_google_mail_authorisation`)
			if ( method_exists( $this, 'catch_' . $provider . '_authorisation' ) ) {

				// make sure our dependencies are installed
				$this->ensure_packages_installed();

				return call_user_func_array( array( $this, 'catch_' . $provider . '_authorisation' ), array() );

			}
		}
	}

	/*
	* Catch Google Mail Authorisation requests
	* https://github.com/thephpleague/oauth2-google
	*/
	public function catch_google_mail_authorisation() {

		// at this point the Endpoint_Listener layer has verified the call via `is_listener_request()`

		global $zbs;

		// require admin
		if ( ! zeroBSCRM_isZBSAdminOrAdmin() ) {

			return false;

		}

		// retrieve keys
		$config = $this->get_provider_config( 'google_mail' );
		if ( ! is_array( $config ) || empty( $config['id'] ) || empty( $config['secret'] ) ) {

			// not yet setup, needs id/secret
			$this->display_alert(
				'info',
				__( 'Google API Credentials needed', 'zero-bs-crm' ),
				'<p>' . __( 'To authenticate this OAuth connection you\'ll first need to add API credentials.', 'zero-bs-crm' ) . '</p>'
				. '<p style="text-align:center"><a href="' . jpcrm_esc_link( $zbs->slugs['settings'] . '&tab=oauth&edit-provider=google_mail' ) . '" class="ui button green">' . __( 'Add your credentials', 'zero-bs-crm' ) . '</a></p>',
				'',
				'',
				true // exit
			);

		} else {

			$google_client_id     = $config['id'];
			$google_client_secret = $config['secret'];

		}

		// quick check
		if ( empty( $google_client_id ) || empty( $google_client_secret ) ) {
			return false;
		}

		// start session
		// Remove if session.auto_start=1 in php.ini
		session_start();

		// args
		$args = array(
			'id'     => $google_client_id,
			'secret' => $google_client_secret,
		);

		// retrieve provider
		$provider = $this->provider_google_mail( $args );

		// process
		if ( ! empty( $_GET['error'] ) ) {

			// Got an error, probably user denied access
			$error_string = htmlspecialchars( $_GET['error'], ENT_QUOTES, 'UTF-8' );
			$this->display_alert(
				'warning',
				sprintf( __( 'Google API Connection Error %s', 'zero-bs-crm' ), '#1' ),
				'<p>' . __( 'There was an error connecting to Google:', 'zero-bs-crm' ) . '</p>'
				. '<p><code>' . $error_string . '</code></p>',
				'',
				'',
				true // exit
			);

		} elseif ( empty( $_GET['code'] ) ) {

			// If we don't have an authorization code then redirect to get one
			$authorisation_url       = $provider->getAuthorizationUrl(
				array(
					'prompt'      => 'consent',
					'access_type' => 'offline',
				)
			);
			$_SESSION['oauth2state'] = $provider->getState();
			header( 'Location: ' . $authorisation_url );
			exit( 0 );

		} elseif ( empty( $_GET['state'] ) || ( $_GET['state'] !== $_SESSION['oauth2state'] ) ) {
			// State is invalid, possible CSRF attack in progress
			// (though to get here we'd need to be admin, so probably this'll never fire)
			unset( $_SESSION['oauth2state'] );
			exit( 0 );
		} else {

			try {

				// Try to get an access token (using the authorization code grant)
				$token = $provider->getAccessToken(
					'authorization_code',
					array(
						'code' => $_GET['code'],
					)
				);

				// save token (override any existing)
				$config['token']         = $token->getToken();
				$config['refresh_token'] = $token->getRefreshToken();
				$config['expires']       = $token->getExpires();

				try {

					// verify:
					if ( ! $token->hasExpired() ) {

						$resource_owner = $provider->getResourceOwner( $token );
						$resource_owner = $resource_owner->toArray();

						// if this is an array, we're definitely connected!
						if ( is_array( $resource_owner ) && isset( $resource_owner['email'] ) ) {

							// here we can save any meta :)
							// (in gmail case, might be useful for mail delivery methods)
							if ( ! isset( $config['meta'] ) || ! is_array( $config['meta'] ) ) {
								$config['meta'] = array();
							}

							/*
							Example:
							Array
							(
								[sub] => 111285058559146358423
								[name] => Woody Hayday
								[given_name] => Woody
								[family_name] => Hayday
								[picture] => https://lh3.googleusercontent.com/a-/AOh14GgBbDQY4COolmrmvq1G4w_NQdirrJfGSPraw8LzEA=s96-c
								[email] => woodyhayday@gmail.com
								[email_verified] => 1
								[locale] => en-GB
							)
							*/

							$fields = array( 'name', 'given_name', 'family_name', 'picture', 'email', 'email_verified', 'locale' );
							foreach ( $fields as $field_key ) {

								if ( isset( $resource_owner[ $field_key ] ) ) {
									$config['meta'][ $field_key ] = sanitize_text_field( $resource_owner[ $field_key ] );
								}
							}
						}

						// Save
						$this->update_provider_config( 'google_mail', $config );

						// visual output
						$account_details = $resource_owner['email'];
						if ( isset( $resource_owner['picture'] ) && ! empty( $resource_owner['picture'] ) ) {

							$account_details = '<img src="' . $resource_owner['picture'] . '" alt="" style="max-width:60px;margin:1em" />' . $account_details;

						}

						$this->display_alert(
							'info',
							__( 'Google API Connection Complete', 'zero-bs-crm' ),
							'<p>' . __( 'You have successfully connected the following Google account to your CRM:', 'zero-bs-crm' ) . '</p>'
							. '<div style="display: flex;align-items: center;justify-content: center;">' . $account_details . '</div>'
							. '<p>' . __( 'You can now close this window and return to your settings page.', 'zero-bs-crm' ) . '</p>',
							'',
							'',
							true,
							true // refresh on parent close
						);

					}
				} catch ( Exception $e ) {

					$this->display_alert(
						'warning',
						sprintf( __( 'Google API Connection Error %s', 'zero-bs-crm' ), '#2' ),
						'<p>' . __( 'There was an error connecting to Google:', 'zero-bs-crm' ) . '</p>'
						. '<p><code>' . $e->getMessage() . '</code></p>',
						'',
						'',
						true // exit
					);

				}
			} catch ( \League\OAuth2\Client\Provider\Exception\IdentityProviderException $e ) {

				$this->display_alert(
					'warning',
					sprintf( __( 'Google API Connection Error %s', 'zero-bs-crm' ), '#3' ),
					'<p>' . __( 'There was an error connecting to Google:', 'zero-bs-crm' ) . '</p>'
					. '<p><code>' . $e->getMessage() . '</code></p>',
					'',
					'',
					true // exit
				);

			}
		}
	}

	/*
	 * Retrieve a callback url with provider affixed
	*/
	public function get_callback_url( $provider ) {

		global $zbs;

		if ( $this->legitimate_provider( $provider ) ) {

			// load listener if it's not already loaded
			$zbs->load_listener();

			return $zbs->listener->get_callback_url( 'oauth' ) . '&jpcrm_oauth_provider=' . urlencode( $provider );

		}

		return false;
	}

	/*
	 * Checks if a provider is on the list
	*/
	public function legitimate_provider( $provider ) {

		if ( ! empty( $provider ) && in_array( $provider, array_keys( $this->providers ) ) ) {

			return true;

		}

		return false;
	}

	/*
	* Retrieves a provider detail, (e.g. google's 'url_authorize')
	*/
	public function get_provider_detail( $provider, $attribute_key ) {

		if ( isset( $this->providers[ $provider ] ) && isset( $this->providers[ $provider ][ $attribute_key ] ) ) {

			return $this->providers[ $provider ][ $attribute_key ];

		}

		return false;
	}

	/*
	 * Returns provider summary details
	*/
	public function get_provider( $provider ) {

		if ( isset( $this->providers[ $provider ] ) ) {

			return $this->providers[ $provider ];

		}
	}

	/**
	 * Returns providers array
	 */
	public function get_providers() {

		return $this->providers;
	}

	/*
	 * get config/token for a provider
	 *
	 * @param string $provider_key - e.g. 'google_mail'
	*/
	public function get_provider_config( $provider_key, $return_token_string_only = false, $refresh_token_if_expired = true ) {

		global $zbs;

		// basic check
		if ( ! $this->legitimate_provider( $provider_key ) ) {
			return false;
		}

		// retrieve token configs as they are
		$existing_token_configs = $zbs->settings->get( 'oauth_tokens', false );

		// retrieve
		if ( is_array( $existing_token_configs ) && isset( $existing_token_configs[ $provider_key ] ) ) {

			// get current
			$token_config            = $existing_token_configs[ $provider_key ];
			$token_config['expires'] = (int) $token_config['expires'];

			// if expired and $refresh_token_if_expired, regen
			if ( $refresh_token_if_expired && ! empty( $token_config['token'] ) && isset( $token_config['expires'] ) && $token_config['expires'] < time() ) {

				$this->debug_message( 'Found token which has expired!<br>' );

				// refresh token
				$refreshed_token_config = $this->refresh_token( $provider_key, $token_config );

				if ( is_array( $refreshed_token_config ) ) {

					// success
					$token_config = $refreshed_token_config;

				} else {

					// failed refreshing token
					return false;

				}
			}

			// if only the token itself, return that
			if ( $return_token_string_only ) {

				return $token_config['token'];

			}

			// else return the whole token info array
			return $token_config;

		}

		// no such token
		return false;
	}

	/*
	 * Update config/token for a provider
	 *
	 * @param string $provider - e.g. 'google_mail'
	 * @param array $args
	*/
	public function update_provider_config( $provider, $args = array() ) {

		global $zbs;

		// ============ LOAD ARGS =============
		$default_args = array(

			'id'            => '',
			'secret'        => '',
			'token'         => '',
			'refresh_token' => '',
			'expires'       => '',
			'meta'          => array(),

		);
		foreach ( $default_args as $argK => $argV ) {
			$$argK = $argV;
			if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
				if ( is_array( $args[ $argK ] ) ) {
					$newData = $$argK;
					if ( ! is_array( $newData ) ) {
						$newData = array();
					} foreach ( $args[ $argK ] as $subK => $subV ) {
						$newData[ $subK ] = $subV;
					}$$argK = $newData;
				} else {
					$$argK = $args[ $argK ]; }
			}
		}
		// ============ / LOAD ARGS =============

		// basic check
		if ( ! $this->legitimate_provider( $provider ) ) {
			return false;
		}

		// retrieve tokens as they are
		$existing_token_configs = $zbs->settings->get( 'oauth_tokens', false );

		// check is an array
		if ( ! is_array( $existing_token_configs ) ) {
			$existing_token_configs = array();
		}

		// hard override
		$existing_token_configs[ $provider ] = array(

			// credentials
			'id'              => $id,
			'secret'          => $secret,

			// token
			'token'           => $token,
			'expires'         => $expires,
			'refresh_token'   => $refresh_token,
			'last_updated_by' => zeroBSCRM_user(),
			'last_updated'    => time(),

			// optional, per provider extras
			'meta'            => $meta,

		);

		// update
		$zbs->settings->update( 'oauth_tokens', $existing_token_configs );
	}

	/*
	 * Delete config/token for a provider
	 *
	 * @param string $provider - e.g. 'google_mail'
	*/
	public function delete_provider_config( $provider ) {

		global $zbs;

		// retrieve tokens as they are
		$existing_token_configs = $zbs->settings->get( 'oauth_tokens', false );

		// if exists
		if ( isset( $existing_token_configs[ $provider ] ) ) {

			// direct unset
			unset( $existing_token_configs[ $provider ] );

			// update
			$zbs->settings->update( 'oauth_tokens', $existing_token_configs );

			return true;

		}

		return false;
	}

	/*
	 * Refreshes a user token and returns
	 *
	 * @param string $provider_key - e.g. `google_mail`
	 * @param array|bool $provider_config - either the config (if already available), or false (and this function will load it)
	 *
	 * @return array - updated token config
	*/
	public function refresh_token( $provider_key, $provider_config = false ) {

		// if no config, load it
		if ( ! is_array( $provider_config ) ) {

			// note false for third parameter `refresh_token_if_expired` - this is to avoid an infinite loop
			$provider_config = $this->get_provider_config( $provider_key, false, false );

		}

		// got refresh token?
		if ( ! isset( $provider_config['refresh_token'] ) || empty( $provider_config['refresh_token'] ) ) {

			return false;

		}

		// make sure our dependencies are installed
		$this->ensure_packages_installed();

		// if we have a specific function for this provider, defer to that:
		if ( method_exists( $this, 'refresh_token_' . $provider_key ) ) {

			return call_user_func_array( array( $this, 'refresh_token_' . $provider_key ), array( $provider_config ) );

		}

		// otherwise, generic...

		$this->debug_message( 'Refreshing access token...' );

		// build args
		$args = array(

			'id'                      => $provider_config['id'],
			'secret'                  => $provider_config['secret'],
			'redirect_uri'            => $this->get_callback_url( $provider_key ),

			'urlAuthorize'            => $this->get_provider_detail( $provider_key, 'url_authorize' ),
			'urlAccessToken'          => $this->get_provider_detail( $provider_key, 'url_access_token' ),
			'urlResourceOwnerDetails' => $this->get_provider_detail( $provider_key, 'url_resource_owner_details' ),

		);

		// retrieve provider
		$provider = $this->provider( $provider_key, $args );

		try {

			// generate new token (generic)
			// https://oauth2-client.thephpleague.com/usage/#refreshing-a-token
			$new_token = $provider->getAccessToken(
				'refresh_token',
				array(
					'refresh_token' => $provider_config['refresh_token'],
				)
			);

			// success?
			if ( ! empty( $new_token->getToken() ) ) {

				$this->debug_message( 'Success!' );

				$new_refresh_token = $new_token->getRefreshToken();

				// when a new refresh token isn't set, keep the old one
				if ( empty( $new_refresh_token ) ) {
					$new_refresh_token = $provider_config['refresh_token'];
				}

				// save it
				$config                  = $this->get_provider_config( $provider_key, false, false );
				$config['token']         = $new_token->getToken();
				$config['refresh_token'] = $new_refresh_token;
				$config['expires']       = $new_token->getExpires();

				$this->update_provider_config( $provider_key, $config );

				// return it
				return $config;

			}
		} catch ( \League\OAuth2\Client\Provider\Exception\IdentityProviderException $e ) {

			$this->debug_message( 'Error refreshing token: ' . $e->getMessage() );

			switch ( $e->getMessage() ) {

				case 'invalid_request':
				default:
					// Something wrong with the request
					$this->debug_message( '<pre>' . print_r( $e->getResponseBody(), 1 ) . '</pre>' );

					// clear tokens
					$config                  = $this->get_provider_config( $provider_key, false, false );
					$config['token']         = '';
					$config['expires']       = '';
					$config['refresh_token'] = '';
					$this->update_provider_config( $provider_key, $config );

					break;
			}
		}

		return false;
	}

	/*
	 * Refreshes a Google mail user token and returns refreshed provider config
	 *
	 * @param array|bool $provider_config - either the config (if already available), or false (and this function will load it)
	 *
	 * @return array - updated token config
	*/
	public function refresh_token_google_mail( $provider_config = false ) {

		// if no config, load it
		if ( ! is_array( $provider_config ) ) {

			// note false for third parameter `refresh_token_if_expired` - this is to avoid an infinite loop
			$provider_config = $this->get_provider_config( 'google_mail', false, false );

		}

		// got refresh token?
		if ( ! isset( $provider_config['refresh_token'] ) || empty( $provider_config['refresh_token'] ) ) {

			return false;

		}

		// make sure our dependencies are installed
		$this->ensure_packages_installed();

		// build arguments
		$args = array(

			'id'     => $provider_config['id'],
			'secret' => $provider_config['secret'],

		);

		// retrieve provider
		$provider = $this->provider_google_mail( $args );

		// grant type
		$grant = new RefreshToken();

		$this->debug_message( 'Refreshing Google Token...' );

		try {

			$new_token         = $provider->getAccessToken( $grant, array( 'refresh_token' => $provider_config['refresh_token'] ) );
			$new_refresh_token = $new_token->getRefreshToken();

			// when a new refresh token isn't set, keep the old one
			if ( empty( $new_refresh_token ) ) {
				$new_refresh_token = $provider_config['refresh_token'];
			}

			$this->debug_message( 'Success!' );

			// save it
			$config                  = $this->get_provider_config( 'google_mail', false, false );
			$config['token']         = $new_token->getToken();
			$config['refresh_token'] = $new_refresh_token;
			$config['expires']       = $new_token->getExpires();

			$this->update_provider_config( 'google_mail', $config );

			return $config;

		} catch ( \League\OAuth2\Client\Provider\Exception\IdentityProviderException $e ) {

			$this->debug_message( 'Error refreshing Google token: ' . $e->getMessage() );
			$this->debug_message( '<pre>' . print_r( $e->getResponseBody(), 1 ) . '</pre>' );

			// clear expired/faulty token
			$config                  = $this->get_provider_config( 'google_mail', false, false );
			$config['token']         = '';
			$config['expires']       = '';
			$config['refresh_token'] = '';
			$this->update_provider_config( 'google_mail', $config );

		}

		return false;
	}

	/*
	 * Returns a connection status (bool) for provider from a provider key or config
	 * Note: Could be hardened by using oauth to re-verify valid token
	 *
	 * @param string $provider - e.g. 'google_mail'
	 * @param array|bool $provider_config - if passed, validity of config is checked
	*/
	public function connection_status( $provider, $provider_config = false ) {

		// load if not provided
		if ( ! is_array( $provider_config ) ) {
			$provider_config = $this->get_provider_config( $provider );
		}

		// got config?
		if ( is_array( $provider_config ) && ! empty( $provider_config['id'] ) && ! empty( $provider_config['secret'] ) ) {

			if (
				isset( $provider_config['refresh_token'] ) && ! empty( $provider_config['refresh_token'] )
				&&
				isset( $provider_config['expires'] ) && ! empty( $provider_config['expires'] )
			) {

				return true;

			}
		}

		return false;
	}

	/*
	 * Returns a connection status string for provider
	 *
	 * @param string $provider - e.g. 'google_mail'
	 * @param bool $shorthand - if true will return 'connected' instead of longform html status
	*/
	public function connection_status_string( $provider, $shorthand = false ) {

		$status       = __( 'Not yet configured', 'zero-bs-crm' );
		$short_status = 'no-config';

		$provider_config = $this->get_provider_config( $provider );

		// got config?
		if ( is_array( $provider_config ) && ! empty( $provider_config['id'] ) && ! empty( $provider_config['secret'] ) ) {

			$status       = __( 'Not yet connected', 'zero-bs-crm' );
			$short_status = 'config-no-connect';

			if (
				isset( $provider_config['refresh_token'] ) && ! empty( $provider_config['refresh_token'] )
				&&
				isset( $provider_config['expires'] ) && ! empty( $provider_config['expires'] )
			) {

				$short_status = 'seems-connected';

				// some providers can give specific user info to enrich the status string:
				switch ( $provider ) {

					case 'google_mail':
						$username = __( 'Generic User', 'zero-bs-crm' );
						if ( isset( $provider_config['meta'] ) && is_array( $provider_config['meta'] ) ) {

							if ( isset( $provider_config['meta']['name'] ) ) {

								$username = $provider_config['meta']['name'];

							}

							if ( isset( $provider_config['meta']['email'] ) ) {

								if ( ! empty( $username ) ) {
									$username .= ' ';
								}

								$username .= '(' . $provider_config['meta']['email'] . ')';

							}
						}

						$status = '<span class="ui green label">' . sprintf( __( 'Connected as %s', 'zero-bs-crm' ), $username ) . '</span>';

						break;

					default:
						$status = '<span class="ui green label">' . __( 'Connected', 'zero-bs-crm' ) . '</span>';

						break;

				}
			}
		}

		if ( $shorthand ) {

			return $short_status;

		}

		return $status;
	}

	/*
	 * Wrapper for outputting messages, abstracted in case we want to phase into a stack system
	 * There are surely better ways...
	*/
	private function display_alert( $msg_class = '', $msg_header = '', $msg_html = '', $icon_class = '', $id = '', $singular = false, $refresh_parent = false ) {

		// if $singular we're just showing the error as a single page (as used in OAuth flow)
		if ( $singular ) {

			?><html>
			<head>
				<link rel="stylesheet" type="text/css" href="<?php echo esc_url( plugins_url( '/build/lib/semantic-ui-css/semantic.min.css', ZBS_ROOTFILE ) ); /* phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet */ ?>">
				<style>
					body {
						vertical-align: middle;
						display: flex;
					}
					#jpcrm-oauth-error-wrapper {
						max-width: 600px;
						margin: auto;
						vertical-align: middle;
					}
				</style>
				<?php
				if ( $refresh_parent ) {
					?>
					<script>
						// reloads parent window
						window.opener.location.reload();
					</script>
				<?php } ?>
			</head>
			<body>
				<div id="jpcrm-oauth-error-wrapper">
					<?php

		}

		// err msg
		echo zeroBSCRM_UI2_messageHTML( $msg_class, $msg_header, $msg_html, $icon_class, $id );

		if ( $singular ) {

			?>
			</div></body></html>
			<?php

			exit( 0 );

		}
	}

	/*
	* Output debug, if enabled
	*/
	private function debug_message( $message ) {

		if ( $this->debug ) {

			echo $message;

		}
	}

	/**
	 * Returns an authorized Google API client.
	 *
	 * @param string $provider_key - key of OAuth provider to use
	 *
	 * @return Client the authorized client object
	 */
	// adapted from class-v3-zerobscrm-googlecontact.php
	function get_google_client( $provider_key ) {

		// make sure our dependencies are installed
		$this->ensure_packages_installed();

		// retrieve config
		$provider_config = $this->get_provider_config( $provider_key );

		if ( is_array( $provider_config ) && ! empty( $provider_config['token'] ) ) {

			// modified from: https://developers.google.com/people/quickstart/php  since we will always be getting 'offline' access so does not need to re-ask user
			$client = new \Google_Client();

			$client->setAccessType( 'offline' );
			$client->setClientId( $provider_config['id'] );
			$client->setClientSecret( $provider_config['secret'] );
			$client->setAccessToken( $provider_config['token'] );
			$client->setScopes( 'https://www.googleapis.com/auth/gmail.send' );

			return $client;

		}

		return false;
	}
}
