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

/**
 * Hybrid_Providers_Facebook provider adapter based on OAuth2 protocol
 * 
 * Hybrid_Providers_Facebook use the Facebook PHP SDK created by Facebook
 * 
 * http://hybridauth.sourceforge.net/userguide/IDProvider_info_Facebook.html
 */
class Hybrid_Providers_Facebook extends Hybrid_Provider_Model
{
	// default permissions, and a lot of them. You can change them from the configuration by setting the scope to what you want/need
	public $scope = "email, user_about_me, user_birthday, user_hometown, user_website, read_stream, publish_actions, read_friendlists";

	/**
	* IDp wrappers initializer 
	*/
	function initialize() 
	{
		if ( ! $this->config["keys"]["id"] || ! $this->config["keys"]["secret"] ){
			throw new Exception( "Your application id and secret are required in order to connect to {$this->providerId}.", 4 );
		}

		if ( ! class_exists('FacebookApiException', false) ) {
			require_once Hybrid_Auth::$config["path_libraries"] . "Facebook/base_facebook.php";
			require_once Hybrid_Auth::$config["path_libraries"] . "Facebook/facebook.php";
		}
		
		if ( isset ( Hybrid_Auth::$config["proxy"] ) ) {
			BaseFacebook::$CURL_OPTS[CURLOPT_PROXY] = Hybrid_Auth::$config["proxy"];
		}

		$trustForwarded = isset( $this->config['trustForwarded'] ) ? (bool) $this->config['trustForwarded'] : false;
		$this->api = new Facebook( ARRAY( 'appId' => $this->config["keys"]["id"], 'secret' => $this->config["keys"]["secret"], 'trustForwarded' => $trustForwarded ) );

		if ( $this->token("access_token") ) {
			$this->api->setAccessToken( $this->token("access_token") );
			$this->api->setExtendedAccessToken();
			$access_token = $this->api->getAccessToken();

			if( $access_token ){
				$this->token("access_token", $access_token );
				$this->api->setAccessToken( $access_token );
			}

			$this->api->setAccessToken( $this->token("access_token") );
		}

		$this->api->getUser();
	}

	/**
	* begin login step
	* 
	* simply call Facebook::require_login(). 
	*/
	function loginBegin()
	{
		$parameters = array("scope" => $this->scope, "redirect_uri" => $this->endpoint, "display" => "page");
		$optionals  = array("scope", "redirect_uri", "display", "auth_type");

		foreach ($optionals as $parameter){
			if( isset( $this->config[$parameter] ) && ! empty( $this->config[$parameter] ) ){
				$parameters[$parameter] = $this->config[$parameter];
				
				//If the auth_type parameter is used, we need to generate a nonce and include it as a parameter
				if($parameter == "auth_type"){
					$nonce = md5(uniqid(mt_rand(), true));
					$parameters['auth_nonce'] = $nonce;
					
					Hybrid_Auth::storage()->set('fb_auth_nonce', $nonce);
				}
			}
		}

        if( isset( $this->config[ 'force' ] ) && $this->config[ 'force' ] === true ){
            $parameters[ 'auth_type' ]  = 'reauthenticate';
            $parameters[ 'auth_nonce' ] = md5( uniqid( mt_rand(), true ) );

            Hybrid_Auth::storage()->set( 'fb_auth_nonce', $parameters[ 'auth_nonce' ] );
        }

		// get the login url 
		$url = $this->api->getLoginUrl( $parameters );

		// redirect to facebook
		Hybrid_Auth::redirect( $url );
	}

	/**
	* finish login step 
	*/
	function loginFinish()
	{ 
		// in case we get error_reason=user_denied&error=access_denied
		if ( isset( $_REQUEST['error'] ) && $_REQUEST['error'] == "access_denied" ){ 
			throw new Exception( "Authentication failed! The user denied your request.", 5 );
		}

		// in case we are using iOS/Facebook reverse authentication
		if(isset($_REQUEST['access_token'])){
			$this->token("access_token",  $_REQUEST['access_token'] );
			$this->api->setAccessToken( $this->token("access_token") );
			$this->api->setExtendedAccessToken();
			$access_token = $this->api->getAccessToken();

			if( $access_token ){
				$this->token("access_token", $access_token );
				$this->api->setAccessToken( $access_token );
			}

			$this->api->setAccessToken( $this->token("access_token") );
		}

		
		// if auth_type is used, then an auth_nonce is passed back, and we need to check it.
		if(isset($_REQUEST['auth_nonce'])){
			
			$nonce = Hybrid_Auth::storage()->get('fb_auth_nonce');
			
			//Delete the nonce
			Hybrid_Auth::storage()->delete('fb_auth_nonce');
			
			if($_REQUEST['auth_nonce'] != $nonce){
				throw new Exception( "Authentication failed! Invalid nonce used for reauthentication.", 5 );
			}
		}

		// try to get the UID of the connected user from fb, should be > 0 
		if ( ! $this->api->getUser() ){
			throw new Exception( "Authentication failed! {$this->providerId} returned an invalid user id.", 5 );
		}

		// set user as logged in
		$this->setUserConnected();

		// store facebook access token 
		$this->token( "access_token", $this->api->getAccessToken() );
	}

