<?php
/*!
* HybridAuth
* http://hybridauth.sourceforge.net | https://github.com/hybridauth/hybridauth
*  (c) 2009-2011 HybridAuth authors | hybridauth.sourceforge.net/licenses.html
*/

/**
 * Hybrid_Provider_Adapter is the basic class which Hybrid_Auth will use
 * to connect users to a given provider. 
 * 
 * Basically Hybrid_Provider_Adapterwill create a bridge from your php 
 * application to the provider api.
 * 
 * Hybrid_Auth will automatically load Hybrid_Provider_Adapter and create
 * an instance of it for each authenticated provider.
 */
class Hybrid_Provider_Adapter
{
	/* Provider ID (or unique name) */
	public $id       = NULL ;

	/* Provider adapter specific config */
	public $config   = NULL ;

	/* Provider adapter extra parameters */
	public $params   = NULL ; 

	/* Provider adapter wrapper path */
	public $wrapper  = NULL ;

	/* Provider adapter instance */
	public $adapter  = NULL ;

	// --------------------------------------------------------------------

	/**
	* create a new adapter switch IDp name or ID
	*
	* @param string  $id      The id or name of the IDp
	* @param array   $params  (optional) required parameters by the adapter 
	*/
	function factory( $id, $params = NULL )
	{
		Hybrid_Logger::info( "Enter Hybrid_Provider_Adapter::factory( $id )" );

		# init the adapter config and params
		$this->id     = $id;
		$this->params = $params;
		$this->id     = $this->getProviderCiId( $this->id );
		$this->config = $this->getConfigById( $this->id );

		# check the IDp id
		if( ! $this->id ){
			throw new Exception( "No provider ID specified.", 2 ); 
		}

		# check the IDp config
		if( ! $this->config ){
			throw new Exception( "Unknown Provider ID, check your configuration file.", 3 ); 
		}

		# check the IDp adapter is enabled
		if( ! $this->config["enabled"] ){
			throw new Exception( "The provider '{$this->id}' is not enabled.", 3 );
		}

		# include the adapter wrapper
		if( isset( $this->config["wrapper"] ) && is_array( $this->config["wrapper"] ) ){
			require_once $this->config["wrapper"]["path"];

			if( ! class_exists( $this->config["wrapper"]["class"] ) ){
				throw new Exception( "Unable to load the adapter class.", 3 );
			}

			$this->wrapper = $this->config["wrapper"]["class"];
		}
		else{ 
			require_once Hybrid_Auth::$config["path_providers"] . $this->id . ".php" ;

			$this->wrapper = "Hybrid_Providers_" . $this->id; 
		}

		# create the adapter instance, and pass the current params and config
		$this->adapter = new $this->wrapper( $this->id, $this->config, $this->params );

		return $this;
	}

	// --------------------------------------------------------------------

	/**
	* Hybrid_Provider_Adapter::login(), prepare the user session and the authentification request
	* for index.php
	*/
	function login()
	{
		Hybrid_Logger::info( "Enter Hybrid_Provider_Adapter::login( {$this->id} ) " );

		if( ! $this->adapter ){
			throw new Exception( "Hybrid_Provider_Adapter::login() should not directly used." );
		}

		// clear all unneeded params
		foreach( Hybrid_Auth::$config["providers"] as $idpid => $params ){
			Hybrid_Auth::storage()->delete( "hauth_session.{$idpid}.hauth_return_to"    );
			Hybrid_Auth::storage()->delete( "hauth_session.{$idpid}.hauth_endpoint"     );
			Hybrid_Auth::storage()->delete( "hauth_session.{$idpid}.id_provider_params" );
		}

		// make a fresh start
		$this->logout();

		# get hybridauth base url
		$HYBRID_AUTH_URL_BASE = Hybrid_Auth::$config["base_url"];

		# we make use of session_id() as storage hash to identify the current user
		# using session_regenerate_id() will be a problem, but ..
		$this->params["hauth_token"] = session_id();

		# set request timestamp
		$this->params["hauth_time"]  = time();

		# for default HybridAuth endpoint url hauth_login_start_url
		# 	auth.start  required  the IDp ID
		# 	auth.time   optional  login request timestamp
		$this->params["login_start"] = $HYBRID_AUTH_URL_BASE . ( strpos( $HYBRID_AUTH_URL_BASE, '?' ) ? '&' : '?' ) . "hauth.start={$this->id}&hauth.time={$this->params["hauth_time"]}";

		# for default HybridAuth endpoint url hauth_login_done_url
		# 	auth.done   required  the IDp ID
		$this->params["login_done"]  = $HYBRID_AUTH_URL_BASE . ( strpos( $HYBRID_AUTH_URL_BASE, '?' ) ? '&' : '?' ) . "hauth.done={$this->id}";

		Hybrid_Auth::storage()->set( "hauth_session.{$this->id}.hauth_return_to"    , $this->params["hauth_return_to"] );
		Hybrid_Auth::storage()->set( "hauth_session.{$this->id}.hauth_endpoint"     , $this->params["login_done"] ); 
		Hybrid_Auth::storage()->set( "hauth_session.{$this->id}.id_provider_params" , $this->params );

		// store config to be used by the end point
		$_SESSION["HA::CONFIG"] = serialize( Hybrid_Auth::$config );

		// move on
		Hybrid_Logger::debug( "Hybrid_Provider_Adapter::login( {$this->id} ), redirect the user to login_start URL.", $this->params );

		Hybrid_Auth::redirect( $this->params["login_start"] );
	}

