Merge branch 'master' of gitorious.org:statusnet/mainline

This commit is contained in:
Brion Vibber 2010-05-25 13:11:36 -07:00
commit e68d2c9015
4 changed files with 299 additions and 168 deletions

View File

@ -97,15 +97,20 @@ class Notice extends Memcached_DataObject
// For auditing purposes, save a record that the notice
// was deleted.
$deleted = new Deleted_notice();
// @fixme we have some cases where things get re-run and so the
// insert fails.
$deleted = Deleted_notice::staticGet('id', $this->id);
if (!$deleted) {
$deleted = new Deleted_notice();
$deleted->id = $this->id;
$deleted->profile_id = $this->profile_id;
$deleted->uri = $this->uri;
$deleted->created = $this->created;
$deleted->deleted = common_sql_now();
$deleted->id = $this->id;
$deleted->profile_id = $this->profile_id;
$deleted->uri = $this->uri;
$deleted->created = $this->created;
$deleted->deleted = common_sql_now();
$deleted->insert();
$deleted->insert();
}
// Clear related records

View File

@ -45,7 +45,9 @@ class Facebook {
public $user;
public $profile_user;
public $canvas_user;
public $ext_perms = array();
protected $base_domain;
/*
* Create a Facebook client like this:
*
@ -104,17 +106,17 @@ class Facebook {
*
* For nitty-gritty details of when each of these is used, check out
* http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
*
* @param bool resolve_auth_token convert an auth token into a session
*/
public function validate_fb_params($resolve_auth_token=true) {
public function validate_fb_params() {
$this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
// note that with preload FQL, it's possible to receive POST params in
// addition to GET, so use a different prefix to differentiate them
if (!$this->fb_params) {
$fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
$fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
$fb_post_params = $this->get_valid_fb_params($_POST,
48 * 3600, // 48 hours
'fb_post_sig');
$this->fb_params = array_merge($fb_params, $fb_post_params);
}
@ -128,6 +130,9 @@ class Facebook {
$this->fb_params['canvas_user'] : null;
$this->base_domain = isset($this->fb_params['base_domain']) ?
$this->fb_params['base_domain'] : null;
$this->ext_perms = isset($this->fb_params['ext_perms']) ?
explode(',', $this->fb_params['ext_perms'])
: array();
if (isset($this->fb_params['session_key'])) {
$session_key = $this->fb_params['session_key'];
@ -141,13 +146,11 @@ class Facebook {
$this->set_user($user,
$session_key,
$expires);
}
// if no Facebook parameters were found in the GET or POST variables,
// then fall back to cookies, which may have cached user information
// Cookies are also used to receive session data via the Javascript API
else if ($cookies =
$this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
} else if ($cookies =
$this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
// if no Facebook parameters were found in the GET or POST variables,
// then fall back to cookies, which may have cached user information
// Cookies are also used to receive session data via the Javascript API
$base_domain_cookie = 'base_domain_' . $this->api_key;
if (isset($_COOKIE[$base_domain_cookie])) {
$this->base_domain = $_COOKIE[$base_domain_cookie];
@ -160,25 +163,6 @@ class Facebook {
$cookies['session_key'],
$expires);
}
// finally, if we received no parameters, but the 'auth_token' GET var
// is present, then we are in the middle of auth handshake,
// so go ahead and create the session
else if ($resolve_auth_token && isset($_GET['auth_token']) &&
$session = $this->do_get_session($_GET['auth_token'])) {
if ($this->generate_session_secret &&
!empty($session['secret'])) {
$session_secret = $session['secret'];
}
if (isset($session['base_domain'])) {
$this->base_domain = $session['base_domain'];
}
$this->set_user($session['uid'],
$session['session_key'],
$session['expires'],
isset($session_secret) ? $session_secret : null);
}
return !empty($this->fb_params);
}
@ -309,11 +293,28 @@ class Facebook {
// require_add and require_install have been removed.
// see http://developer.facebook.com/news.php?blog=1&story=116 for more details
public function require_login() {
if ($user = $this->get_loggedin_user()) {
public function require_login($required_permissions = '') {
$user = $this->get_loggedin_user();
$has_permissions = true;
if ($required_permissions) {
$this->require_frame();
$permissions = array_map('trim', explode(',', $required_permissions));
foreach ($permissions as $permission) {
if (!in_array($permission, $this->ext_perms)) {
$has_permissions = false;
break;
}
}
}
if ($user && $has_permissions) {
return $user;
}
$this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
$this->redirect(
$this->get_login_url(self::current_url(), $this->in_frame(),
$required_permissions));
}
public function require_frame() {
@ -342,10 +343,11 @@ class Facebook {
return $page . '?' . http_build_query($params);
}
public function get_login_url($next, $canvas) {
public function get_login_url($next, $canvas, $req_perms = '') {
$page = self::get_facebook_url().'/login.php';
$params = array('api_key' => $this->api_key,
'v' => '1.0');
$params = array('api_key' => $this->api_key,
'v' => '1.0',
'req_perms' => $req_perms);
if ($next) {
$params['next'] = $next;

View File

@ -569,7 +569,7 @@ function toggleDisplay(id, type) {
return $this->call_method('facebook.events.invite',
array('eid' => $eid,
'uids' => $uids,
'personal_message', $personal_message));
'personal_message' => $personal_message));
}
/**
@ -1350,53 +1350,6 @@ function toggleDisplay(id, type) {
);
}
/**
* Dashboard API
*/
/**
* Set the news for the specified user.
*
* @param int $uid The user for whom you are setting news for
* @param string $news Text of news to display
*
* @return bool Success
*/
public function dashboard_setNews($uid, $news) {
return $this->call_method('facebook.dashboard.setNews',
array('uid' => $uid,
'news' => $news)
);
}
/**
* Get the current news of the specified user.
*
* @param int $uid The user to get the news of
*
* @return string The text of the current news for the user
*/
public function dashboard_getNews($uid) {
return json_decode(
$this->call_method('facebook.dashboard.getNews',
array('uid' => $uid)
), true);
}
/**
* Set the news for the specified user.
*
* @param int $uid The user you are clearing the news of
*
* @return bool Success
*/
public function dashboard_clearNews($uid) {
return $this->call_method('facebook.dashboard.clearNews',
array('uid' => $uid)
);
}
/**
* Creates a note with the specified title and content.
@ -2005,7 +1958,7 @@ function toggleDisplay(id, type) {
* @return array A list of strings describing any compile errors for the
* submitted FBML
*/
function profile_setFBML($markup,
public function profile_setFBML($markup,
$uid=null,
$profile='',
$profile_action='',
@ -3267,9 +3220,8 @@ function toggleDisplay(id, type) {
} else {
$get['v'] = '1.0';
}
if (isset($this->use_ssl_resources) &&
$this->use_ssl_resources) {
$post['return_ssl_resources'] = true;
if (isset($this->use_ssl_resources)) {
$post['return_ssl_resources'] = (bool) $this->use_ssl_resources;
}
return array($get, $post);
}

View File

@ -81,114 +81,286 @@ function isFacebookBound($notice, $flink) {
function facebookBroadcastNotice($notice)
{
$facebook = getFacebook();
$flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
$flink = Foreign_link::getByUserID(
$notice->profile_id,
FACEBOOK_SERVICE
);
if (isFacebookBound($notice, $flink)) {
// Okay, we're good to go, update the FB status
$status = null;
$fbuid = $flink->foreign_id;
$user = $flink->getUser();
$attachments = $notice->attachments();
try {
// Get the status 'verb' (prefix) the user has set
// Check permissions
// XXX: Does this call count against our per user FB request limit?
// If so we should consider storing verb elsewhere or not storing
common_debug(
'FacebookPlugin - checking for publish_stream permission for user '
. "$user->nickname ($user->id), Facebook UID: $fbuid"
);
$prefix = trim($facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX,
$fbuid));
// NOTE: $facebook->api_client->users_hasAppPermission('publish_stream', $fbuid)
// has been returning bogus results, so we're using FQL to check for
// publish_stream permission now
$status = "$prefix $notice->content";
$fql = "SELECT publish_stream FROM permissions WHERE uid = $fbuid";
$result = $facebook->api_client->fql_query($fql);
common_debug("FacebookPlugin - checking for publish_stream permission for user $user->id");
$canPublish = 0;
$can_publish = $facebook->api_client->users_hasAppPermission('publish_stream',
$fbuid);
if (!empty($result)) {
$canPublish = $result[0]['publish_stream'];
}
common_debug("FacebookPlugin - checking for status_update permission for user $user->id");
if ($canPublish == 1) {
common_debug(
"FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
. 'has publish_stream permission.'
);
} else {
common_debug(
"FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
. 'does NOT have publish_stream permission. Facebook '
. 'returned: ' . var_export($result, true)
);
}
$can_update = $facebook->api_client->users_hasAppPermission('status_update',
$fbuid);
if (!empty($attachments) && $can_publish == 1) {
$fbattachment = format_attachments($attachments);
$facebook->api_client->stream_publish($status, $fbattachment,
null, null, $fbuid);
common_log(LOG_INFO,
"FacebookPlugin - Posted notice $notice->id w/attachment " .
"to Facebook user's stream (fbuid = $fbuid).");
} elseif ($can_update == 1 || $can_publish == 1) {
$facebook->api_client->users_setStatus($status, $fbuid, false, true);
common_log(LOG_INFO,
"FacebookPlugin - Posted notice $notice->id to Facebook " .
"as a status update (fbuid = $fbuid).");
common_debug(
'FacebookPlugin - checking for status_update permission for user '
. "$user->nickname ($user->id), Facebook UID: $fbuid. "
);
$canUpdate = $facebook->api_client->users_hasAppPermission(
'status_update',
$fbuid
);
if ($canUpdate == 1) {
common_debug(
"FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
. 'has status_update permission.'
);
} else {
common_debug(
"FacebookPlugin - $user->nickname ($user->id), Facebook UID: $fbuid "
.'does NOT have status_update permission. Facebook '
. 'returned: ' . var_export($canPublish, true)
);
}
// Post to Facebook
if ($notice->hasAttachments() && $canPublish == 1) {
publishStream($notice, $user, $fbuid);
} elseif ($canUpdate == 1 || $canPublish == 1) {
statusUpdate($notice, $user, $fbuid);
} else {
$msg = "FacebookPlugin - Not sending notice $notice->id to Facebook " .
"because user $user->nickname hasn't given the " .
"because user $user->nickname has not given the " .
'Facebook app \'status_update\' or \'publish_stream\' permission.';
common_log(LOG_WARNING, $msg);
}
// Finally, attempt to update the user's profile box
if ($can_publish == 1 || $can_update == 1) {
updateProfileBox($facebook, $flink, $notice);
if ($canPublish == 1 || $canUpdate == 1) {
updateProfileBox($facebook, $flink, $notice, $user);
}
} catch (FacebookRestClientException $e) {
$code = $e->getCode();
$msg = "FacebookPlugin - Facebook returned error code $code: " .
$e->getMessage() . ' - ' .
"Unable to update Facebook status (notice $notice->id) " .
"for $user->nickname (user id: $user->id)!";
common_log(LOG_WARNING, $msg);
if ($code == 100 || $code == 200 || $code == 250) {
// 100 The account is 'inactive' (probably - this is not well documented)
// 200 The application does not have permission to operate on the passed in uid parameter.
// 250 Updating status requires the extended permission status_update or publish_stream.
// see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML
remove_facebook_app($flink);
} else if ($code == 341) {
// 341 Feed action request limit reached - Unable to update Facebook status
// Reposting immediately probably won't work, so drop the message for now. :(
common_log(LOG_ERR, "Facebook rate limit hit: dropping notice $notice->id");
return true;
} else {
// Try sending again later.
//
// @fixme at the moment, returning false here could lead to an infinite loop
// if the error condition isn't actually transitory.
//
// Temporarily throwing an exception to kill the process so it'll hit our
// retry limits.
throw new Exception("Facebook error $code on notice $notice->id");
return false;
}
return handleFacebookError($e, $notice, $flink);
}
}
return true;
}
function updateProfileBox($facebook, $flink, $notice) {
$fbaction = new FacebookAction($output = 'php://output',
$indent = null, $facebook, $flink);
function handleFacebookError($e, $notice, $flink)
{
$fbuid = $flink->foreign_id;
$user = $flink->getUser();
$code = $e->getCode();
$errmsg = $e->getMessage();
// XXX: Check for any others?
switch($code) {
case 100: // Invalid parameter
$msg = "FacebookPlugin - Facebook claims notice %d was posted with an invalid parameter (error code 100):"
. "\"%s\" (Notice details: nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). "
. "Removing notice from the Facebook queue for safety.";
common_log(
LOG_ERR, sprintf(
$msg,
$notice->id,
$errmsg,
$user->nickname,
$user->id,
$fbuid,
$notice->content
)
);
return true;
break;
case 200: // Permissions error
case 250: // Updating status requires the extended permission status_update
remove_facebook_app($flink);
return true; // dequeue
break;
case 341: // Feed action request limit reached
$msg = "FacebookPlugin - User %s (User ID=%d, Facebook ID=%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,
$notice->id
)
);
// @fixme: We want to rety at a later time when the throttling has expired
// instead of just giving up.
return true;
break;
default:
$msg = "FacebookPlugin - Facebook returned an error we don't know how to deal with while trying to "
. "post notice %d. Error code: %d, error message: \"%s\". (Notice details: "
. "nickname=%s, user ID=%d, Facebook ID=%d, notice content=\"%s\"). Removing notice "
. "from the Facebook queue for safety.";
common_log(
LOG_ERR, sprintf(
$msg,
$notice->id,
$code,
$errmsg,
$user->nickname,
$user->id,
$fbuid,
$notice->content
)
);
return true; // dequeue
break;
}
}
function statusUpdate($notice, $user, $fbuid)
{
common_debug(
"FacebookPlugin - Attempting to post notice $notice->id "
. "as a status update for $user->nickname ($user->id), "
. "Facebook UID: $fbuid"
);
$text = formatNotice($notice, $user, $fbuid);
$facebook = getFacebook();
$result = $facebook->api_client->users_setStatus(
$text,
$fbuid,
false,
true
);
common_debug('Facebook returned: ' . var_export($result, true));
common_log(
LOG_INFO,
"FacebookPlugin - Posted notice $notice->id as a status "
. "update for $user->nickname ($user->id), "
. "Facebook UID: $fbuid"
);
}
function publishStream($notice, $user, $fbuid)
{
common_debug(
"FacebookPlugin - Attempting to post notice $notice->id "
. "as stream item with attachment for $user->nickname ($user->id), "
. "Facebook UID: $fbuid"
);
$text = formatNotice($notice, $user, $fbuid);
$fbattachment = format_attachments($notice->attachments());
$facebook = getFacebook();
$facebook->api_client->stream_publish(
$text,
$fbattachment,
null,
null,
$fbuid
);
common_log(
LOG_INFO,
"FacebookPlugin - Posted notice $notice->id as a stream "
. "item with attachment for $user->nickname ($user->id), "
. "Facebook UID: $fbuid"
);
}
function formatNotice($notice, $user, $fbuid)
{
// Get the status 'verb' the user has set, if any
common_debug(
"FacebookPlugin - Looking to see if $user->nickname ($user->id), "
. "Facebook UID: $fbuid has set a verb for Facebook posting..."
);
$facebook = getFacebook();
$verb = trim(
$facebook->api_client->data_getUserPreference(
FACEBOOK_NOTICE_PREFIX,
$fbuid
)
);
common_debug("Facebook returned " . var_export($verb, true));
$text = null;
if (!empty($verb)) {
common_debug("FacebookPlugin - found a verb: $verb");
$text = trim($verb) . ' ' . $notice->content;
} else {
common_debug("FacebookPlugin - no verb found.");
$text = $notice->content;
}
return $text;
}
function updateProfileBox($facebook, $flink, $notice, $user) {
$facebook = getFacebook();
$fbaction = new FacebookAction(
$output = 'php://output',
$indent = null,
$facebook,
$flink
);
$fbuid = $flink->foreign_id;
common_debug(
'FacebookPlugin - Attempting to update profile box with '
. "content from notice $notice->id for $user->nickname ($user->id), "
. "Facebook UID: $fbuid"
);
$fbaction->updateProfileBox($notice);
common_debug(
'FacebookPlugin - finished updating profile box for '
. "$user->nickname ($user->id) Facebook UID: $fbuid"
);
}
function format_attachments($attachments)