<?php
/***************************************************************************
 *
 * Copyright (c) 2011 Baidu.com, Inc. All Rights Reserved
 *
 **************************************************************************/

/**
 * @author zhujianting(zhujianting@baidu.com)
 *
 * @version v2.0.0
 */
class BaiduAPI
{
    /**
     * The client_id of the app or access_key of the developer.
     *
     * @var string
     */
    protected $clientId;

    /**
     * The client_secret of the app or secret_key of the developer.
     *
     * @var string
     */
    protected $clientSecret;

    /**
     * Redirect uri of the app, where we will redirect to after user authorization.
     *
     * @var string
     */
    protected $redirectUri;

    /**
     * Storage for the user session related datas, like state, authorization code,
     * access token and so on.
     *
     * @var BaiduStore
     */
    protected $store = null;

    /**
     * @var string
     */
    protected $state = null;

    /**
     * User session info.
     *
     * @var array
     */
    protected $session = null;

    /**
     * @var BaiduOAuth2
     */
    protected $oauth2 = null;

    /**
     * Constructor.
     *
     * @param string     $clientId     The client_id of the app or access_key of the developer.
     * @param string     $clientSecret The client_secret of the app or secret_key of the developer.
     * @param string     $redirectUri  Redirect uri of the app.
     * @param BaiduStore $store        Storage for the user session related datas.
     */
    public function __construct($clientId, $clientSecret, $redirectUri, $store = null)
    {
        $this->clientId = $clientId;
        $this->clientSecret = $clientSecret;
        $this->redirectUri = $redirectUri;
        $this->setStore($store ? $store : new BaiduCookieStore($clientId));
    }

    /**
     * Get an instance of BaiduOAuth2 class.
     *
     * @return BaiduOAuth2
     */
    public function getBaiduOAuth2Service()
    {
        if (!$this->oauth2) {
            $this->oauth2 = new BaiduOAuth2($this->clientId, $this->clientSecret);
            $this->oauth2->setRedirectUri($this->redirectUri);
        }

        return $this->oauth2;
    }

    /**
     * Get an instance of BaiduApiClient class.
     *
     * @param string $accessToken Access token for api calls.
     *
     * @return BaiduApiClient
     */
    public function getBaiduApiClientService()
    {
        return new BaiduApiClient($this->clientId, $this->getAccessToken());
    }

    /**
     * Get access token for openapi calls.
     *
     * @return string|false Returns access token if user has authorized the app, or false if not.
     */
    public function getAccessToken()
    {
        $session = $this->getSession();
        if (isset($session['access_token'])) {
            return $session['access_token'];
        } else {
            return false;
        }
    }

    /**
     * Get refresh token.
     *
     * @return string|false Returns refresh token if app has, or false if not.
     */
    public function getRefreshToken()
    {
        $session = $this->getSession();
        if (isset($session['refresh_token'])) {
            return $session['refresh_token'];
        } else {
            return false;
        }
    }

    /**
     * Get currently logged in user's uid.
     *
     * @return uint|false Return uid of the loggedin user, or return
     *                    false if user isn't loggedin.
     */
    public function getLoggedInUser()
    {
        // Get user from cached data or from access token
        $user = $this->getUser();

        // If there's bd_sig & bd_user parameter in query parameters,
        // it must be an inside web app(app on baidu) loading request,
        // then we must check whether the uid passed from baidu is the
        // same as we get from persistent data or from access token,
        // if it's not, we should clear all the persistent data and to
        // get an access token again.
        if (isset($_REQUEST['bd_sig']) && isset($_REQUEST['bd_user'])) {
            $params = array('bd_user' => $_REQUEST['bd_user']);
            $sig = BaiduUtils::generateSign($params, $this->clientSecret, 'bd_sig');
            if ($sig != $_REQUEST['bd_sig'] || $user['uid'] != $_REQUEST['bd_user']) {
                $this->store->remove('session');

                return false;
            }
        }

        return $user;
    }