	/**
	* logout
	*/
	function logout()
	{ 
		$this->api->destroySession();

		parent::logout();
	}

	/**
	* load the user profile from the IDp api client
	*/
	function getUserProfile()
	{
		// request user profile from fb api
		try{ 
			$data = $this->api->api('/me'); 
		}
		catch( FacebookApiException $e ){
			throw new Exception( "User profile request failed! {$this->providerId} returned an error: $e", 6 );
		} 

		// if the provider identifier is not received, we assume the auth has failed
		if ( ! isset( $data["id"] ) ){ 
			throw new Exception( "User profile request failed! {$this->providerId} api returned an invalid response.", 6 );
		}

		# store the user profile.
		$this->user->profile->identifier    = (array_key_exists('id',$data))?$data['id']:"";
		$this->user->profile->username      = (array_key_exists('username',$data))?$data['username']:"";
		$this->user->profile->displayName   = (array_key_exists('name',$data))?$data['name']:"";
		$this->user->profile->firstName     = (array_key_exists('first_name',$data))?$data['first_name']:"";
		$this->user->profile->lastName      = (array_key_exists('last_name',$data))?$data['last_name']:"";
		$this->user->profile->photoURL      = "https://graph.facebook.com/" . $this->user->profile->identifier . "/picture?width=150&height=150";
		$this->user->profile->coverInfoURL  = "https://graph.facebook.com/" . $this->user->profile->identifier . "?fields=cover";
		$this->user->profile->profileURL    = (array_key_exists('link',$data))?$data['link']:""; 
		$this->user->profile->webSiteURL    = (array_key_exists('website',$data))?$data['website']:""; 
		$this->user->profile->gender        = (array_key_exists('gender',$data))?$data['gender']:"";
        	$this->user->profile->language      = (array_key_exists('locale',$data))?$data['locale']:"";
		$this->user->profile->description   = (array_key_exists('about',$data))?$data['about']:"";
		$this->user->profile->email         = (array_key_exists('email',$data))?$data['email']:"";
		$this->user->profile->emailVerified = (array_key_exists('email',$data))?$data['email']:"";
		$this->user->profile->region        = (array_key_exists("hometown",$data)&&array_key_exists("name",$data['hometown']))?$data['hometown']["name"]:"";
		
		if(!empty($this->user->profile->region )){
			$regionArr = explode(',',$this->user->profile->region );
			if(count($regionArr) > 1){
				$this->user->profile->city = trim($regionArr[0]);
				$this->user->profile->country = trim($regionArr[1]);
			}
		}
		
		if( array_key_exists('birthday',$data) ) {
			list($birthday_month, $birthday_day, $birthday_year) = explode( "/", $data['birthday'] );

			$this->user->profile->birthDay   = (int) $birthday_day;
			$this->user->profile->birthMonth = (int) $birthday_month;
			$this->user->profile->birthYear  = (int) $birthday_year;
		}

		return $this->user->profile;
 	}

	/**
	* Attempt to retrieve the url to the cover image given the coverInfoURL
	*
	* @param  string $coverInfoURL   coverInfoURL variable
	* @retval string                 url to the cover image OR blank string
	*/
	function getCoverURL($coverInfoURL)
	{
		try {
			$headers = get_headers($coverInfoURL);
			if(substr($headers[0], 9, 3) != "404") {
				$coverOBJ = json_decode(file_get_contents($coverInfoURL));
				if(array_key_exists('cover', $coverOBJ)) {
					return $coverOBJ->cover->source;
				}
			}
		} catch (Exception $e) { }

		return "";
	}
	
	/**
	* load the user contacts
	*/
	function getUserContacts()
	{
		$apiCall = '?fields=link,name';
		$returnedContacts = array();
		$pagedList = false;

		do {
			try{ 
				$response = $this->api->api('/me/friends' . $apiCall); 
			}
			catch( FacebookApiException $e ){
				throw new Exception( 'User contacts request failed! {$this->providerId} returned an error: $e' );
			} 

			// Prepare the next call if paging links have been returned
			if (array_key_exists('paging', $response) && array_key_exists('next', $response['paging'])) {
				$pagedList = true;
			        $next_page = explode('friends', $response['paging']['next']);
			        $apiCall = $next_page[1];
			}
			else{
				$pagedList = false;
			}

			// Add the new page contacts
			$returnedContacts = array_merge($returnedContacts, $response['data']);
		}
		while ($pagedList == true);

		$contacts = ARRAY();
 
		foreach( $returnedContacts as $item ){
			$uc = new Hybrid_User_Contact();

			$uc->identifier  = (array_key_exists("id",$item))?$item["id"]:"";
			$uc->displayName = (array_key_exists("name",$item))?$item["name"]:"";
			$uc->profileURL  = (array_key_exists("link",$item))?$item["link"]:"https://www.facebook.com/profile.php?id=" . $uc->identifier;
			$uc->photoURL    = "https://graph.facebook.com/" . $uc->identifier . "/picture?width=150&height=150";

			$contacts[] = $uc;
		}

		return $contacts;
 	}

