From ca4c0a160122d20f95877102f9712aee45c7afb8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 16 Nov 2010 02:30:08 +0000 Subject: [PATCH] - Map notices to Facebook stream items - rename plugin FacebookBridgePlugin - delete/like/unlike notices across the bridge --- ...SSOPlugin.php => FacebookBridgePlugin.php} | 74 +++- .../actions/facebookdeauthorize.php | 6 +- .../actions/facebookfinishlogin.php | 190 ++++++---- plugins/FacebookSSO/actions/facebooklogin.php | 2 +- .../FacebookSSO/classes/Notice_to_item.php | 190 ++++++++++ plugins/FacebookSSO/lib/facebookclient.php | 351 +++++++++++++++++- 6 files changed, 716 insertions(+), 97 deletions(-) rename plugins/FacebookSSO/{FacebookSSOPlugin.php => FacebookBridgePlugin.php} (86%) create mode 100644 plugins/FacebookSSO/classes/Notice_to_item.php diff --git a/plugins/FacebookSSO/FacebookSSOPlugin.php b/plugins/FacebookSSO/FacebookBridgePlugin.php similarity index 86% rename from plugins/FacebookSSO/FacebookSSOPlugin.php rename to plugins/FacebookSSO/FacebookBridgePlugin.php index 19d61211d8..c30ea15440 100644 --- a/plugins/FacebookSSO/FacebookSSOPlugin.php +++ b/plugins/FacebookSSO/FacebookBridgePlugin.php @@ -45,10 +45,9 @@ define("FACEBOOK_SERVICE", 2); * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ -class FacebookSSOPlugin extends Plugin +class FacebookBridgePlugin extends Plugin { public $appId = null; // Facebook application ID - public $apikey = null; // Facebook API key (for deprecated "Old REST API") public $secret = null; // Facebook application secret public $facebook = null; // Facebook application instance public $dir = null; // Facebook SSO plugin dir @@ -64,7 +63,6 @@ class FacebookSSOPlugin extends Plugin { $this->facebook = Facebookclient::getFacebook( $this->appId, - $this->apikey, $this->secret ); @@ -101,12 +99,32 @@ class FacebookSSOPlugin extends Plugin case 'FacebookQueueHandler': include_once $dir . '/lib/' . strtolower($cls) . '.php'; return false; + case 'Notice_to_item': + include_once $dir . '/classes/' . $cls . '.php'; + return false; default: return true; } } + /** + * Database schema setup + * + * We maintain a table mapping StatusNet notices to Facebook items + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('notice_to_item', Notice_to_item::schemaDef()); + return true; + } + /* * Does this $action need the Facebook JavaScripts? */ @@ -436,6 +454,54 @@ ENDOFSCRIPT; } } + /** + * If a notice gets deleted, remove the Notice_to_item mapping and + * delete the item on Facebook + * + * @param User $user The user doing the deleting + * @param Notice $notice The notice getting deleted + * + * @return boolean hook value + */ + function onStartDeleteOwnNotice(User $user, Notice $notice) + { + $client = new Facebookclient($notice); + $client->streamRemove(); + + return true; + } + + /** + * Notify remote users when their notices get favorited. + * + * @param Profile or User $profile of local user doing the faving + * @param Notice $notice being favored + * @return hook return value + */ + function onEndFavorNotice(Profile $profile, Notice $notice) + { + $client = new Facebookclient($notice); + $client->like(); + + return true; + } + + /** + * Notify remote users when their notices get de-favorited. + * + * @param Profile $profile Profile person doing the de-faving + * @param Notice $notice Notice being favored + * + * @return hook return value + */ + function onEndDisfavorNotice(Profile $profile, Notice $notice) + { + $client = new Facebookclient($notice); + $client->unLike(); + + return true; + } + /* * Add version info for this plugin * @@ -447,7 +513,7 @@ ENDOFSCRIPT; 'name' => 'Facebook Single-Sign-On', 'version' => STATUSNET_VERSION, 'author' => 'Craig Andrews, Zach Copley', - 'homepage' => 'http://status.net/wiki/Plugin:FacebookSSO', + 'homepage' => 'http://status.net/wiki/Plugin:FacebookBridge', 'rawdescription' => _m('A plugin for integrating StatusNet with Facebook.') ); diff --git a/plugins/FacebookSSO/actions/facebookdeauthorize.php b/plugins/FacebookSSO/actions/facebookdeauthorize.php index fb4afa13bc..cb816fc54a 100644 --- a/plugins/FacebookSSO/actions/facebookdeauthorize.php +++ b/plugins/FacebookSSO/actions/facebookdeauthorize.php @@ -112,7 +112,7 @@ class FacebookdeauthorizeAction extends Action common_log( LOG_WARNING, sprintf( - '%s (%d), fbuid $s has deauthorized his/her Facebook ' + '%s (%d), fbuid %d has deauthorized his/her Facebook ' . 'connection but hasn\'t set a password so s/he ' . 'is locked out.', $user->nickname, @@ -135,8 +135,8 @@ class FacebookdeauthorizeAction extends Action ); } else { // It probably wasn't Facebook that hit this action, - // so redirect to the login page - common_redirect(common_local_url('login'), 303); + // so redirect to the public timeline + common_redirect(common_local_url('public'), 303); } } } diff --git a/plugins/FacebookSSO/actions/facebookfinishlogin.php b/plugins/FacebookSSO/actions/facebookfinishlogin.php index e61f351547..2174c5ad4a 100644 --- a/plugins/FacebookSSO/actions/facebookfinishlogin.php +++ b/plugins/FacebookSSO/actions/facebookfinishlogin.php @@ -97,7 +97,7 @@ class FacebookfinishloginAction extends Action parent::handle($args); if (common_is_real_login()) { - + // User is already logged in, are her accounts already linked? $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); @@ -121,48 +121,52 @@ class FacebookfinishloginAction extends Action } else { // Possibly reconnect an existing account - + $this->connectUser(); } } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $this->handlePost(); + } else { + $this->tryLogin(); + } + } - $token = $this->trimmed('token'); + function handlePost() + { + $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { + if (!$token || $token != common_session_token()) { + $this->showForm( + _m('There was a problem with your session token. Try again, please.') + ); + return; + } + + if ($this->arg('create')) { + + if (!$this->boolean('license')) { $this->showForm( - _m('There was a problem with your session token. Try again, please.')); + _m('You can\'t register if you don\'t agree to the license.'), + $this->trimmed('newname') + ); return; } - if ($this->arg('create')) { - - if (!$this->boolean('license')) { - $this->showForm( - _m('You can\'t register if you don\'t agree to the license.'), - $this->trimmed('newname') - ); - return; - } - - // We has a valid Facebook session and the Facebook user has - // agreed to the SN license, so create a new user - $this->createNewUser(); - - } else if ($this->arg('connect')) { + // We has a valid Facebook session and the Facebook user has + // agreed to the SN license, so create a new user + $this->createNewUser(); - $this->connectNewUser(); + } else if ($this->arg('connect')) { - } else { + $this->connectNewUser(); - $this->showForm( - _m('An unknown error has occured.'), - $this->trimmed('newname') - ); - } } else { - $this->tryLogin(); + $this->showForm( + _m('An unknown error has occured.'), + $this->trimmed('newname') + ); } } @@ -173,7 +177,7 @@ class FacebookfinishloginAction extends Action $this->element('div', array('class' => 'error'), $this->error); } else { - + $this->element( 'div', 'instructions', // TRANS: %s is the site name. @@ -343,19 +347,23 @@ class FacebookfinishloginAction extends Action 'nickname' => $nickname, 'fullname' => $this->fbuser['first_name'] . ' ' . $this->fbuser['last_name'], - 'email' => $this->fbuser['email'], - 'email_confirmed' => true, 'homepage' => $this->fbuser['website'], 'bio' => $this->fbuser['about'], 'location' => $this->fbuser['location']['name'] ); + // It's possible that the email address is already in our + // DB. It's a unique key, so we need to check + if ($this->isNewEmail($this->fbuser['email'])) { + $args['email'] = $this->fbuser['email']; + $args['email_confirmed'] = true; + } + if (!empty($invite)) { $args['code'] = $invite->code; } - $user = User::register($args); - + $user = User::register($args); $result = $this->flinkUser($user->id, $this->fbuid); if (!$result) { @@ -363,6 +371,9 @@ class FacebookfinishloginAction extends Action return; } + // Add a Foreign_user record + Facebookclient::addFacebookUser($this->fbuser); + $this->setAvatar($user); common_set_user($user); @@ -371,20 +382,16 @@ class FacebookfinishloginAction extends Action common_log( LOG_INFO, sprintf( - 'Registered new user %d from Facebook user %s', + 'Registered new user %s (%d) from Facebook user %s, (fbuid %d)', + $user->nickname, $user->id, + $this->fbuser['name'], $this->fbuid ), __FILE__ ); - common_redirect( - common_local_url( - 'showstream', - array('nickname' => $user->nickname) - ), - 303 - ); + $this->goHome($user->nickname); } /* @@ -401,17 +408,19 @@ class FacebookfinishloginAction extends Action // fetch the picture from Facebook $client = new HTTPClient(); - common_debug("status = $status - " . $finalUrl , __FILE__); - // fetch the actual picture $response = $client->get($picUrl); if ($response->isOk()) { $finalUrl = $client->getUrl(); - $filename = 'facebook-' . substr(strrchr($finalUrl, '/'), 1 ); - common_debug("Filename = " . $filename, __FILE__); + // Make sure the filename is unique becuase it's possible for a user + // to deauthorize our app, and then come back in as a new user but + // have the same Facebook picture (avatar URLs have a unique index + // and their URLs are based on the filenames). + $filename = 'facebook-' . common_good_rand(4) . '-' + . substr(strrchr($finalUrl, '/'), 1); $ok = file_put_contents( Avatar::path($filename), @@ -430,17 +439,20 @@ class FacebookfinishloginAction extends Action } else { + // save it as an avatar $profile = $user->getProfile(); if ($profile->setOriginal($filename)) { common_log( LOG_INFO, sprintf( - 'Saved avatar for %s (%d) from Facebook profile %s, filename = %s', + 'Saved avatar for %s (%d) from Facebook picture for ' + . '%s (fbuid %d), filename = %s', $user->nickname, $user->id, + $this->fbuser['name'], $this->fbuid, - $picture + $filename ), __FILE__ ); @@ -462,19 +474,17 @@ class FacebookfinishloginAction extends Action $user = User::staticGet('nickname', $nickname); if (!empty($user)) { - common_debug('Facebook Connect Plugin - ' . - "Legit user to connect to Facebook: $nickname"); - } - - $result = $this->flinkUser($user->id, $this->fbuid); - - if (!$result) { - $this->serverError(_m('Error connecting user to Facebook.')); - return; + common_debug( + sprintf( + 'Found a legit user to connect to Facebook: %s (%d)', + $user->nickname, + $user->id + ), + __FILE__ + ); } - common_debug('Facebook Connnect Plugin - ' . - "Connected Facebook user $this->fbuid to local user $user->id"); + $this->tryLinkUser($user); common_set_user($user); common_real_login(true); @@ -485,7 +495,12 @@ class FacebookfinishloginAction extends Action function connectUser() { $user = common_current_user(); + $this->tryLinkUser($user); + common_redirect(common_local_url('facebookfinishlogin'), 303); + } + function tryLinkUser($user) + { $result = $this->flinkUser($user->id, $this->fbuid); if (empty($result)) { @@ -495,14 +510,14 @@ class FacebookfinishloginAction extends Action common_debug( sprintf( - 'Connected Facebook user %s to local user %d', + 'Connected Facebook user %s (fbuid %d) to local user %s (%d)', + $this->fbuser['name'], $this->fbuid, + $user->nickname, $user->id ), __FILE__ ); - - common_redirect(common_local_url('facebookfinishlogin'), 303); } function tryLogin() @@ -573,7 +588,7 @@ class FacebookfinishloginAction extends Action $flink->user_id = $user_id; $flink->foreign_id = $fbuid; $flink->service = FACEBOOK_SERVICE; - + // Pull the access token from the Facebook cookies $flink->credentials = $this->facebook->getAccessToken(); @@ -595,8 +610,8 @@ class FacebookfinishloginAction extends Action // Try the full name - $fullname = trim($this->fbuser['firstname'] . - ' ' . $this->fbuser['lastname']); + $fullname = trim($this->fbuser['first_name'] . + ' ' . $this->fbuser['last_name']); if (!empty($fullname)) { $fullname = $this->nicknamize($fullname); @@ -617,20 +632,57 @@ class FacebookfinishloginAction extends Action return strtolower($str); } - function isNewNickname($str) - { - if (!Validate::string($str, array('min_length' => 1, - 'max_length' => 64, - 'format' => NICKNAME_FMT))) { + /* + * Is the desired nickname already taken? + * + * @return boolean result + */ + function isNewNickname($str) + { + if ( + !Validate::string( + $str, + array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT + ) + ) + ) { return false; } + if (!User::allowed_nickname($str)) { return false; } + if (User::staticGet('nickname', $str)) { return false; } + return true; } + /* + * Do we already have a user record with this email? + * (emails have to be unique but they can change) + * + * @param string $email the email address to check + * + * @return boolean result + */ + function isNewEmail($email) + { + // we shouldn't have to validate the format + $result = User::staticGet('email', $email); + + if (empty($result)) { + common_debug("XXXXXXXXXXXXXXXXXX We've never seen this email before!!!"); + return true; + } + common_debug("XXXXXXXXXXXXXXXXXX dupe email address!!!!"); + + return false; + } + } diff --git a/plugins/FacebookSSO/actions/facebooklogin.php b/plugins/FacebookSSO/actions/facebooklogin.php index 08c237fe6e..9a230b7241 100644 --- a/plugins/FacebookSSO/actions/facebooklogin.php +++ b/plugins/FacebookSSO/actions/facebooklogin.php @@ -89,7 +89,7 @@ class FacebookloginAction extends Action $attrs = array( 'src' => common_path( - 'plugins/FacebookSSO/images/login-button.png', + 'plugins/FacebookBridge/images/login-button.png', true ), 'alt' => 'Login with Facebook', diff --git a/plugins/FacebookSSO/classes/Notice_to_item.php b/plugins/FacebookSSO/classes/Notice_to_item.php new file mode 100644 index 0000000000..a6a8030342 --- /dev/null +++ b/plugins/FacebookSSO/classes/Notice_to_item.php @@ -0,0 +1,190 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * 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 . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; + +/** + * Data class for mapping notices to Facebook stream items + * + * Note that notice_id is unique only within a single database; if you + * want to share this data for some reason, get the notice's URI and use + * that instead, since it's universally unique. + * + * @category Action + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class Notice_to_item extends Memcached_DataObject +{ + public $__table = 'notice_to_item'; // table name + public $notice_id; // int(4) primary_key not_null + public $item_id; // varchar(255) not null + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup + * @param mixed $v Value to lookup + * + * @return Notice_to_item object found, or null for no hits + * + */ + + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Notice_to_item', $k, $v); + } + + /** + * return table definition for DB_DataObject + * + * DB_DataObject needs to know something about the table to manipulate + * instances. This method provides all the DB_DataObject needs to know. + * + * @return array array of column definitions + */ + + function table() + { + return array( + 'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, + 'item_id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, + 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL + ); + } + + static function schemaDef() + { + return array( + new ColumnDef('notice_id', 'integer', null, false, 'PRI'), + new ColumnDef('item_id', 'varchar', 255, false, 'UNI'), + new ColumnDef('created', 'datetime', null, false) + ); + } + + /** + * return key definitions for DB_DataObject + * + * DB_DataObject needs to know about keys that the table has, since it + * won't appear in StatusNet's own keys list. In most cases, this will + * simply reference your keyTypes() function. + * + * @return array list of key field names + */ + + function keys() + { + return array_keys($this->keyTypes()); + } + + /** + * return key definitions for Memcached_DataObject + * + * Our caching system uses the same key definitions, but uses a different + * method to get them. This key information is used to store and clear + * cached data, so be sure to list any key that will be used for static + * lookups. + * + * @return array associative array of key definitions, field name to type: + * 'K' for primary key: for compound keys, add an entry for each component; + * 'U' for unique keys: compound keys are not well supported here. + */ + + function keyTypes() + { + return array('notice_id' => 'K', 'item_id' => 'U'); + } + + /** + * Magic formula for non-autoincrementing integer primary keys + * + * If a table has a single integer column as its primary key, DB_DataObject + * assumes that the column is auto-incrementing and makes a sequence table + * to do this incrementation. Since we don't need this for our class, we + * overload this method and return the magic formula that DB_DataObject needs. + * + * @return array magic three-false array that stops auto-incrementing. + */ + + function sequenceKey() + { + return array(false, false, false); + } + + /** + * Save a mapping between a notice and a Facebook item + * + * @param integer $notice_id ID of the notice in StatusNet + * @param integer $item_id ID of the stream item on Facebook + * + * @return Notice_to_item new object for this value + */ + + static function saveNew($notice_id, $item_id) + { + $n2i = Notice_to_item::staticGet('notice_id', $notice_id); + + if (!empty($n2i)) { + return $n2i; + } + + $n2i = Notice_to_item::staticGet('item_id', $item_id); + + if (!empty($n2i)) { + return $n2i; + } + + common_debug( + "Mapping notice {$notice_id} to Facebook item {$item_id}", + __FILE__ + ); + + $n2i = new Notice_to_item(); + + $n2i->notice_id = $notice_id; + $n2i->item_id = $item_id; + $n2i->created = common_sql_now(); + + $n2i->insert(); + + return $n2i; + } +} diff --git a/plugins/FacebookSSO/lib/facebookclient.php b/plugins/FacebookSSO/lib/facebookclient.php index cf00b55e3a..33edf5c6b1 100644 --- a/plugins/FacebookSSO/lib/facebookclient.php +++ b/plugins/FacebookSSO/lib/facebookclient.php @@ -173,11 +173,11 @@ class Facebookclient if ($this->isFacebookBound()) { common_debug("notice is facebook bound", __FILE__); if (empty($this->flink->credentials)) { - $this->sendOldRest(); + return $this->sendOldRest(); } else { // Otherwise we most likely have an access token - $this->sendGraph(); + return $this->sendGraph(); } } else { @@ -213,6 +213,7 @@ class Facebookclient $params = array( 'access_token' => $this->flink->credentials, + // XXX: Need to worrry about length of the message? 'message' => $this->notice->content ); @@ -220,7 +221,7 @@ class Facebookclient if (!empty($attachments)) { - // We can only send one attachment with the Graph API + // We can only send one attachment with the Graph API :( $first = array_shift($attachments); @@ -240,6 +241,21 @@ class Facebookclient sprintf('/%s/feed', $fbuid), 'post', $params ); + // Save a mapping + Notice_to_item::saveNew($this->notice->id, $result['id']); + + common_log( + LOG_INFO, + sprintf( + "Posted notice %d as a stream item for %s (%d), fbuid %s", + $this->notice->id, + $this->user->nickname, + $this->user->id, + $fbuid + ), + __FILE__ + ); + } catch (FacebookApiException $e) { return $this->handleFacebookError($e); } @@ -481,24 +497,42 @@ class Facebookclient $result = $this->facebook->api( array( 'method' => 'users.setStatus', - 'status' => $this->notice->content, + 'status' => $this->formatMessage(), 'status_includes_verb' => true, 'uid' => $fbuid ) ); - common_log( - LOG_INFO, - sprintf( - "Posted notice %s as a status update for %s (%d), fbuid %s", + if ($result == 1) { // 1 is success + + 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__ + ); + + // There is no item ID returned for status update so we can't + // save a Notice_to_item mapping + + } else { + + $msg = sprintf( + "Error posting notice %s as a status update for %s (%d), fbuid %s - error code: %s", $this->notice->id, $this->user->nickname, $this->user->id, - $fbuid - ), - __FILE__ - ); + $fbuid, + $result // will contain 0, or an error + ); + throw new FacebookApiException($msg, $result); + } } /* @@ -524,25 +558,66 @@ class Facebookclient $result = $this->facebook->api( array( 'method' => 'stream.publish', - 'message' => $this->notice->content, + 'message' => $this->formatMessage(), 'attachment' => $fbattachment, 'uid' => $fbuid ) ); - common_log( - LOG_INFO, - sprintf( - 'Posted notice %d as a %s for %s (%d), fbuid %s', + if (!empty($result)) { // result will contain the item ID + + // Save a mapping + Notice_to_item::saveNew($this->notice->id, $result); + + common_log( + LOG_INFO, + sprintf( + 'Posted notice %d as a %s for %s (%d), fbuid %s', + $this->notice->id, + empty($fbattachment) ? 'stream item' : 'stream item with attachment', + $this->user->nickname, + $this->user->id, + $fbuid + ), + __FILE__ + ); + + } else { + + $msg = sprintf( + 'Could not post notice %d as a %s for %s (%d), fbuid %s - error code: %s', $this->notice->id, empty($fbattachment) ? 'stream item' : 'stream item with attachment', $this->user->nickname, $this->user->id, + $result, // result will contain an error code $fbuid - ), - __FILE__ - ); + ); + + throw new FacebookApiException($msg, $result); + } + } + /* + * Format the text message of a stream item so it's appropriate for + * sending to Facebook. If the notice is too long, truncate it, and + * add a linkback to the original notice at the end. + * + * @return String $txt the formated message + */ + function formatMessage() + { + // Start with the plaintext source of this notice... + $txt = $this->notice->content; + + // Facebook has a 420-char hardcoded max. + if (mb_strlen($statustxt) > 420) { + $noticeUrl = common_shorten_url($this->notice->uri); + $urlLen = mb_strlen($noticeUrl); + $txt = mb_substr($statustxt, 0, 420 - ($urlLen + 3)) . ' … ' . $noticeUrl; + } + + return $txt; } /* @@ -708,4 +783,240 @@ BODY; return mail_to_user($this->user, $subject, $body); } + /* + * Check to see if we have a mapping to a copy of this notice + * on Facebook + * + * @param Notice $notice the notice to check + * + * @return mixed null if it can't find one, or the id of the Facebook + * stream item + */ + static function facebookStatusId($notice) + { + $n2i = Notice_to_item::staticGet('notice_id', $notice->id); + + if (empty($n2i)) { + return null; + } else { + return $n2i->item_id; + } + } + + /* + * Save a Foreign_user record of a Facebook user + * + * @param object $fbuser a Facebook Graph API user obj + * See: http://developers.facebook.com/docs/reference/api/user + * @return mixed $result Id or key + * + */ + static function addFacebookUser($fbuser) + { + // remove any existing, possibly outdated, record + $luser = Foreign_user::getForeignUser($fbuser['id'], FACEBOOK_SERVICE); + + if (!empty($luser)) { + + $result = $luser->delete(); + + if ($result != false) { + common_log( + LOG_INFO, + sprintf( + 'Removed old Facebook user: %s, fbuid %d', + $fbuid['name'], + $fbuid['id'] + ), + __FILE__ + ); + } + } + + $fuser = new Foreign_user(); + + $fuser->nickname = $fbuser['name']; + $fuser->uri = $fbuser['link']; + $fuser->id = $fbuser['id']; + $fuser->service = FACEBOOK_SERVICE; + $fuser->created = common_sql_now(); + + $result = $fuser->insert(); + + if (empty($result)) { + common_log( + LOG_WARNING, + sprintf( + 'Failed to add new Facebook user: %s, fbuid %d', + $fbuser['name'], + $fbuser['id'] + ), + __FILE__ + ); + + common_log_db_error($fuser, 'INSERT', __FILE__); + } else { + common_log( + LOG_INFO, + sprintf( + 'Added new Facebook user: %s, fbuid %d', + $fbuser['name'], + $fbuser['id'] + ), + __FILE__ + ); + } + + return $result; + } + + /* + * Remove an item from a Facebook user's feed if we have a mapping + * for it. + */ + function streamRemove() + { + $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id); + + if (!empty($this->flink) && !empty($n2i)) { + + $result = $this->facebook->api( + array( + 'method' => 'stream.remove', + 'post_id' => $n2i->item_id, + 'uid' => $this->flink->foreign_id + ) + ); + + if (!empty($result) && result == true) { + + common_log( + LOG_INFO, + sprintf( + 'Deleted Facebook item: %s for %s (%d), fbuid %d', + $n2i->item_id, + $this->user->nickname, + $this->user->id, + $this->flink->foreign_id + ), + __FILE__ + ); + + $n2i->delete(); + + } else { + + common_log( + LOG_WARNING, + sprintf( + 'Could not deleted Facebook item: %s for %s (%d), fbuid %d', + $n2i->item_id, + $this->user->nickname, + $this->user->id, + $this->flink->foreign_id + ), + __FILE__ + ); + } + } + } + + /* + * Like an item in a Facebook user's feed if we have a mapping + * for it. + */ + function like() + { + $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id); + + if (!empty($this->flink) && !empty($n2i)) { + + $result = $this->facebook->api( + array( + 'method' => 'stream.addlike', + 'post_id' => $n2i->item_id, + 'uid' => $this->flink->foreign_id + ) + ); + + if (!empty($result) && result == true) { + + common_log( + LOG_INFO, + sprintf( + 'Added like for item: %s for %s (%d), fbuid %d', + $n2i->item_id, + $this->user->nickname, + $this->user->id, + $this->flink->foreign_id + ), + __FILE__ + ); + + } else { + + common_log( + LOG_WARNING, + sprintf( + 'Could not like Facebook item: %s for %s (%d), fbuid %d', + $n2i->item_id, + $this->user->nickname, + $this->user->id, + $this->flink->foreign_id + ), + __FILE__ + ); + } + } + } + + /* + * Unlike an item in a Facebook user's feed if we have a mapping + * for it. + */ + function unLike() + { + $n2i = Notice_to_item::staticGet('notice_id', $this->notice->id); + + if (!empty($this->flink) && !empty($n2i)) { + + $result = $this->facebook->api( + array( + 'method' => 'stream.removeLike', + 'post_id' => $n2i->item_id, + 'uid' => $this->flink->foreign_id + ) + ); + + if (!empty($result) && result == true) { + + common_log( + LOG_INFO, + sprintf( + 'Removed like for item: %s for %s (%d), fbuid %d', + $n2i->item_id, + $this->user->nickname, + $this->user->id, + $this->flink->foreign_id + ), + __FILE__ + ); + + } else { + + common_log( + LOG_WARNING, + sprintf( + 'Could not remove like for Facebook item: %s for %s (%d), fbuid %d', + $n2i->item_id, + $this->user->nickname, + $this->user->id, + $this->flink->foreign_id + ), + __FILE__ + ); + } + } + } + } -- 2.39.5