    /**
     * Get a Login URL for use with redirects. By default, full page redirect is
     * assumed. If you are using the generated URL with a window.open() call in
     * JavaScript, you can pass in display=popup as part of the $params.
     *
     * @param string $scope   blank space separated list of requested extended perms
     * @param string $display Authorization page style, 'page', 'popup', 'touch' or 'mobile'
     *
     * @return string the URL for the login flow
     */
    public function getLoginUrl($scope = '', $display = 'page')
    {
        $oauth2 = $this->getBaiduOAuth2Service();

        return $oauth2->getAuthorizeUrl('code', $scope, $this->state, $display);
    }

    /**
     * Get the Logout URL suitable for use with redirects.
     *
     * @param string $next Url to go to after a successful logout.
     *
     * @return string
     */
    public function getLogoutUrl($next)
    {
        $oauth2 = $this->getBaiduOAuth2Service();

        return $oauth2->getLogoutUrl($this->getAccessToken(), $next);
    }

    /**
     * Get user session info.
     *
     * @return array
     */
    public function getSession()
    {
        if ($this->session === null) {
            $this->session = $this->doGetSession();
        }

        return $this->session;
    }

    /**
     * Set user session.
     *
     * @param array $session User session info.
     *
     * @return Baidu
     */
    public function setSession($session)
    {
        $this->session = $session;
        if ($session) {
            $this->store->set('session', $session);
        } else {
            $this->store->remove('session');
        }

        return $this;
    }

    /**
     * Get current user's uid and uname.
     *
     * @return array|false array('uid' => xx, 'uname' => xx)
     */
    protected function getUser()
    {
        $session = $this->getSession();
        if (is_array($session) && isset($session['uid']) && isset($session['uname'])) {
            return array('uid' => $session['uid'], 'uname' => $session['uname']);
        } else {
            return false;
        }
    }

    /**
     * Set the session data storage instance.
     *
     * @param BaiduStore $store
     *
     * @return Baidu
     */
    protected function setStore($store)
    {
        $this->store = $store;
        if ($this->store) {
            $state = $this->store->get('state');
            if (!empty($state)) {
                $this->state = $state;
            }
            //as the storage engine is changed, we need to get the session again.
            $this->session = null;
            $this->getSession();
            $this->establishCSRFTokenState();
        }

        return $this;
    }

    /**
     * Get session info from Baidu server or from the store in app server side.
     *
     * @return array|false
     */
    protected function doGetSession()
    {
        // get authorization code from query parameters
        $code = $this->getCode();
        // check whether it is a CSRF attack request
        if ($code && $code != $this->store->get('code')) {
            $oauth2 = $this->getBaiduOAuth2Service();
            $session = $oauth2->getAccessTokenByAuthorizationCode($code);
            if ($session) {
                $this->store->set('code', $code);
                $this->setSession($session);
                $apiClient = new BaiduApiClient($this->clientId, $session['access_token']);
                $user = $apiClient->api('passport/users/getLoggedInUser');
                if ($user) {
                    $session = array_merge($session, $user);
                    $this->setSession($session);
                }

                return $session;
            }

            // code was bogus, so everything based on it should be invalidated.
            $this->store->removeAll();

            return false;
        }

        // as a fallback, just return whatever is in the storage
        $session = $this->store->get('session');
        $this->setSession($session);
        if ($session && !isset($session['uid'])) {
            $apiClient = new BaiduApiClient($this->clientId, $session['access_token']);
            $user = $apiClient->api('passport/users/getLoggedInUser');
            if ($user) {
                $session = array_merge($session, $user);
                $this->setSession($session);
            }
        }

        return $session;
    }

    /**
     * Get the authorization code from the query parameters, if it exists,
     * otherwise return false to signal no authorization code was discoverable.
     *
     * @return mixed Returns the authorization code, or false if the authorization
     *               code could not be determined.
     */
    protected function getCode()
    {
        if (isset($_GET['code'])) {
            if ($this->state && $this->state === $_GET['state']) {
                // CSRF state has done its job, so clear it
                $this->state = null;
                $this->store->remove('state');

                return $_GET['code'];
            } else {
                BaiduUtils::errorLog('CSRF state token does not match one provided.');

                return false;
            }
        }

        return false;
    }

    /**
     * Lays down a CSRF state token for this process.
     */
    protected function establishCSRFTokenState()
    {
        if ($this->state === null) {
            $this->state = md5(uniqid(mt_rand(), true));
            $this->store->set('state', $this->state);
        }
    }
}
