Creating Facebook Connect

I’m developing Facebook apps.

My goal is to copy the Facebook’s The Run Around (The Run Around) connect mechanism into my application.

Please be warned, that this is my first PHP application. So, please bear with me if there’re a lot of unusual PHP method/convention/way in my code.

After googling,compare some framework, I decide to pick Yii as my first PHP framework. From my point of view, Yii is very easy to setup as web-apps. It’s backed with very good documentation. But, because it’s pretty new, I find there’re only few implementations, or maybe a ‘hack’ to the Yii framework. Specially in Facebook integration, that currently I’m working on.

So, I would like to get your opinion on my method on integrating Facebook Connect into Yii Framework.

So this is my workflow:

  1. Download the Facebook client to protected/components

  2. Create a HFacebook class to wrap the facebook


<?php

Yii::import('application.components.facebook.Facebook');

class HFacebook {


    static public $callback_url='xd_receiver.html';

    static public $api_key='thekey';

    static public $secret='thesecret';


    static function isFBML(){

        return (isset($_POST['fb_sig_in_canvas']) || $_POST['fb_sig_in_canvas']==1);

    }


    static function facebook_client() {

      static $_facebook = null;

      if ($_facebook === null) {

        $_facebook = new Facebook(self::$api_key, self::$secret);


        if (!$_facebook) {

          Yii::log('Could not create facebook client.','error','HFacebook');

        }


      }

      return $_facebook;

    }


    static function init(){

        $GLOBALS['facebook_config']['debug'] = NULL;

        //return new Facebook(self::$api_key, self::$secret);

        return self::facebook_client();

    }


    static function beforeActionController($controller){

        Yii::trace('beforecontrol','HFacebook');

        if (isset($controller->facebook))

            Yii::trace('Controllers facebook object is set','HFacebook');

        //$fb_user=$controller->facebook->require_login();

        $fb_uid=$controller->facebook->get_loggedin_user();

        Yii::trace('fb_uid:'.$fb_uid,'HFacebook');

        if($fb_uid){

            $user=Users::model()->find('fb_uid=?',array($fb_uid));

            if(!Yii::app()->user->isGuest && $user!=null){

                Yii::trace('Logout, and use FB User that exist in DB','HFacebook');

                Yii::app()->user->logout();

                $identity=new UserIdentity($user->username,$user->password);

                Yii::app()->user->login($identity);

                Yii::app()->user->setId($user->idusr);

            }else if($user==null){

                Yii::trace('Add new FB user into DB','HFacebook');

                $user = new Users;

                $user->username='FacebookUser_'.$fb_uid;

                $user->name='FacebookUser_'.$fb_uid;

                $user->password=md5('FacebookUser_'.$fb_uid);

                $user->email='';

                $user->fb_uid=$fb_uid;

                $user->email_hash='';

                if($user->save()){

                  $identity=new UserIdentity($user->username,$user->password);

                  Yii::app()->user->login($identity);

                  Yii::app()->user->setId($user->idusr);

                }

            }else if($user!=null && Yii::app()->user->isGuest){

                Yii::trace('Autologon with existing FB user','HFacebook');

                $identity=new UserIdentity($user->username,$user->password);

                Yii::app()->user->login($identity);

                Yii::app()->user->setId($user->idusr);

            }

        }

    }

 }

?>



  1. Create a base protected/components/HController.php class, all controller classes will be extend-ed from this base class.



<?php

class HController extends CController{

    public $facebook;


    public function __construct($id,$module=null)

    {

        parent::__construct($id,$module);

        $this->facebook = HFacebook::init();

    }


    public function redirect($url,$terminate=true,$statusCode=302){

        if (HFacebook::isFBML()){

            echo '<fb:redirect url="'.$url.'"/>';

        }

        parent::redirect($url,$terminate,$statusCode);

    }


    function beforeAction($action) {

       $conact=$this->id.'/'.$this->getAction()->id;

       if($conact!='site/logout' && $conact!='site/index'){

          Yii::trace('Execute Facebook component','HController');

          HFacebook::beforeActionController($this);

       }

       return parent::beforeAction($action);

    }

}

?>



  1. Modiy the protected/controller/SiteController.php

class SiteController extends HController

  1. Modify the protected/views/layout/main.php



