<?php
/*!
* HybridAuth
* http://hybridauth.sourceforge.net | http://github.com/hybridauth/hybridauth
* (c) 2009-2012, HybridAuth authors | http://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
    */
    public 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 authentication request
    * for index.php
    */
    public 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
        Hybrid_Auth::storage()->config( "CONFIG", Hybrid_Auth::$config );

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

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

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

    /**
    * let hybridauth forget all about the user for the current provider
    */
    public 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
    */
    public 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)
    */
    public 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
    */
    public 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
    */
    public function getProviderCiId( $id )
    {
        foreach (Hybrid_Auth::$config["providers"] as $idpid => $params) {
            if ( strtolower( $idpid ) == strtolower( $id ) ) {
                return $idpid;
            }
        }

        return NULL;
    }
}
