<?php
/**
 * HybridAuth Integration class
 *
 * @package um_ext\um_social_login\core\Social_Login_Hybridauth
 */

namespace um_ext\um_social_login\core;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// Include Composer's autoloader.
require_once UM_SOCIAL_LOGIN_PATH . 'vendor/autoload.php';

use Exception;
use Hybridauth\Storage\Session;
/**
 * Class Social_Login_Hybridauth
 *
 * @package um_ext\um_social_login\core
 */
class Social_Login_Hybridauth {

	/**
	 * Networks
	 *
	 * @var array
	 */
	public $networks;

	/**
	 * Load settions on wp action hook
	 *
	 * @var $um_session
	 */
	public $um_session;

	/**
	 * Social Networks Providers
	 *
	 * @var array
	 */
	public $all_networks = array();

	/**
	 * Active Social Network Providers
	 *
	 * @var array
	 */
	public $available_networks = array();

	/**
	 * Times connectUser is called
	 *
	 * @var integer
	 */
	public $called = 0;

	/**
	 * __construct
	 */
	public function __construct() {

		if ( is_admin() ) {
			return;
		}

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}

		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return;
		}

		/**
		 * Cache compatibility
		 */
		add_action(
			'after_setup_theme',
			function () {
				/**
				 * Load hybridauth library to specific pages e.g. /login/ or /register/
				 */
				$um_urls = apply_filters( 'um_sso_load_hybridauth', array() );

				$request_uri = ! empty( $_SERVER['REQUEST_URI'] ) ? sanitize_key( $_SERVER['REQUEST_URI'] ) : '';

				$has_url = false;
				foreach ( $um_urls as $url ) {
					if ( strpos( $request_uri, $url ) !== false ) {
						$has_url = true;
						break;
					}
				}

				if ( $has_url || empty( $um_urls ) ) {
					add_action( 'wp', array( &$this, 'wp_init' ) );
				}
			}
		);

		add_action( 'init', array( &$this, 'init' ), 1 );
	}

	/**
	 * WP Init
	 *
	 * @version 2.5.3 Added conditions to avoid a conflict with the site health tool.
	 */
	public function wp_init() {

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}
		if ( wp_doing_ajax() ) {
			return;
		}
		if ( wp_doing_cron() ) {
			return;
		}
		if ( is_admin() ) {
			return;
		}
		if ( headers_sent() ) {
			return;
		}

		$this->session();
	}

	/**
	 * Init
	 */
	public function init() {
		$this->all_networks       = apply_filters( 'um_social_login_networks', array() );
		$this->available_networks = UM()->Social_Login_API()->available_networks();
	}


	/**
	 * A shortcut for the session object.
	 *
	 * @since 2.5.3
	 */
	public function session() {
		if ( empty( $this->um_session ) || ! is_object( $this->um_session ) ) {
			if ( session_id() === '' ) {
				session_start();
			}
			if ( session_status() === PHP_SESSION_NONE ) {
				session_start();
			}

			try {
				$this->um_session = new Session();
			} catch ( Exception $e ) {
				$response = array(
					'Social Login - Session Error',
					$e->getMessage(),
					'ID:' . session_id(),
					'Status: ' . session_status(),
				);
				$this->log_error( implode( "\n", $response ) );
			}
		}
		return $this->um_session;
	}


	/**
	 * Get Session
	 *
	 * @deprecated since version 2.5.3
	 *
	 * @return object
	 */
	public function get_session() {
		_deprecated_function( __METHOD__, '2.5.3', 'UM()->Social_Login_API()->hybridauth()->session()' );

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}

		if ( is_admin() ) {
			return;
		}

		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return;
		}

		if ( wp_doing_cron() ) {
			return;
		}

		if ( headers_sent() ) {
			return;
		}

		if ( session_id() === '' ) { // fallback when Session is disabled.
			session_start();
		}

		if ( session_status() === PHP_SESSION_NONE ) {
			// session has not started.
			session_start();
		}

		$storage = new Session();

		try {
			$storage->get( 'sso_last_auth_provider' );
		} catch ( Exception $e ) {
			$response = array(
				'Social Login - Session Error',
				$e->getMessage(),
				'ID:' . session_id(),
				'Status: ' . session_status(),
			);
			$this->log_error( implode( "\n", $response ) );
		}

		return $storage;
	}

	/**
	 * Connect a user with a provider account
	 *
	 * @param  string $provider Social Provider id.
	 * @param  string $callback_url Site's callback URL.
	 * @param  object $sso_session Session storage.
	 * @param  bool   $session_loaded Session has loaded.
	 */
	public function connect_user( $provider = '', $callback_url = '', $sso_session = '', $session_loaded = false ) {

		if ( file_exists( UM_SOCIAL_LOGIN_PATH . 'includes/core/providers/' . strtolower( $provider ) . '.php' ) ) {
			require_once UM_SOCIAL_LOGIN_PATH . 'includes/core/providers/' . strtolower( $provider ) . '.php';
		}

		$config = $this->get_config( $provider );

		if ( empty( $config['keys'] ) ) {
			return;
		}

		if ( ! $sso_session ) {
			$sso_session = $this->session();
		}

		if ( isset( $config['provider'] ) ) {
			$provider = $config['provider'];
		}

		if ( ! empty( $callback_url ) ) {
			$config['callback'] = $callback_url;
		}

		if ( ! wp_http_validate_url( $config['returnUrl'] ) ) {
			wp_safe_redirect( UM()->permalinks()->get_current_url( get_option( 'permalink_structure' ) ) );
		}

		$provider_class     = "Hybridauth\Provider\\$provider";
		$hybridauth_adapter = null;

		if ( class_exists( $provider_class ) ) {
			try {

				$hybridauth_adapter = new $provider_class( $config );
				$provider           = strtolower( $provider );
				$access_token       = $hybridauth_adapter->getAccessToken();

				if ( ! empty( $access_token ) && ! isset( $_REQUEST['um_sso'] ) && ! defined( 'UM_SSO_PARENT_WINDOW' ) ) { //phpcs:ignore  WordPress.Security.NonceVerification
					return array(
						'hybridauth'  => $hybridauth_adapter,
						'userProfile' => $hybridauth_adapter->getUserProfile(),
						'callbackUrl' => $config['callback'],
						'returnUrl'   => $this->session()->get( 'sso_last_auth_page' ),
					);
				}

				if ( isset( $_REQUEST['provider'] ) && empty( $access_token ) ) { //phpcs:ignore  WordPress.Security.NonceVerification
					$this->session()->set( 'sso_last_auth_provider', $config['provider_slug'] );
					$this->session()->set( 'sso_last_auth_page', $config['returnUrl'] );

					if ( isset( $_REQUEST['um_sso'] ) ) { //phpcs:ignore  WordPress.Security.NonceVerification
						$this->session()->set( 'um_sso', sanitize_key( $_REQUEST['um_sso'] ) ); //phpcs:ignore  WordPress.Security.NonceVerification
					}

					$hybridauth_adapter->authenticate();
				}

				$user_profile = $hybridauth_adapter->getUserProfile();
				$this->session()->set( 'um_sso_has_authenticated', true );

				$user_profile = apply_filters( "um_social_login_get_user_profile__{$config['provider_slug']}", $user_profile, $hybridauth_adapter, $access_token );
				$user_profile = apply_filters( 'um_social_login_get_user_profile', $user_profile, $hybridauth_adapter, $access_token );

				// oAuth 2.
				if ( $access_token ) {

					return array(
						'hybridauth'  => $hybridauth_adapter,
						'userProfile' => $user_profile,
						'callbackUrl' => $config['callback'],
						'returnUrl'   => $config['returnUrl'],
					);

				}

				// OpenID.
				if ( $user_profile ) {

					return array(
						'hybridauth'  => $hybridauth_adapter,
						'userProfile' => $user_profile,
						'callbackUrl' => $config['callback'],
						'returnUrl'   => $config['returnUrl'],
					);

				}

				if ( ! $user_profile || ! $access_token ) {
					return array(
						'hybridauth'  => $hybridauth_adapter,
						'has_errors'  => true,
						'callbackUrl' => $config['callback'],
						'returnUrl'   => $config['returnUrl'],
					);
				}
			} catch ( \Hybridauth\Exception\HttpClientFailureException $e ) {

				$response = array(
					'Error:'       => $hybridauth_adapter->getHttpClient()->getResponseClientError(),
					'callbackUrl:' => $config['callback'],
					'returnUrl:'   => $config['returnUrl'],
					'type:'        => 'Curl text ',
				);
				$this->log_error( implode( "\n", $response ) );

				$this->session()->delete( 'sso_last_auth_provider' );

				return array(
					'hybridauth'         => $hybridauth_adapter,
					'has_errors'         => true,
					'raw_error_response' => $e->getMessage(),
					'raw_error'          => $e,
					'error_message'      => '',
					'callbackUrl'        => $config['callback'],
					'returnUrl'          => $config['returnUrl'],
				);

			} catch ( \Hybridauth\Exception\HttpRequestFailedException $e ) {

				$response = array(
					'Error:'            => $hybridauth_adapter->getHttpClient()->getResponseBody(),
					'callbackUrl:'      => $config['callback'],
					'returnUrl:'        => $config['returnUrl'],
					'type:'             => 'Raw API Response',
					'provider'          => 'Provider: ' . $provider,
					'storage_init'      => 'Storage Init: ' . $session_loaded,
					'hook'              => current_filter(),
					'has_authenticated' => $this->session()->get( 'um_sso_has_authenticated' ),
				);
				$this->log_error( implode( "\n", $response ) );
				$this->session()->delete( 'sso_last_auth_provider' );

				return array(
					'hybridauth'         => $hybridauth_adapter,
					'has_errors'         => true,
					'raw_error_response' => $e->getMessage(),
					'raw_error'          => $e,
					'error_message'      => '',
					'callbackUrl'        => $config['callback'],
					'returnUrl'          => $config['returnUrl'],
				);

			} catch ( \Exception $e ) {

				$response = array(
					'Error:'       => $e->getMessage(),
					'provider'     => $provider,
					'callbackUrl:' => $config['callback'],
					'returnUrl:'   => $config['returnUrl'],
					'type:'        => 'default error',
				);
				$this->log_error( implode( "\n", $response ) );

				$this->session()->delete( 'sso_last_auth_provider' );

				return array(
					'hybridauth'         => $hybridauth_adapter,
					'has_errors'         => true,
					'raw_error_response' => $e->getMessage(),
					'raw_error'          => $e,
					'error_message'      => '',
					'callbackUrl'        => $config['callback'],
					'returnUrl'          => $config['returnUrl'],
				);
			}
		}
	}

	/**
	 * Get Config
	 *
	 * @param string $provider Social Provider's ID.
	 *
	 * @return array $config
	 */
	public function get_config( $provider = '' ) {

		if ( empty( $provider ) ) {
			return false;
		}

		if ( ! isset( $this->available_networks[ $provider ] ) && ! empty( $provider ) ) {
			return;
		}

		$arr_providers = array();
		$config        = array();

		foreach ( $this->available_networks as $p_key => $p_data ) {
			$arr_providers[ $p_key ] = array(
				'enabled'       => true,
				'keys'          => $this->get_keys( $p_data['opts'] ),
				'provider'      => $p_data['hybridauth_key'],
				'provider_slug' => $p_key,
			);
		}

		$config['callback']     = $this->get_callback_url( $provider );
		$config['returnUrl']    = $this->get_return_url( $provider, $config['callback'] );
		$config['redirect_uri'] = $this->get_return_url( $provider, $config['callback'] );

		if ( $provider ) {
			$config = array_merge( $config, $arr_providers[ $provider ] );
		} else {
			$config['providers'] = $arr_providers;
		}

		/**
		 * Example usage:
		 * add_filter("um_social_login_{$provider}__config", function( $config ){
		 * $config[
		 *          'scope' => ['r_liteprofile', ... ],
		 *          'enabled' => true,
		 *          'keys' => [
		 *                   'key' => '...',
		 *                   'secret' => '...',
		 *          ]
		 *      ],
		 *   return $config;
		 * });
		 */
		return apply_filters( "um_social_login_{$provider}__config", $config );
	}

	/**
	 * Get Provider's API keys
	 *
	 * @param  array $options  API keys.
	 * @return array $arr_keys API Keys setup.
	 */
	public function get_keys( $options ) {

		$arr_keys         = array();
		$arr_options_keys = array_keys( $options );
		foreach ( $arr_options_keys as $key ) {

			if ( strrpos( $key, 'id' ) ) {
				$arr_keys['id'] = UM()->options()->get( $key );
			}

			if ( strrpos( $key, 'key' ) ) {
				$arr_keys['key'] = UM()->options()->get( $key );
			}

			if ( strrpos( $key, 'secret' ) ) {
				$arr_keys['secret'] = UM()->options()->get( $key );
			}
		}

		return $arr_keys;
	}

	/**
	 * Get current URL
	 *
	 * @return string $current_url Site's current URL excluding specific parameters.
	 */
	public function get_current_url() {

		global $wp;

		$current_url = '';

		$current_url = home_url( add_query_arg( array(), $wp->request ) );

		$current_url = remove_query_arg( array( 'provider', 'state', 'code' ), $current_url );

		// Reject all file URLs from current URL.
		if ( false !== strpos( $current_url, '/wp-content/' ) ) {
			exit;
		}

		return $current_url;
	}

	/**
	 * Get Login URL
	 *
	 * @param string  $provider Social Provider's ID.
	 * @param boolean $is_shortcode determine if social connection is via Shortcode.
	 *
	 * @return string
	 */
	public function get_connect_url( $provider = '', $is_shortcode = false ) {

		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return;
		}
		// phpcs:disable WordPress.Security.NonceVerification
		if ( isset( $_REQUEST['provider'] ) || isset( $_REQUEST['code'] ) || isset( $_REQUEST['state'] ) ) {
			return;
		}

		if ( isset( $_REQUEST['um_dynamic_sso'] ) && ! isset( $_REQUEST['err'] ) ) {
			return;
		}
		// phpcs:enable WordPress.Security.NonceVerification

		$current_url = add_query_arg( 'oauthWindow', 'true', '' );

		$current_url = add_query_arg( 'provider', $provider, $current_url );

		if ( $is_shortcode > 0 ) {

			$return_url  = $this->get_current_url();
			$current_url = add_query_arg( 'um_sso', $is_shortcode, $current_url );
			$this->session()->set( 'um_sso_has_dynamic_return_url', true );
			$this->session()->set( 'um_sso_current_url', $return_url );
			$this->session()->set( 'um_social_login_redirect', $return_url );

		} else {
			$this->session()->set( 'um_sso_has_dynamic_return_url', null );
			if ( ( um_is_core_page( 'login' ) || um_is_core_page( 'register' ) ) && ! isset( $_REQUEST['return_provider'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
				$this->session()->set( 'um_sso', null );
			}

			$this->session()->set( '_um_shortcode_id', null );

			$return_url = $this->get_current_url();
			$this->session()->set( 'um_sso_current_url', $return_url );
		}

		if ( ! empty( $_REQUEST['redirect_to'] ) ) {  // phpcs:ignore WordPress.Security.NonceVerification
			$this->session()->set( 'um_social_login_redirect', esc_url_raw( $_REQUEST['redirect_to'] ) );  // phpcs:ignore
		}

		$current_url = apply_filters( 'um_social_login_connect_url', $current_url, $provider );
		$current_url = apply_filters( "um_social_login_connect_url__{$provider}", $current_url );

		return $current_url;
	}

	/**
	 * Get Disconnect Url
	 *
	 * @param  string $provider Social Provider ID.
	 * @param  string $user_id Current User's ID.
	 * @return string $current_url The current site's URL with extra parameters.
	 */
	public function get_disconnect_url( $provider = '', $user_id = null ) {
		$user_id;
		if ( isset( $_REQUEST['provider'] ) ) {  // phpcs:ignore WordPress.Security.NonceVerification
			return;
		}

		$current_url = add_query_arg( 'disconnect', $provider, '' );
		$current_url = add_query_arg( '_nonce', wp_create_nonce( 'um-sso-disconnect-' . $provider ), $current_url );

		return $current_url;
	}

	/**
	 * Get Callback URL
	 *
	 * @param  string $provider Network provider.
	 * @param string $return_url Site's current return URL.
	 * @return string $callback_url Site's current callback URL.
	 */
	public function get_callback_url( $provider = '', $return_url = false ) {
		$return_url;
		if ( um_is_core_page( 'login' ) ) {
			$callback_url = um_get_core_page( 'login' );
		} elseif ( um_is_core_page( 'register' ) ) {
			$callback_url = um_get_core_page( 'register' );
		} elseif ( um_is_core_page( 'account' ) ) {
			$callback_url = str_replace( '//social', '/social', um_get_core_page( 'account' ) . '/social/' );
		} else {
			$callback_url = um_get_core_page( 'login' );
		}

		$callback_url = add_query_arg( 'provider', $provider, $callback_url );

		$callback_url = remove_query_arg( 'err', $callback_url );

		$callback_url = remove_query_arg( 'oauthWindow', $callback_url );

		$callback_url = apply_filters( 'um_social_login_callback_url', $callback_url, $provider );

		$callback_url = apply_filters( "um_social_login_callback_url__{$provider}", $callback_url );

		return $callback_url;
	}

	/**
	 * Get return Url
	 *
	 * @param  string $provider Social Provider ID.
	 * @param  string $callback_url Site's Callback URL.
	 * @return string $return_url Return URL.
	 */
	public function get_return_url( $provider = '', $callback_url = '' ) {

		$callback_url = $this->session()->get( 'um_sso_current_url' );

		if ( isset( $_REQUEST['redirect_to'] ) ) {  // phpcs:ignore WordPress.Security.NonceVerification
			$callback_url = esc_url_raw( $_REQUEST['redirect_to'] );  // phpcs:ignore
		}

		$return_url = remove_query_arg( 'provider', $callback_url );

		$has_dynamic_sso = $this->session()->get( 'um_sso' );

		if ( $has_dynamic_sso && ! um_is_core_page( 'account' ) && ! um_is_core_page( 'login' ) ) {

			$return_url = remove_query_arg( 'provider', $return_url );

			$return_url = add_query_arg( 'um_dynamic_sso', 1, $return_url );

		}

		$return_url = add_query_arg( 'return_provider', $provider, $return_url );

		return $return_url;
	}

	/**
	 * Jsdump browser console for debugging purposes
	 *
	 * @param  array $args mixed parameters to dump.
	 */
	public function jsdump( $args ) {
		echo '<script>console.log(' . esc_js( wp_json_encode( $args ) ) . ');</script>';
	}

	/**
	 * Log errors in a text file
	 *
	 * @param string $logs object response from HybridAuth.
	 */
	public function log_error( $logs ) {
		global $wp_filesystem;
		if ( empty( UM()->options()->get( 'enable_debug_logs' ) ) ) {
			return;
		}

		update_option( 'um_sso_error_log_timestamp', gmdate( 'U' ) );

		// Initialize the WP filesystem, no more using 'file-put-contents' function.
		if ( empty( $wp_filesystem ) ) {
			require_once ABSPATH . '/wp-admin/includes/file.php';
			WP_Filesystem();
		}
		$response = '';
		$file     = WP_CONTENT_DIR . '/um-sso-logs-' . md5( site_url() ) . '.txt';
		if ( file_exists( $file ) ) {
			$response = $wp_filesystem->get_contents( $file );
		}
		$response .= "\n\n" . gmdate( 'F d, Y - h:i:sA' ) . ":\n" . $logs . "\n";
		$wp_filesystem->put_contents( $file, $response );
	}
}