	// --------------------------------------------------------------------

	/**
	* let hybridauth forget all about the user for the current provider
	*/
	function logout()
	{
		$this->adapter->logout();
	}

	// --------------------------------------------------------------------

	/**
	* return true if the user is connected to the current provider
	*/ 
	public function isUserConnected()
	{
		return $this->adapter->isUserConnected();
	}

	// --------------------------------------------------------------------

	/**
	* handle :
	*   getUserProfile()
	*   getUserContacts()
	*   getUserActivity() 
	*   setUserStatus() 
	*/ 
	public function __call( $name, $arguments ) 
	{
		Hybrid_Logger::info( "Enter Hybrid_Provider_Adapter::$name(), Provider: {$this->id}" );

		if ( ! $this->isUserConnected() ){
			throw new Exception( "User not connected to the provider {$this->id}.", 7 );
		} 

		if ( ! method_exists( $this->adapter, $name ) ){
			throw new Exception( "Call to undefined function Hybrid_Providers_{$this->id}::$name()." );
		}

		if( count( $arguments ) ){
			return $this->adapter->$name( $arguments[0] ); 
		} 
		else{
			return $this->adapter->$name(); 
		}
	}

	// --------------------------------------------------------------------

	/**
	* If the user is connected, then return the access_token and access_token_secret
	* if the provider api use oauth
	*/
	public function getAccessToken()
	{
		if( ! $this->adapter->isUserConnected() ){
			Hybrid_Logger::error( "User not connected to the provider." );

			throw new Exception( "User not connected to the provider.", 7 );
		}

		return
			ARRAY(
				"access_token"        => $this->adapter->token( "access_token" )       , // OAuth access token
				"access_token_secret" => $this->adapter->token( "access_token_secret" ), // OAuth access token secret
				"refresh_token"       => $this->adapter->token( "refresh_token" )      , // OAuth refresh token
				"expires_in"          => $this->adapter->token( "expires_in" )         , // OPTIONAL. The duration in seconds of the access token lifetime
				"expires_at"          => $this->adapter->token( "expires_at" )         , // OPTIONAL. Timestamp when the access_token expire. if not provided by the social api, then it should be calculated: expires_at = now + expires_in
			);
	}

	// --------------------------------------------------------------------

	/**
	* Naive getter of the current connected IDp API client
	*/
	function api()
	{
		if( ! $this->adapter->isUserConnected() ){
			Hybrid_Logger::error( "User not connected to the provider." );

			throw new Exception( "User not connected to the provider.", 7 );
		}

		return $this->adapter->api;
	}

	// --------------------------------------------------------------------

	/**
	* redirect the user to hauth_return_to (the callback url)
	*/
	function returnToCallbackUrl()
	{ 
		// get the stored callback url
		$callback_url = Hybrid_Auth::storage()->get( "hauth_session.{$this->id}.hauth_return_to" );

		// remove some unneed'd stored data 
		Hybrid_Auth::storage()->delete( "hauth_session.{$this->id}.hauth_return_to"    );
		Hybrid_Auth::storage()->delete( "hauth_session.{$this->id}.hauth_endpoint"     );
		Hybrid_Auth::storage()->delete( "hauth_session.{$this->id}.id_provider_params" );

		// back to home
		Hybrid_Auth::redirect( $callback_url );
	}

	// --------------------------------------------------------------------

	/**
	* return the provider config by id
	*/
	function getConfigById( $id )
	{ 
		if( isset( Hybrid_Auth::$config["providers"][$id] ) ){
			return Hybrid_Auth::$config["providers"][$id];
		}

		return NULL;
	}

	// --------------------------------------------------------------------

	/**
	* return the provider config by id; insensitive
	*/
	function getProviderCiId( $id )
	{
		foreach( Hybrid_Auth::$config["providers"] as $idpid => $params ){
			if( strtolower( $idpid ) == strtolower( $id ) ){
				return $idpid;
			}
		}

		return NULL;
	}
}
