diff --git a/README.md b/README.md index d2d9a88..7fcdc95 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ interface. If you want to use the OAuth version see ### What it provides -* Loads and setups Facebook Connect for Single Sign on via the Javascript SDK +* Loads and setups Facebook Connect for Single Sign on via the Javascript SDK or + via a basic HTTP redirect * Authenticates users visiting the site - if they are logged into Facebook then you can access their information via the following controls. You can also @@ -48,13 +49,13 @@ it won't save the information to the database To setup Facebook Connect your first need to download the module: ``` -composer require wilr/silverstripe-facebookconnect +composer require "wilr/silverstripe-facebookconnect" "dev-master" ``` [Register your website / application](https://developers.facebook.com/apps/?action=create) with Facebook. -Set your configuration through the SilverStripe Config API. For instance, you +Set your configuration through the SilverStripe config API. For instance, you could put this in your `mysite/_config/facebookconnect.yml` file: ``` @@ -66,6 +67,11 @@ FacebookControllerExtension: Update the database by running `/dev/build` to add the additional fields to the `Member` table. +To allow the user to login to the application either do it via the javascript +SDK or use the simple HTTP requests. + +#### Javascript SDK + Include the `ConnectInit.ss` template in the `` part of every site you wish to call a Facebook function. This includes the Facebook JavaScript SDK. @@ -74,32 +80,33 @@ E.g. on `Page.ss` ``` <% include ConnectInit %> + ... ``` -Once you have done that you should be able to use the includes provided in this -module. +#### Standard Method ``` -<% if CurrentFacebookMember %> -

Hi $CurrentFacebookMember.FirstName

