summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorZach Copley <zach@status.net>2010-11-16 02:30:08 +0000
committerZach Copley <zach@status.net>2010-11-16 02:30:08 +0000
commitca4c0a160122d20f95877102f9712aee45c7afb8 (patch)
tree5c1537511d429a40c56ec0ddd359f7665704690f /plugins
parent3c921f38de55922b1de3a331826e01cb876898a2 (diff)
- Map notices to Facebook stream items
- rename plugin FacebookBridgePlugin - delete/like/unlike notices across the bridge
Diffstat (limited to 'plugins')
-rw-r--r--plugins/FacebookSSO/FacebookBridgePlugin.php (renamed from plugins/FacebookSSO/FacebookSSOPlugin.php)74
-rw-r--r--plugins/FacebookSSO/actions/facebookdeauthorize.php6
-rw-r--r--plugins/FacebookSSO/actions/facebookfinishlogin.php190
-rw-r--r--plugins/FacebookSSO/actions/facebooklogin.php2
-rw-r--r--plugins/FacebookSSO/classes/Notice_to_item.php190
-rw-r--r--plugins/FacebookSSO/lib/facebookclient.php351
6 files changed, 716 insertions, 97 deletions
diff --git a/plugins/FacebookSSO/FacebookSSOPlugin.php b/plugins/FacebookSSO/FacebookBridgePlugin.php
index 19d61211d..c30ea1544 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 fb4afa13b..cb816fc54 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 e61f35154..2174c5ad4 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 08c237fe6..9a230b724 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 000000000..a6a803034
--- /dev/null
+++ b/plugins/FacebookSSO/classes/Notice_to_item.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Data class for storing notice-to-Facebook-item mappings
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <zach@status.net>
+ * @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 cf00b55e3..33edf5c6b 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__
+ );
+ }
+ }
+ }
+
}