<?php if (!isset($_POST['fb_sig_in_canvas'])){?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml" xml:lang="en" lang="en">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<meta name="language" content="en" />

<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/main.css" />

<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/form.css" />

<title><?php echo $this->pageTitle; ?></title>

</head>


<body>

<?php }/* -------------------------------------*/  ?>




<div id="page">

....nomodificationonthispart....

</div><!-- page -->




<?php if (!isset($_POST['fb_sig_in_canvas'])){/* -------------------------------------*/?>

   <?php $conact=$this->id.'/'.$this->getAction()->id;

         if($conact!='site/logout' && $conact!='site/index' && $conact!='site/login'){ ?>

    <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/id_ID" type="text/javascript"></script>

    <script type="text/javascript">

        FB.init("<?php echo HFacebook::$api_key; ?>", "<?php echo HFacebook::$callback_url; ?>");

    </script>

  <?php } ?>

    </body>

    </html>

<?php } ?>



  1. Modify the protected/views/site/login.php



...theendofthefile...

<fb:login-button autologoutlink="true" size="medium" background="light" length="long" ></fb:login-button>

    <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/id_ID" type="text/javascript"></script>

<script type="text/javascript">

	FB.init("<?php echo HFacebook::$api_key; ?>", "<?php echo HFacebook::$callback_url; ?>",{"reloadIfSessionStateChanged":true});

</script>



Please let me know what you all think about this method (or ‘hack’?).

Nice! But you don’t store the user info from Facebook? Or am I wrong?

I store the UID of the Facebook User. As I think, that’s the only information that allow to be stored out of Facebook. CMIIW.

UID is the only information you can store outside FB.

This is my request: I never understand well the Authentification of Yii. Can you show me how can you log a user using only the Facebok authentification and then save in the Yii::app()->user the $user information recovered by the db using the FacebookUserId ?

Thank you very much :) Is there a way to extend the Yii normal role based with: guest, authed, superuser, admin?

I would love to see example how facebook connect is integrated with sbrac :)

Me too… :)

I override the existing beforeAction in HController.php. This method calls HFacebook.beforeAction.