- <% include ConnectLogout %> -<% else %> - <% include ConnectLogin %> -<% end_if %> +Login via Facebook ``` -You can also access the Facebook member information in your PHP code. The -Facebook API connection and current member are cached on the controller object. -So for example if this is in your Page_Controller class +You can also access the Facebook member information in your PHP code.. ```php // returns the current facebook member (wrapped in a SS Member Object) $this->getCurrentFacebookMember(); // returns the API connection which you can use to write your own query -$this->getFacebook(); +// while in a controller +$this->getFacebookSession(); + +// if you're not in a controller +Controller::curr()->getFacebookSession(); + ``` +For more information about what you can do through the SDK see +https://developers.facebook.com/docs/reference/php/4.0.0 + ### Options All the following values are set either via the PHP Config API like follows @@ -136,7 +143,7 @@ A list of group codes to add the user. For instance if you want every member who joins through facebook to be added to a group `Facebook Members` set the following: - FacebookConnectExtensions: + FacebookControllerExtension: member_groups: - facebook_members diff --git a/_config/facebookconnect.yml b/_config/facebookconnect.yml index 9bb8c10..fdb971d 100644 --- a/_config/facebookconnect.yml +++ b/_config/facebookconnect.yml @@ -7,7 +7,7 @@ Member: Controller: extensions: - - FacebookConnectExtension + - FacebookControllerExtension Authenticator:: authenticators: diff --git a/code/FacebookAuthenticator.php b/code/FacebookAuthenticator.php index f76efc8..4f5299d 100644 --- a/code/FacebookAuthenticator.php +++ b/code/FacebookAuthenticator.php @@ -20,7 +20,7 @@ public static function authenticate($RAW_data, Form $form = null) { } /** - * Return the facebook login form + * Return the Facebook login form * * @return Form */ @@ -29,9 +29,9 @@ public static function get_login_form(Controller $controller) { } /** - * Return the name for the facebook tab + * Return the name for the Facebook tab * - * @return String + * @return string */ public static function get_name() { return _t('FacebookAuthenicator.TITLE', "Facebook Connect"); diff --git a/code/extensions/FacebookControllerExtension.php b/code/extensions/FacebookControllerExtension.php index ba06aba..6bfc277 100644 --- a/code/extensions/FacebookControllerExtension.php +++ b/code/extensions/FacebookControllerExtension.php @@ -1,15 +1,19 @@ load(get_class($this))))) { - $result = new Facebook(array( - 'appId' => Config::inst()->get('FacebookControllerExtension', 'app_id'), - 'secret' => Config::inst()->get('FacebookControllerExtension', 'api_secret'), - 'cookie' => true, - )); - - $cache->save(serialize($result), get_class($this)); - } + * @var + */ + private $session = null; - return $result; - } - /** - * Call an API function but cache the result. - * - * Passes the call through to {@link Facebook::api()} but looks up the - * value in the {@link SS_Cache} first. * - * Bases the cache on the users session id so then we don't get incorrect information - * for when users are viewing the cache of different users. - * - * @param string $name - * @param string $params + */ + public function __construct() { + $this->beginFacebookSession(); + } + + /** + * @todo Error handler * - * @return array + * @return FacebookSession */ - public function callCached($name, $params) { - $cache = SS_Cache::factory(get_class($this) . session_id()); - $key = rtrim(base64_encode(get_class($this) . $name), '='); + public function getFacebookSession() { + if($this->session) { + return $this->session; + } - if(!($result = unserialize($cache->load($key)))) { - $result = $this->getFacebook()->api($params); - - $cache->save(serialize($result), $key); + try { + $helper = Injector::inst()->create("Facebook\FacebookRedirectLoginHelper", + $this->getCurrentPageUrl() + ); + } catch(\Exception $ex) { + SS_Log::log($ex, SS_Log::ERR); } - - return $result; + + $this->session = $helper->getSessionFromRedirect(); + + return $this->session; } /** @@ -117,88 +100,135 @@ public function callCached($name, $params) { * required files for facebook connect. */ public function onBeforeInit() { - $user = $this->getFacebook()->getUser(); + $session = $this->getFacebookSession(); + + if(!$session) { + return; + } + + $user = $this->getCurrentFacebookMember(); + + if($user) { + $this->facebookMember = $member; - if(!isset($_GET['updatecache']) && $user) { try { - $result = $this->callCached('me', '/me'); + if(!$member = Member::currentUser()) { + // member is not currently logged into SilverStripe. Look up + // for a member with the UID which matches first. + $member = Member::get()->filter(array( + "FacebookUID" => $user->getId() + ))->first(); - // if email is empty and proxied_email is set instead - // write down proxied_email to email - if(!stristr($result['email'], '@')){ - $result['email'] = $result['proxied_email']; - } - - // if logged in and authorized to fb sync details - if($member = Member::currentUser()) { - if(isset($result['email']) && ($result['email'] != $member->Email)) { - // member email has changed. Require new login - $member->logOut(); - } else { - $member->updateFacebookFields($result); - - if(Config::inst()->get('FacebookControllerExtension', 'sync_member_details')) { - $member->write(); - } - } - } else { - // member is not currently logged into SilverStripe. Look up for - // a member with the UID which matches and log them in or - // create a new member - $SQL_uid = Convert::raw2sql($result['id']); - $member = Member::get()->filter("FacebookUID", $SQL_uid)->first(); - - if($member) { - $member->updateFacebookFields($result); - - if(Config::inst()->get('FacebookControllerExtension', 'sync_member_details')) { - $member->write(); - } - - $member->logIn(); - } else if(isset($result['email']) && ($member = Member::get()->filter('Email', $result['email'])->first())) { - $member->updateFacebookFields($result); - - if(Config::inst()->get('FacebookControllerExtension', 'create_member')) { - $member->write(); - $member->logIn(); + if(!$member) { + // see if we have a match based on email. From a + // security point of view, users have to confirm their + // email address in facebook so doing a match up is fine + $email = $user->getProperty('email'); + + if($email) { + $member = Member::get()->filter(array( + 'Email' => $email + ))->first(); } - } else { - // create a new member - $member = singleton('Member')->addFacebookMember( - $result, - Config::inst()->get('FacebookControllerExtension', 'create_member') - ); } - } - - Session::set('logged-in-member-via-faceboook', true); - - if($groups = Config::inst()->get('FacebookControllerExtension', 'member_groups')) { - foreach($groups as $group) { - $member->addToGroupByCode($group); + + if(!$member) { + // fallback, if still + $member = Injector::create('Member'); } } - $this->facebookMember = $member; - } catch (FacebookApiException $e) { + $this->updateMemberFromFacebook($member, $user); + $member->logIn(); + + Session::set('logged-in-member-via-faceboook', true); + } catch (Exception $e) { + SS_Log::log($e, SS_Log::ERR); } } else if($logged = Session::get('logged-in-member-via-faceboook')) { - Session::clear('logged-in-member-via-faceboook'); - - $member = Member::currentUser(); + $this->logUserOut(); + } + } + + /** + * @param Member + * + * @return Member + */ + protected function updateMemberFromFacebook($member, $info) { + $sync = Config::inst()->get('FacebookControllerExtension', 'sync_member_details'); + $create = Config::inst()->get('FacebookControllerExtension', 'create_member'); + + $member->updateFacebookFields($info, $sync); + + // sync details to the database + if(($member->ID && $sync) || $create) { + if($member->isChanged()) { + $member->write(); + } + } - if($member) { - $member->logOut(); + // ensure members are in the correct groups + if($groups = Config::inst()->get('FacebookControllerExtension', 'member_groups')) { + foreach($groups as $group) { + $member->addToGroupByCode($group); } } + + return $member; } - + + /** + * @return FacebookSession|null + */ + protected function beginFacebookSession() { + $appId = Config::inst()->get('FacebookControllerExtension', 'app_id'); + $secret = Config::inst()->get('FacebookControllerExtension', 'api_secret'); + + if(!$appId || !$secret) { + return null; + } + + FacebookSession::setDefaultApplication($appId, $secret); + + if(session_status() !== PHP_SESSION_ACTIVE) { + Session::start(); + } + } + + /** + * @return GraphUser|null + */ + public function getFacebookUser() { + try { + $user = (new FacebookRequest( + $session, 'GET', '/me' + ))->execute()->getGraphObject(GraphUser::className()); + + return $user; + } catch(FacebookRequestException $e) { + SS_Log::log($e, SS_Log::ERR); + } + } + + /** + * @return void + */ + protected function logFacebookUserOut() { + Session::clear('logged-in-member-via-faceboook'); + + $member = Member::currentUser(); + + if($member) { + $member->logOut(); + } + } + /** * @return ArrayData */ - public function getCurrentfacebookMember() { + public function getCurrentFacebookMember() { if(isset($this->facebookMember) && $this->facebookMember) { return $this->facebookMember; } @@ -206,42 +236,36 @@ public function getCurrentfacebookMember() { /** - * Logout link - * - * @return String + * @return string */ public function getFacebookLogoutLink() { - $link = $this->getLink(); - - return $this->getFacebook()->getLogoutUrl(array( - 'next' => Controller::join_links($link, '?updatecache=1') - )); + $helper = new Facebook\FacebookRedirectLoginHelper($this->getCurrentPageUrl()); + + return $helper->getLogoutUrl(); } /** * @return string */ public function getFacebookLoginLink() { - $link = $this->getLink(); - - return $this->getFacebook()->getLoginUrl(array( - 'next' => Controller::join_links($link, '?updatecache=1') - )); + $helper = new Facebook\FacebookRedirectLoginHelper($this->getCurrentPageUrl()); + $scope = Config::inst()->get('FacebookControllerExtension', 'permissions'); + if(!$scope) $scope = array(); + + return $helper->getLoginUrl($scope); } - + /** - * @returnstring + * @return string */ - public function getLink() { - $controller = Controller::curr(); - $link = Director::absoluteBaseURL(); - - if($controller->hasMethod('AbsoluteLink')) { - $link = $controller->AbsoluteLink(); - } else if($controller->hasMethod('Link')) { - $link .= $controller->Link(); - } - - return $link; + public function getFacebookAppId() { + return Config::inst()->get('FacebookControllerExtension', 'app_id'); + } + + /** + * @return string + */ + public function getCurrentPageUrl() { + return Director::protocol() . "//$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; } } \ No newline at end of file diff --git a/code/extensions/FacebookMemberExtension.php b/code/extensions/FacebookMemberExtension.php index 3d8266c..27d22be 100644 --- a/code/extensions/FacebookMemberExtension.php +++ b/code/extensions/FacebookMemberExtension.php @@ -20,83 +20,54 @@ class FacebookMemberExtension extends DataExtension { ); public function updateCMSFields(FieldList $fields) { - $fields->makeFieldReadonly('Email'); $fields->makeFieldReadonly('FacebookUID'); $fields->makeFieldReadonly('FacebookLink'); $fields->makeFieldReadonly('FacebookTimezone'); } - /** - * After logging out on the security logout panel log out of Facebook - */ - public function memberLoggedOut() { - $controller = Controller::curr(); - - if(!$controller->redirectedTo()) { - if($controller->getCurrentFacebookMember()) { - $controller->getFacebook()->destroySession(); - } - } - } - /** * Takes one of 'square' (50x50), 'small' (50xXX) or 'large' (200xXX) * - * @return string + * @return string $type */ public function getAvatar($type = "square") { $controller = Controller::curr(); - if($controller && ($member = $controller->getCurrentFacebookMember())) { - return sprintf( - "http://graph.facebook.com/%s/picture?type=%s", - $member->FacebookUID, $type - ); - } - } - - /** - * Create a new User based on the Facebook Member. - * - * @param array - * @param bool - * - * @return DataObject - */ - public function addFacebookMember($result, $create_member) { - $member = new Member(); - $member->updateFacebookFields($result); - - if($create_member) { - $member->write(); - $member->logIn(); - } + if($controller && ($session = $controller->getFacebookSession())) { + try { + $request = (new FacebookRequest($session, 'GET', "me/picture?type=$type&redirect=false"))->execute(); + $picture = $request->getGraphObject(); - // the return value must be an instance of Member. - // whether it's an inherited instance doesn't matter. - $this->owner->extend('onAddFacebookMember', $result); + return $picture->getProperty('url'); - return $member; + } catch(FacebookRequestException $e) { + SS_Log::log($e, SS_Log::ERR); + } + } } /** * Sync the new data from a users Facebook profile to the member database. * - * @param array + * @param GraphUser $result + * @param bool $sync Flag to whether we override fields like first name */ - public function updateFacebookFields($result) { - // only Update Email if ist already set to a correct Email, - // while $result['email'] is still a proxied_email - if(!Email::validEmailAddress($this->owner->Email) || (!stristr($result['email'], '@facebook.com') && !DataObject::get_one('Member', "\"Email\" = '". Convert::raw2sql($result['email']) ."'"))){ - $this->owner->Email = (isset($result['email'])) ? $result['email'] : ""; - } + public function updateFacebookFields(GraphUser $result, $override = true) { + $this->owner->FacebookLink = $result->getProperty('link'); + $this->owner->FacebookUID = $result->getProperty('id'); + $this->owner->FacebookTimezone = $result->getProperty('timezone'); + + if($override) { + $email = $result->getProperty('email'); - $this->owner->FirstName = (isset($result['first_name'])) ? $result['first_name'] : ""; - $this->owner->Surname = (isset($result['last_name'])) ? $result['last_name'] : ""; - $this->owner->FacebookLink = (isset($result['link'])) ? $result['link'] : ""; - $this->owner->FacebookUID = (isset($result['id'])) ? $result['id'] : ""; - $this->owner->FacebookTimezone = (isset($result['timezone'])) ? $result['timezone'] : ""; + if($email && !$this->owner->Email || !Email::validEmailAddress($this->owner->Email)) { + $this->owner->Email = $email; + } + + $this->owner->FirstName = $result->getProperty('first_name'); + $this->owner->Surname = $result->getProperty('last_name'); + } $this->owner->extend('onUpdateFacebookFields', $result); } -} +} \ No newline at end of file diff --git a/composer.json b/composer.json index 546b34a..c3a9029 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ }], "require": { "silverstripe/framework": ">=3.1", - "facebook/php-sdk": "dev-master#5c5a3e80af30ea00e7682e94b31112c505051f50" + "facebook/php-sdk-v4": "@stable" }, "extra": { "installer-name": "facebookconnect" diff --git a/templates/ConnectInit.ss b/templates/ConnectInit.ss index 9f34db7..f4ea62b 100644 --- a/templates/ConnectInit.ss +++ b/templates/ConnectInit.ss @@ -1,22 +1,19 @@
+ \ No newline at end of file