summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach Copley <zach@status.net>2010-11-05 06:34:06 +0000
committerZach Copley <zach@status.net>2010-11-05 06:34:06 +0000
commit035081a803b005b8a2410c6936eac4027fda493c (patch)
treeaa7a3341e48851d4f3383ef9cbbb7f61f08f9b25
parentc0cce1891307ebceed448118b318bf8b527dc06e (diff)
Much more reliable Facebook SSO
-rw-r--r--plugins/FacebookSSO/FacebookSSOPlugin.php66
-rw-r--r--plugins/FacebookSSO/actions/facebookfinishlogin.php (renamed from plugins/FacebookSSO/actions/facebookregister.php)50
-rw-r--r--plugins/FacebookSSO/actions/facebooklogin.php123
-rw-r--r--plugins/FacebookSSO/images/login-button.pngbin0 -> 1661 bytes
-rw-r--r--plugins/FacebookSSO/lib/facebookclient.php640
-rw-r--r--plugins/FacebookSSO/lib/facebookqueuehandler.php63
6 files changed, 807 insertions, 135 deletions
diff --git a/plugins/FacebookSSO/FacebookSSOPlugin.php b/plugins/FacebookSSO/FacebookSSOPlugin.php
index b14ef0bad..a726b2fac 100644
--- a/plugins/FacebookSSO/FacebookSSOPlugin.php
+++ b/plugins/FacebookSSO/FacebookSSOPlugin.php
@@ -125,7 +125,7 @@ class FacebookSSOPlugin extends Plugin
include_once $dir . '/extlib/facebookapi_php5_restlib.php';
return false;
case 'FacebookloginAction':
- case 'FacebookregisterAction':
+ case 'FacebookfinishloginAction':
case 'FacebookadminpanelAction':
case 'FacebooksettingsAction':
include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
@@ -146,15 +146,17 @@ class FacebookSSOPlugin extends Plugin
function needsScripts($action)
{
static $needy = array(
- 'FacebookloginAction',
- 'FacebookregisterAction',
+ //'FacebookloginAction',
+ 'FacebookfinishloginAction',
'FacebookadminpanelAction',
'FacebooksettingsAction'
);
if (in_array(get_class($action), $needy)) {
+ common_debug("needs scripts!");
return true;
} else {
+ common_debug("doesn't need scripts!");
return false;
}
}
@@ -185,8 +187,8 @@ class FacebookSSOPlugin extends Plugin
array('action' => 'facebooklogin')
);
$m->connect(
- 'main/facebookregister',
- array('action' => 'facebookregister')
+ 'main/facebookfinishlogin',
+ array('action' => 'facebookfinishlogin')
);
$m->connect(
@@ -298,50 +300,42 @@ class FacebookSSOPlugin extends Plugin
function onStartShowHeader($action)
{
- if ($this->needsScripts($action)) {
+ // output <div id="fb-root"></div> as close to <body> as possible
+ $action->element('div', array('id' => 'fb-root'));
+ return true;
+ }
- // output <div id="fb-root"></div> as close to <body> as possible
- $action->element('div', array('id' => 'fb-root'));
+ function onEndShowScripts($action)
+ {
+ if ($this->needsScripts($action)) {
- $dir = dirname(__FILE__);
+ $action->script('https://connect.facebook.net/en_US/all.js');
$script = <<<ENDOFSCRIPT
-window.fbAsyncInit = function() {
-
- FB.init({
- appId : %s,
- session : %s, // don't refetch the session when PHP already has it
- status : true, // check login status
- cookie : true, // enable cookies to allow the server to access the session
- xfbml : true // parse XFBML
- });
-
- // whenever the user logs in, refresh the page
- FB.Event.subscribe(
- 'auth.login',
- function() {
- window.location.reload();
+FB.init({appId: %1\$s, session: %2\$s, status: true, cookie: true, xfbml: true});
+
+$('#facebook_button').bind('click', function(event) {
+
+ event.preventDefault();
+
+ FB.login(function(response) {
+ if (response.session && response.perms) {
+ window.location.href = '%3\$s';
+ } else {
+ // NOP (user cancelled login)
}
- );
-};
-
-(function() {
- var e = document.createElement('script');
- e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
- e.async = true;
- document.getElementById('fb-root').appendChild(e);
-}());
+ }, {perms:'read_stream,publish_stream,offline_access,user_status,user_location,user_website'});
+});
ENDOFSCRIPT;
$action->inlineScript(
sprintf($script,
json_encode($this->facebook->getAppId()),
- json_encode($this->facebook->getSession())
+ json_encode($this->facebook->getSession()),
+ common_local_url('facebookfinishlogin')
)
);
}
-
- return true;
}
/*
diff --git a/plugins/FacebookSSO/actions/facebookregister.php b/plugins/FacebookSSO/actions/facebookfinishlogin.php
index e21deff88..16f7cff50 100644
--- a/plugins/FacebookSSO/actions/facebookregister.php
+++ b/plugins/FacebookSSO/actions/facebookfinishlogin.php
@@ -2,7 +2,7 @@
/**
* StatusNet, the distributed open-source microblogging tool
*
- * Register a local user and connect it to a Facebook account
+ * Login or register a local user based on a Facebook user
*
* PHP version 5
*
@@ -31,7 +31,7 @@ if (!defined('STATUSNET')) {
exit(1);
}
-class FacebookregisterAction extends Action
+class FacebookfinishloginAction extends Action
{
private $facebook = null; // Facebook client
@@ -220,7 +220,7 @@ class FacebookregisterAction extends Action
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_facebook_connect',
'class' => 'form_settings',
- 'action' => common_local_url('facebookregister')));
+ 'action' => common_local_url('facebookfinishlogin')));
$this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
// TRANS: Legend.
$this->element('legend', null, _m('Connection options'));
@@ -428,27 +428,46 @@ class FacebookregisterAction extends Action
return;
}
- common_debug('Facebook Connect Plugin - ' .
- "Connected Facebook user $this->fbuid to local user $user->id");
+ common_debug(
+ sprintf(
+ 'Connected Facebook user %s to local user %d',
+ $this->fbuid,
+ $user->id
+ ),
+ __FILE__
+ );
// Return to Facebook connection settings tab
- common_redirect(common_local_url('FBConnectSettings'), 303);
+ common_redirect(common_local_url('facebookfinishlogin'), 303);
}
function tryLogin()
{
- common_debug('Facebook Connect Plugin - ' .
- "Trying login for Facebook user $this->fbuid.");
+ common_debug(
+ sprintf(
+ 'Trying login for Facebook user %s',
+ $this->fbuid
+ ),
+ __FILE__
+ );
- $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
+ $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if (!empty($flink)) {
$user = $flink->getUser();
if (!empty($user)) {
- common_debug('Facebook Connect Plugin - ' .
- "Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Logged in Facebook user %s as user %d (%s)',
+ $this->fbuid,
+ $user->nickname,
+ $user->id
+ ),
+ __FILE__
+ );
common_set_user($user);
common_real_login(true);
@@ -457,8 +476,13 @@ class FacebookregisterAction extends Action
} else {
- common_debug('Facebook Connect Plugin - ' .
- "No flink found for fbuid: $this->fbuid - new user");
+ common_debug(
+ sprintf(
+ 'No flink found for fbuid: %s - new user',
+ $this->fbuid
+ ),
+ __FILE__
+ );
$this->showForm(null, $this->bestNewNickname());
}
diff --git a/plugins/FacebookSSO/actions/facebooklogin.php b/plugins/FacebookSSO/actions/facebooklogin.php
index bb30be1af..08c237fe6 100644
--- a/plugins/FacebookSSO/actions/facebooklogin.php
+++ b/plugins/FacebookSSO/actions/facebooklogin.php
@@ -40,90 +40,10 @@ class FacebookloginAction extends Action
parent::handle($args);
if (common_is_real_login()) {
-
$this->clientError(_m('Already logged in.'));
-
- } else {
-
- $facebook = new Facebook(
- array(
- 'appId' => common_config('facebook', 'appid'),
- 'secret' => common_config('facebook', 'secret'),
- 'cookie' => true,
- )
- );
-
- $session = $facebook->getSession();
- $fbuser = null;
-
- if ($session) {
- try {
- $fbuid = $facebook->getUser();
- $fbuser = $facebook->api('/me');
- } catch (FacebookApiException $e) {
- common_log(LOG_ERROR, $e);
- }
- }
-
- if (!empty($fbuser)) {
- common_debug("Found a valid Facebook user", __FILE__);
-
- // Check to see if we have a foreign link already
- $flink = Foreign_link::getByForeignId($fbuid, FACEBOOK_SERVICE);
-
- if (empty($flink)) {
-
- // See if the user would like to register a new local
- // account
- common_redirect(
- common_local_url('facebookregister'),
- 303
- );
-
- } else {
-
- // Log our user in!
- $user = $flink->getUser();
-
- if (!empty($user)) {
-
- common_log(
- LOG_INFO,
- sprintf(
- 'Logged in Facebook user %s as user %s (%s)',
- $fbuid,
- $user->id,
- $user->nickname
- ),
- __FILE__
- );
-
- common_set_user($user);
- common_real_login(true);
- $this->goHome($user->nickname);
- }
- }
-
- }
- }
-
- $this->showPage();
- }
-
- function goHome($nickname)
- {
- $url = common_get_returnto();
- if ($url) {
- // We don't have to return to it again
- common_set_returnto(null);
} else {
- $url = common_local_url(
- 'all',
- array('nickname' => $nickname)
- );
+ $this->showPage();
}
-
- common_redirect($url, 303);
}
function getInstructions()
@@ -151,14 +71,45 @@ class FacebookloginAction extends Action
$this->elementStart('fieldset');
+ $facebook = Facebookclient::getFacebook();
+
+ // Degrade to plain link if JavaScript is not available
+ $this->elementStart(
+ 'a',
+ array(
+ 'href' => $facebook->getLoginUrl(
+ array(
+ 'next' => common_local_url('facebookfinishlogin'),
+ 'cancel' => common_local_url('facebooklogin')
+ )
+ ),
+ 'id' => 'facebook_button'
+ )
+ );
+
$attrs = array(
- //'show-faces' => 'true',
- //'max-rows' => '4',
- //'width' => '600',
- 'perms' => 'user_location,user_website,offline_access,publish_stream'
+ 'src' => common_path(
+ 'plugins/FacebookSSO/images/login-button.png',
+ true
+ ),
+ 'alt' => 'Login with Facebook',
+ 'title' => 'Login with Facebook'
);
- $this->element('fb:login-button', $attrs);
+ $this->element('img', $attrs);
+
+ $this->elementEnd('a');
+
+ /*
+ $this->element('div', array('id' => 'fb-root'));
+ $this->script(
+ sprintf(
+ 'http://connect.facebook.net/en_US/all.js#appId=%s&xfbml=1',
+ common_config('facebook', 'appid')
+ )
+ );
+ $this->element('fb:facepile', array('max-rows' => '2', 'width' =>'300'));
+ */
$this->elementEnd('fieldset');
}
diff --git a/plugins/FacebookSSO/images/login-button.png b/plugins/FacebookSSO/images/login-button.png
new file mode 100644
index 000000000..4e7766bca
--- /dev/null
+++ b/plugins/FacebookSSO/images/login-button.png
Binary files differ
diff --git a/plugins/FacebookSSO/lib/facebookclient.php b/plugins/FacebookSSO/lib/facebookclient.php
new file mode 100644
index 000000000..a0549a1d0
--- /dev/null
+++ b/plugins/FacebookSSO/lib/facebookclient.php
@@ -0,0 +1,640 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for communicating with Facebook
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009-2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Class for communication with Facebook
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+class Facebookclient
+{
+ protected $facebook = null; // Facebook Graph client obj
+ protected $flink = null; // Foreign_link StatusNet -> Facebook
+ protected $notice = null; // The user's notice
+ protected $user = null; // Sender of the notice
+ protected $oldRestClient = null; // Old REST API client
+
+ function __constructor($notice)
+ {
+ $this->facebook = self::getFacebook();
+ $this->notice = $notice;
+
+ $this->flink = Foreign_link::getByUserID(
+ $notice->profile_id,
+ FACEBOOK_SERVICE
+ );
+
+ $this->user = $this->flink->getUser();
+
+ $this->oldRestClient = self::getOldRestClient();
+ }
+
+ /*
+ * Get and instance of the old REST API client for sending notices from
+ * users with Facebook links that pre-exist the Graph API
+ */
+ static function getOldRestClient()
+ {
+ $apikey = common_config('facebook', 'apikey');
+ $secret = common_config('facebook', 'secret');
+
+ // If there's no app key and secret set in the local config, look
+ // for a global one
+ if (empty($apikey) || empty($secret)) {
+ $apikey = common_config('facebook', 'global_apikey');
+ $secret = common_config('facebook', 'global_secret');
+ }
+
+ return new FacebookRestClient($apikey, $secret, null);
+ }
+
+ /*
+ * Get an instance of the Facebook Graph SDK object
+ *
+ * @param string $appId Application
+ * @param string $secret Facebook API secret
+ *
+ * @return Facebook A Facebook SDK obj
+ */
+ static function getFacebook($appId = null, $secret = null)
+ {
+ // Check defaults and configuration for application ID and secret
+ if (empty($appId)) {
+ $appId = common_config('facebook', 'appid');
+ }
+
+ if (empty($secret)) {
+ $secret = common_config('facebook', 'secret');
+ }
+
+ // If there's no app ID and secret set in the local config, look
+ // for a global one
+ if (empty($appId) || empty($secret)) {
+ $appId = common_config('facebook', 'global_appid');
+ $secret = common_config('facebook', 'global_secret');
+ }
+
+ return new Facebook(
+ array(
+ 'appId' => $appId,
+ 'secret' => $secret,
+ 'cookie' => true
+ )
+ );
+ }
+
+ /*
+ * Broadcast a notice to Facebook
+ *
+ * @param Notice $notice the notice to send
+ */
+ static function facebookBroadcastNotice($notice)
+ {
+ $client = new Facebookclient($notice);
+ $client->sendNotice();
+ }
+
+ /*
+ * Should the notice go to Facebook?
+ */
+ function isFacebookBound() {
+
+ if (empty($this->flink)) {
+ common_log(
+ LOG_WARN,
+ sprintf(
+ "No Foreign_link to Facebook for the author of notice %d.",
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ return false;
+ }
+
+ // Avoid a loop
+ if ($this->notice->source == 'Facebook') {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Skipping notice %d because its source is Facebook.',
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ return false;
+ }
+
+ // If the user does not want to broadcast to Facebook, move along
+ if (!($this->flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Skipping notice %d because user has FOREIGN_NOTICE_SEND bit off.',
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ return false;
+ }
+
+ // If it's not a reply, or if the user WANTS to send @-replies,
+ // then, yeah, it can go to Facebook.
+ if (!preg_match('/@[a-zA-Z0-9_]{1,15}\b/u', $this->notice->content) ||
+ ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * Determine whether we should send this notice using the Graph API or the
+ * old REST API and then dispatch
+ */
+ function sendNotice()
+ {
+ // If there's nothing in the credentials field try to send via
+ // the Old Rest API
+
+ if (empty($this->flink->credentials)) {
+ $this->sendOldRest();
+ } else {
+
+ // Otherwise we most likely have an access token
+ $this->sendGraph();
+ }
+ }
+
+ /*
+ * Send a notice to Facebook using the Graph API
+ */
+ function sendGraph()
+ {
+ common_debug("Send notice via Graph API", __FILE__);
+ }
+
+ /*
+ * Send a notice to Facebook using the deprecated Old REST API. We need this
+ * for backwards compatibility. Users who signed up for Facebook bridging
+ * using the old Facebook Canvas application do not have an OAuth 2.0
+ * access token.
+ */
+ function sendOldRest()
+ {
+ if (isFacebookBound()) {
+
+ try {
+
+ $canPublish = $this->checkPermission('publish_stream');
+ $canUpdate = $this->checkPermission('status_update');
+
+ // Post to Facebook
+ if ($notice->hasAttachments() && $canPublish == 1) {
+ $this->restPublishStream();
+ } elseif ($canUpdate == 1 || $canPublish == 1) {
+ $this->restStatusUpdate();
+ } else {
+
+ $msg = 'Not sending notice %d to Facebook because user %s '
+ . '(%d), fbuid %s, does not have \'status_update\' '
+ . 'or \'publish_stream\' permission.';
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ $msg,
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $this->flink->foreign_id
+ ),
+ __FILE__
+ );
+ }
+
+ } catch (FacebookRestClientException $e) {
+ return $this->handleFacebookError($e);
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * Query Facebook to to see if a user has permission
+ *
+ *
+ *
+ * @param $permission the permission to check for - must be either
+ * public_stream or status_update
+ *
+ * @return boolean result
+ */
+ function checkPermission($permission)
+ {
+
+ if (!in_array($permission, array('publish_stream', 'status_update'))) {
+ throw new ServerExpception("No such permission!");
+ }
+
+ $fbuid = $this->flink->foreign_link;
+
+ common_debug(
+ sprintf(
+ 'Checking for %s permission for user %s (%d), fbuid %s',
+ $permission,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ // NOTE: $this->oldRestClient->users_hasAppPermission() has been
+ // returning bogus results, so we're using FQL to check for
+ // permissions
+
+ $fql = sprintf(
+ "SELECT %s FROM permissions WHERE uid = %s",
+ $permission,
+ $fbuid
+ );
+
+ $result = $this->oldRestClient->fql_query($fql);
+
+ $hasPermission = 0;
+
+ if (isset($result[0][$permission])) {
+ $canPublish = $result[0][$permission];
+ }
+
+ if ($hasPermission == 1) {
+
+ common_debug(
+ sprintf(
+ '%s (%d), fbuid %s has %s permission',
+ $permission,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ return true;
+
+ } else {
+
+ $logMsg = '%s (%d), fbuid $fbuid does NOT have %s permission.'
+ . 'Facebook returned: %s';
+
+ common_debug(
+ sprintf(
+ $logMsg,
+ $this->user->nickname,
+ $this->user->id,
+ $permission,
+ $fbuid,
+ var_export($result, true)
+ ),
+ __FILE__
+ );
+
+ return false;
+
+ }
+
+ }
+
+ function handleFacebookError($e)
+ {
+ $fbuid = $this->flink->foreign_id;
+ $code = $e->getCode();
+ $errmsg = $e->getMessage();
+
+ // XXX: Check for any others?
+ switch($code) {
+ case 100: // Invalid parameter
+ $msg = 'Facebook claims notice %d was posted with an invalid '
+ . 'parameter (error code 100 - %s) Notice details: '
+ . '[nickname=%s, user id=%d, fbuid=%d, content="%s"]. '
+ . 'Dequeing.';
+ common_log(
+ LOG_ERR, sprintf(
+ $msg,
+ $this->notice->id,
+ $errmsg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid,
+ $this->notice->content
+ ),
+ __FILE__
+ );
+ return true;
+ break;
+ case 200: // Permissions error
+ case 250: // Updating status requires the extended permission status_update
+ $this->disconnect();
+ return true; // dequeue
+ break;
+ case 341: // Feed action request limit reached
+ $msg = '%s (userid=%d, fbuid=%d) has exceeded his/her limit '
+ . 'for posting notices to Facebook today. Dequeuing '
+ . 'notice %d';
+ common_log(
+ LOG_INFO, sprintf(
+ $msg,
+ $user->nickname,
+ $user->id,
+ $fbuid,
+ $this->notice->id
+ ),
+ __FILE__
+ );
+ // @fixme: We want to rety at a later time when the throttling has expired
+ // instead of just giving up.
+ return true;
+ break;
+ default:
+ $msg = 'Facebook returned an error we don\'t know how to deal with '
+ . 'when posting notice %d. Error code: %d, error message: "%s"'
+ . ' Notice details: [nickname=%s, user id=%d, fbuid=%d, '
+ . 'notice content="%s"]. Dequeing.';
+ common_log(
+ LOG_ERR, sprintf(
+ $msg,
+ $this->notice->id,
+ $code,
+ $errmsg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid,
+ $this->notice->content
+ ),
+ __FILE__
+ );
+ return true; // dequeue
+ break;
+ }
+ }
+
+ function restStatusUpdate()
+ {
+ $fbuid = $this->flink->foreign_id;
+
+ common_debug(
+ sprintf(
+ "Attempting to post notice %d as a status update for %s (%d), fbuid %s",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $result = $this->oldRestClient->users_setStatus(
+ $this->notice->content,
+ $fbuid,
+ false,
+ true
+ );
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ "Posted notice %s as a status update for %s (%d), fbuid %s",
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ }
+
+ function restPublishStream()
+ {
+ $fbuid = $this->flink->foreign_id;
+
+ common_debug(
+ sprintf(
+ 'Attempting to post notice %d as stream item with attachment for '
+ . '%s (%d) fbuid %s',
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $fbattachment = format_attachments($notice->attachments());
+
+ $this->oldRestClient->stream_publish(
+ $this->notice->content,
+ $fbattachment,
+ null,
+ null,
+ $fbuid
+ );
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Posted notice %d as a stream item with attachment for %s '
+ . '(%d), fbuid %s',
+ $this->notice->id,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ }
+
+ function format_attachments($attachments)
+ {
+ $fbattachment = array();
+ $fbattachment['media'] = array();
+
+ foreach($attachments as $attachment)
+ {
+ if($enclosure = $attachment->getEnclosure()){
+ $fbmedia = get_fbmedia_for_attachment($enclosure);
+ }else{
+ $fbmedia = get_fbmedia_for_attachment($attachment);
+ }
+ if($fbmedia){
+ $fbattachment['media'][]=$fbmedia;
+ }else{
+ $fbattachment['name'] = ($attachment->title ?
+ $attachment->title : $attachment->url);
+ $fbattachment['href'] = $attachment->url;
+ }
+ }
+ if(count($fbattachment['media'])>0){
+ unset($fbattachment['name']);
+ unset($fbattachment['href']);
+ }
+ return $fbattachment;
+ }
+
+ /**
+ * given an File objects, returns an associative array suitable for Facebook media
+ */
+ function get_fbmedia_for_attachment($attachment)
+ {
+ $fbmedia = array();
+
+ if (strncmp($attachment->mimetype, 'image/', strlen('image/')) == 0) {
+ $fbmedia['type'] = 'image';
+ $fbmedia['src'] = $attachment->url;
+ $fbmedia['href'] = $attachment->url;
+ } else if ($attachment->mimetype == 'audio/mpeg') {
+ $fbmedia['type'] = 'mp3';
+ $fbmedia['src'] = $attachment->url;
+ }else if ($attachment->mimetype == 'application/x-shockwave-flash') {
+ $fbmedia['type'] = 'flash';
+
+ // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
+ // says that imgsrc is required... but we have no value to put in it
+ // $fbmedia['imgsrc']='';
+
+ $fbmedia['swfsrc'] = $attachment->url;
+ }else{
+ return false;
+ }
+ return $fbmedia;
+ }
+
+ function disconnect()
+ {
+ $fbuid = $this->flink->foreign_link;
+
+ common_log(
+ LOG_INFO,
+ sprintf(
+ 'Removing Facebook link for %s (%d), fbuid %s',
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+
+ $result = $flink->delete();
+
+ if (empty($result)) {
+ common_log(
+ LOG_ERR,
+ sprintf(
+ 'Could not remove Facebook link for %s (%d), fbuid %s',
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ }
+
+ // Notify the user that we are removing their Facebook link
+
+ $result = $this->mailFacebookDisconnect();
+
+ if (!$result) {
+
+ $msg = 'Unable to send email to notify %s (%d), fbuid %s '
+ . 'about his/her Facebook link being removed.';
+
+ common_log(
+ LOG_WARNING,
+ sprintf(
+ $msg,
+ $this->user->nickname,
+ $this->user->id,
+ $fbuid
+ ),
+ __FILE__
+ );
+ }
+ }
+
+ /**
+ * Send a mail message to notify a user that her Facebook link
+ * has been terminated.
+ *
+ * @return boolean success flag
+ */
+ function mailFacebookDisconnect()
+ {
+ $profile = $user->getProfile();
+
+ $siteName = common_config('site', 'name');
+
+ common_switch_locale($user->language);
+
+ $subject = sprintf(
+ _m('Your Facebook connection has been removed'),
+ $siteName
+ );
+
+ $msg = <<<BODY
+Hi, %1$s. We're sorry to inform you we are unable to publish your notice to
+Facebook, and have removed the connection between your %2$s account and Facebook.
+
+This may have happened because you have removed permission for %2$s to post on
+your behalf, or perhaps you have deactivated your Facebook account. You can
+reconnect your %s account to Facebook at any time by logging in with Facebook
+again.
+BODY;
+ $body = sprintf(
+ _m($msg),
+ $this->user->nickname,
+ $siteName
+ );
+
+ common_switch_locale();
+
+ return mail_to_user($this->user, $subject, $body);
+ }
+
+}
diff --git a/plugins/FacebookSSO/lib/facebookqueuehandler.php b/plugins/FacebookSSO/lib/facebookqueuehandler.php
new file mode 100644
index 000000000..af96d35c4
--- /dev/null
+++ b/plugins/FacebookSSO/lib/facebookqueuehandler.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Queuehandler for Facebook transport
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
+
+class FacebookQueueHandler extends QueueHandler
+{
+ function transport()
+ {
+ return 'facebook';
+ }
+
+ function handle($notice)
+ {
+ if ($this->_isLocal($notice)) {
+ return facebookBroadcastNotice($notice);
+ }
+ return true;
+ }
+
+ /**
+ * Determine whether the notice was locally created
+ *
+ * @param Notice $notice the notice
+ *
+ * @return boolean locality
+ */
+ function _isLocal($notice)
+ {
+ return ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC);
+ }
+}