In this method, it will:

  • get the facebook UID

  • if UID found, then try to get the facebook UID from database

  • if it’s not a guest, logout the existing user and set the yii::app()->user with db user

  • if it can’t find user from DB, set the yii::a[[()->user and insert new record into DB

  • if it’s a guest, set the yii::app()->user with db user

let me know if you’re able to implement this and/or if you have another implementation of facebook…

I still don’t understand on how the role works… i’m still studying it… :)

Me too… I would love to see it as well… anyone share they view on this?

btw, have you successfull implement facebook connect? can you share it with us?

Hello,

I’m now developing a couple sites with full integration with Facebook using the Yii framework.

I think there’s a BIG HOLE in that implementation.

I’m writing here as a big ADVICE for that implementation. I’m using it as a sketch and already noticed a big hole in those files.

You are setting facebook users password to md5(‘FacebookUser_’.$fbuid). You should use some random password instead since it is never supposed to be used anyway. Since there’s probably some other way to login to the site it’d be easy to fool the system so I’d login with another person’s facebook account just by knowing the FB ID and calculating the md5 or whatever function you used.

Correct me if I’m wrong.

Edit: using $user->password = uniqid(rand(), true); should be enough since you should probably have already a mechanism to hash passwords behind that, the function would generate a random string stronger than any password usually used.

Instead of using some random password you can just use a blank password and REQUIRE users to input a password in the form and when you want to authenticate a special user like Facebook single-sign-on you put the blank password.

This way will void any attempt to Login using a Facebook username.

As for other notes. I believe you are adding too much useless computation for the site. That login code should only be used to actually login users (existing or new). The way you did it just logins the user in every page request.

I used the user module from here http://code.google.com/p/yii-user-management/

and the FB client script in extensions:

http://wiki.developers.facebook.com/index.php/PHP#Official_PHP_Client_Library

and modified the usertable to:


CREATE  TABLE IF NOT EXISTS `ebooks`.`users` (

  `id` INT(11) NOT NULL AUTO_INCREMENT ,

  `facebook_id` BIGINT NULL ,

  `username` VARCHAR(20) NULL ,

  `password` VARCHAR(128) NULL ,

  `email` VARCHAR(128) NULL ,

  `activkey` VARCHAR(128) NULL ,

  `createtime` INT(10) NOT NULL ,

  `lastvisit` INT(10) NOT NULL ,

  `superuser` INT(1) NOT NULL ,

  `status` INT(1) NOT NULL ,

  PRIMARY KEY (`id`) ,

  UNIQUE INDEX `username` (`username` ASC) ,

  UNIQUE INDEX `email` (`email` ASC) ,

  INDEX `status` (`status` ASC) ,

  INDEX `superuser` (`superuser` ASC) ,

  INDEX `facebook` (`facebook_id` ASC) )

ENGINE = InnoDB

AUTO_INCREMENT = 3

DEFAULT CHARACTER SET = utf8

Then the login controller:




<?php

class LoginController extends Controller

{

	public $defaultAction = 'login';


    public function actionFacebook(){


        // Make sure the user is logged out

        Yii::app()->user->logout();


        require_once('facebook/facebook.php');

        $facebook = new Facebook(Yii::app()->params['facebookApi'], Yii::app()->params['facebookSecretCode']);

        $fb_user_id = $facebook->get_loggedin_user();

        if($fb_user_id){

            $user = User::model()->findByAttributes(array('facebook_id' => $fb_user_id));

            

            if($user === NULL){

                

                // Save new facebook user to database

                $user = new User;

                $user->facebook_id = $fb_user_id;

                $user->status = User::STATUS_ACTIVE;

                $user->createtime = time();

                $user->lastvisit = time();

                $user->superuser = 0;


                if($user->save(false)){

                    $profile = new Profile;

                    $profile->user_id=$user->id;

                    $profile->save(false);

                }

                

            }


            $identity=new UserIdentity($user->facebook_id, $user->id);

            $identity->authenticate(true);


            switch($identity->errorCode)

            {

                case UserIdentity::ERROR_NONE:

                    $duration= 3600*24*30; // 30 days

                    Yii::app()->user->login($identity,$duration);

                    $this->redirect(Yii::app()->controller->module->returnUrl);

                    

                    break;

                case UserIdentity::ERROR_STATUS_NOTACTIV:

                    $user->addError("status",UserModule::t("You account is not activated."));

                    break;

                case UserIdentity::ERROR_STATUS_BAN:

                    $user->addError("status",UserModule::t("You account is blocked."));

                    break;

                case UserIdentity::ERROR_PASSWORD_INVALID:

                    $user->addError("password",UserModule::t("Password is incorrect."));

                    break;

            }


            $this->render('facebook', array('user' => $user));


        } 

        else

        {

            $this->redirect(Yii::app()->controller->module->returnLogoutUrl);

        }


        


        

    }


	/**

	 * Displays the login page

	 */

	public function actionLogin()

	{                


		if (Yii::app()->user->isGuest) {

			$model=new UserLogin;

			// collect user input data

			if(isset($_POST['UserLogin']))

			{

				$model->attributes=$_POST['UserLogin'];

				// validate user input and redirect to previous page if valid

				if($model->validate()) {

					$this->lastVisit();

					$this->redirect(Yii::app()->controller->module->returnUrl);

				}

			}

			// display the login form

			$this->render('/user/login',array('model'=>$model,));

		} else

			$this->redirect(Yii::app()->controller->module->returnUrl);

	}

	

	private function lastVisit() {

		$lastVisit = User::model()->findByPk(Yii::app()->user->id);

		$lastVisit->lastvisit = time();

		$lastVisit->save();

	}


}

Log out controller:




<?php


class LogoutController extends Controller

{

	public $defaultAction = 'logout';

	

	/**

	 * Logout the current user and redirect to returnLogoutUrl.

	 */

	public function actionLogout()

	{

        $user = User::model()->findByPk(Yii::app()->user->id);

        Yii::app()->user->logout();


        // Logout facebook

        if($user->facebook_id !== NULL){

            require_once('facebook/facebook.php');

            $facebook = new Facebook(Yii::app()->params['facebookApi'], Yii::app()->params['facebookSecretCode']);

            $logout_url = 'http://'.$_SERVER['HTTP_HOST'].Yii::app()->request->baseUrl.Yii::app()->controller->module->returnLogoutUrl[0];;

            $facebook->logout($logout_url);

        }

        

		$this->redirect(Yii::app()->controller->module->returnLogoutUrl);

	}


}

UserIdentity component:




<?php


/**

 * UserIdentity represents the data needed to identity a user.

 * It contains the authentication method that checks if the provided

 * data can identity the user.

 */

class UserIdentity extends CUserIdentity

{

	private $_id;

    public  $facebook_id;

    

	const ERROR_EMAIL_INVALID=3;

	const ERROR_STATUS_NOTACTIV=4;

	const ERROR_STATUS_BAN=5;

	/**

	 * Authenticates a user.

	 * The example implementation makes sure if the username and password

	 * are both 'demo'.

	 * In practical applications, this should be changed to authenticate

	 * against some persistent user identity storage (e.g. database).

	 * @return boolean whether authentication succeeds.

	 */

	public function authenticate($facebook = false)

	{

        // Validate facebook user

        if($facebook){

            require_once('facebook/facebook.php');

            $facebook = new Facebook(Yii::app()->params['facebookApi'], Yii::app()->params['facebookSecretCode']);

            $fb_user_id = $facebook->get_loggedin_user();

            $user=User::model()->findByAttributes(array('facebook_id'=> $fb_user_id));


            if($user===null){

                $this->errorCode=self::ERROR_USERNAME_INVALID;

            }

            else if($user->status==0&&Yii::app()->controller->module->loginNotActiv==false)

                $this->errorCode=self::ERROR_STATUS_NOTACTIV;

            else if($user->status==-1)

                $this->errorCode=self::ERROR_STATUS_BAN;

            else {

                $this->_id= $user->id;

                $this->username = 'facebook';

                $this->facebook_id = $user->facebook_id;

                $this->errorCode=self::ERROR_NONE;

            }

        }

        else

        {

            if (strpos($this->username,"@")) {

                $user=User::model()->findByAttributes(array('email'=>$this->username));

            } else {

                $user=User::model()->findByAttributes(array('username'=>$this->username));

            }

            if($user===null)

                if (strpos($this->username,"@")) {

                    $this->errorCode=self::ERROR_EMAIL_INVALID;

                } else {

                    $this->errorCode=self::ERROR_USERNAME_INVALID;

                }

            else if(Yii::app()->controller->module->encrypting($this->password)!==$user->password)

                $this->errorCode=self::ERROR_PASSWORD_INVALID;

            else if($user->status==0&&Yii::app()->controller->module->loginNotActiv==false)

                $this->errorCode=self::ERROR_STATUS_NOTACTIV;

            else if($user->status==-1)

                $this->errorCode=self::ERROR_STATUS_BAN;

            else {

                $this->_id=$user->id;

                $this->username=$user->username;

                $this->errorCode=self::ERROR_NONE;

            }            

        }

        return !$this->errorCode;

	}

    

    /**

    * @return integer the ID of the user record

    */

	public function getId()

	{

		return $this->_id;

	}

}

in login.php (the login form)




<?php if(Yii::app()->user-isGuest):?>

    <div class="facebook-login">

        <?php if($fb_user === NULL):?>

        <div class="login_sector_fb">

            <div class="login_prompt">Or <b>login</b> with Facebook:</div>

            <fb:login-button onlogin="facebook_onlogin_ready();"></fb:login-button>

        </div>

        <?php else:?>

            <fb:profile-pic uid="<?php echo $fb_user;?>" size="square" facebook-logo="true"></fb:profile-pic>

            <?php CHtml::link('Logout('.Yii::app()->user->name.')', Yii::app()->getModule('user')->logoutUrl)?>

        <?php endif;?>

    </div>

<?php endif;?>

<div class="clear"></div>

<?php

$login_url = 'http://'.$_SERVER['HTTP_HOST'].Yii::app()->request->baseUrl.'/user/login/facebook/';

Yii::app()->clientScript->registerScript(

   'facebook_onligin_ready',

   'function facebook_onlogin_ready() {

        window.location = "'.$login_url.'";

    }',

   CClientScript::POS_END

);