    /**
    * update user status
	*
	* @param  string $pageid   (optional) User page id
    */
    function setUserStatus( $status, $pageid = null )
    {
        if( !is_array( $status ) ){
            $status = array( 'message' => $status );
        }

        if( is_null( $pageid ) ){
            $pageid = 'me';

        // if post on page, get access_token page
        }else{
            $access_token = null;
            foreach( $this->getUserPages( true ) as $p ){
                if( isset( $p[ 'id' ] ) && intval( $p['id'] ) == intval( $pageid ) ){
                    $access_token = $p[ 'access_token' ];
                    break;
                }
            }

            if( is_null( $access_token ) ){
                throw new Exception( "Update user page failed, page not found or not writable!" );
            }

            $status[ 'access_token' ] = $access_token;
        }

        try{ 
            $response = $this->api->api( '/' . $pageid . '/feed', 'post', $status );
        }
        catch( FacebookApiException $e ){
            throw new Exception( "Update user status failed! {$this->providerId} returned an error: $e" );
        }

        return $response;
    }


	/**
	* get user status
	*/
    function getUserStatus( $postid )
    {
		try{
            $postinfo = $this->api->api( "/" . $postid );
        }
		catch( FacebookApiException $e ){
			throw new Exception( "Cannot retrieve user status! {$this->providerId} returned an error: $e" );
		}

        return $postinfo;
    }


	/**
	* get user pages
	*/
    function getUserPages( $writableonly = false )
    {
        if( ( isset( $this->config[ 'scope' ] ) && strpos( $this->config[ 'scope' ], 'manage_pages' ) === false ) || ( !isset( $this->config[ 'scope' ] ) && strpos( $this->scope, 'manage_pages' ) === false ) )
            throw new Exception( "User status requires manage_page permission!" );            

        try{
            $pages = $this->api->api( "/me/accounts", 'get' );
        }
        catch( FacebookApiException $e ){
            throw new Exception( "Cannot retrieve user pages! {$this->providerId} returned an error: $e" );
        }

        if( !isset( $pages[ 'data' ] ) ){
            return array();
        }

        if( !$writableonly ){
            return $pages[ 'data' ];
        }

        $wrpages = array();
        foreach( $pages[ 'data' ] as $p ){
            if( isset( $p[ 'perms' ] ) && in_array( 'CREATE_CONTENT', $p[ 'perms' ] ) ){
                $wrpages[] = $p;
            }
        }

        return $wrpages;
    }

	/**
	* load the user latest activity  
	*    - timeline : all the stream
	*    - me       : the user activity only  
	*/
	function getUserActivity( $stream )
	{
		try{
			if( $stream == "me" ){
				$response = $this->api->api( '/me/feed' ); 
			}
			else{
				$response = $this->api->api('/me/home'); 
			}
		}
		catch( FacebookApiException $e ){
			throw new Exception( "User activity stream request failed! {$this->providerId} returned an error: $e" );
		} 

		if( ! $response || ! count(  $response['data'] ) ){
			return ARRAY();
		}

		$activities = ARRAY();

		foreach( $response['data'] as $item ){
			if( $stream == "me" && $item["from"]["id"] != $this->api->getUser() ){
				continue;
			}

			$ua = new Hybrid_User_Activity();

			$ua->id                 = (array_key_exists("id",$item))?$item["id"]:"";
			$ua->date               = (array_key_exists("created_time",$item))?strtotime($item["created_time"]):"";

			if( $item["type"] == "video" ){
				$ua->text           = (array_key_exists("link",$item))?$item["link"]:"";
			}

			if( $item["type"] == "link" ){
				$ua->text           = (array_key_exists("link",$item))?$item["link"]:"";
			}

			if( empty( $ua->text ) && isset( $item["story"] ) ){
				$ua->text           = (array_key_exists("link",$item))?$item["link"]:"";
			}

			if( empty( $ua->text ) && isset( $item["message"] ) ){
				$ua->text           = (array_key_exists("message",$item))?$item["message"]:"";
			}

			if( ! empty( $ua->text ) ){
				$ua->user->identifier   = (array_key_exists("id",$item["from"]))?$item["from"]["id"]:"";
				$ua->user->displayName  = (array_key_exists("name",$item["from"]))?$item["from"]["name"]:"";
				$ua->user->profileURL   = "https://www.facebook.com/profile.php?id=" . $ua->user->identifier;
				$ua->user->photoURL     = "https://graph.facebook.com/" . $ua->user->identifier . "/picture?type=square";

				$activities[] = $ua;
			}
		}

		return $activities;
 	}
}