?>

in the config:




	'params'=>array(

            // Facebook API key

            'facebookApi' => 'your key',

            // Facebook app secret code

            'facebookSecretCode' => 'your code',

  

In the layout:

Change doctype to:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">



And before body add:





<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>

<script type="text/javascript">

	FB.init("<?php echo Yii::app()->params['facebookApi'];?>", "http://<?php echo $_SERVER['HTTP_HOST'].Yii::app()->request->baseUrl;?>/xd_receiver.php");

</script>

<script src="<?php echo Yii::app()->request->baseUrl;?>/js/fbconnect.js" type="text/javascript"></script>

<script type="text/javascript">

    window.onload = function() { facebook_onload(<?php echo ($fb_user===NULL)? 'false':'true'?>)};

</script>



fbconnect.js


function facebook_onload(already_logged_into_facebook) {

  FB.ensureInit(function() {

      FB.Facebook.get_sessionState().waitUntilReady(function(session) {

          var is_now_logged_into_facebook = session ? true : false;

          if (is_now_logged_into_facebook == already_logged_into_facebook) {

            return;

          }

        });

    });

}


function refresh_page() {   

  window.location = '';

}


function facebook_prompt_permission(permission) {

  FB.ensureInit(function() {

    FB.Connect.showPermissionDialog(permission);

  });

}

Now when you want to show the avatar + username you can do something like:




<?php require_once('facebook/facebook.php');

        $facebook = new Facebook(Yii::app()->params['facebookApi'], Yii::app()->params['facebookSecretCode']);

        $fb_user = $facebook->get_loggedin_user();

        ?>

        <?php if(Yii::app()->user-isGuest):?>

            <?php if($fb_user !== NULL):?>

                <div class="facebook-avatar">

                    <div class="facebook-name">

                    Welcome, <fb:name uid="<?php echo $fb_user;?>" useyou='false'></fb:name>

                    </div>

                    <fb:profile-pic uid="<?php echo $fb_user;?>" size="square" facebook-logo="true"></fb:profile-pic>

                </div>

            <?php endif;?>

        <?php endif;?>



If anybody know how to add properties to WebUser it be very nice to add the facebook id…

You can add properties with the setState method of the UserIdentity class (components/UserIdentity.php).


$this->setState('fbuid', $user->fbuid);

Then, you can access it with


Yii::app()->user->fbuid

:D Oh thx allot very usefull

What about the renewal of the Facebook avatars? Want I think that you can store them for a limit of time.

Do you have the original emailadres? Because you need the have the extended permissions.

I use this code, but not 100%


			FB.init("<?php echo HFacebook::$api_key; ?>", "<?php echo HFacebook::$callback_url; ?>",{"reloadIfSessionStateChanged":true});

			FB.ensureInit(function() {

			    FB.Connect.showPermissionDialog("email");

			});

These questions are only related with Facebook and not with Yii so you might be better off in the Facebook Dev Forums.

Anyway, as for the avatar, in order to show it you can use the fb tag fb:profile-pic


<fb:profile-pic linked="false" uid="<?php echo $fbuid; ?>"> </fb:profile-pic>

(see API for details on how to use it)

I don’t know what the call is to get the profile picture but there’s probably one available. Check PHP API.

As for the permissions you can also make that request as a PHP call like this:


$fbuid = $this->facebook->require_login($required_permissions = 'email');

For more information read Extended Permissions.

If you want to cache information about the user remember to read the terms carefully and to implement a system that responds to the Facebook permissions updates. Recently they changed their permissions to cache information and to subscribe to information updates.

[EDIT]

And regarding the e-mail address: the user might opt to give a "proxy email" instead of a real e-mail address that ONLY your application can use to contact the user. You have to configure the domain which sends emails in your facebook application.

The proxy e-mail might be a bit long (similar to facebook123456789123456789+3456789456789456@proxyemail.facebook.com) so you should prepare your database so that the email doesn’t get cut out.

If anyone interested i am working on a new extension that will use the new facebook open graph. One of it’s features will be the ability to login/logout a member to your site using an application ID.

Have you finished it?

Hi y’all

I would like to request the status of this project please…is is alive?

Id like to know too if this project is alive! Thanks1

Hi there, where do I place the HFacebook class? and what name do we save the fileas?

I found out where - in the components section… but this app doesnt work … can you give more detailed complete instructions?

Hi there Stijnh,

where do I place the HFacebook class and what should I name the file?