]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
- Map notices to Facebook stream items
authorZach Copley <zach@status.net>
Tue, 16 Nov 2010 02:30:08 +0000 (02:30 +0000)
committerZach Copley <zach@status.net>
Tue, 16 Nov 2010 02:30:08 +0000 (02:30 +0000)
- rename plugin FacebookBridgePlugin
- delete/like/unlike notices across the bridge

plugins/FacebookSSO/FacebookBridgePlugin.php [new file with mode: 0644]
plugins/FacebookSSO/FacebookSSOPlugin.php [deleted file]
plugins/FacebookSSO/actions/facebookdeauthorize.php
plugins/FacebookSSO/actions/facebookfinishlogin.php
plugins/FacebookSSO/actions/facebooklogin.php
plugins/FacebookSSO/classes/Notice_to_item.php [new file with mode: 0644]
plugins/FacebookSSO/lib/facebookclient.php

diff --git a/plugins/FacebookSSO/FacebookBridgePlugin.php b/plugins/FacebookSSO/FacebookBridgePlugin.php
new file mode 100644 (file)
index 0000000..c30ea15
--- /dev/null
@@ -0,0 +1,523 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * A plugin for integrating Facebook with StatusNet. Includes single-sign-on
+ * and publishing notices to Facebook using Facebook's Graph API.
+ *
+ * PHP version 5
+ *
+ * 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  Pugin
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+define("FACEBOOK_SERVICE", 2);
+
+/**
+ * Main class for Facebook plugin
+ *
+ * @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 AGPL 3.0
+ * @link      http://status.net/
+ */
+class FacebookBridgePlugin extends Plugin
+{
+    public $appId    = null; // Facebook application ID
+    public $secret   = null; // Facebook application secret
+    public $facebook = null; // Facebook application instance
+    public $dir      = null; // Facebook SSO plugin dir
+
+    /**
+     * Initializer for this plugin
+     *
+     * Gets an instance of the Facebook API client object
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+    function initialize()
+    {
+        $this->facebook = Facebookclient::getFacebook(
+            $this->appId,
+            $this->secret
+        );
+
+        return true;
+    }
+
+    /**
+     * Load related modules when needed
+     *
+     * @param string $cls Name of the class to be loaded
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+    function onAutoload($cls)
+    {
+
+        $dir = dirname(__FILE__);
+
+        //common_debug("class = " . $cls);
+
+        switch ($cls)
+        {
+        case 'Facebook': // Facebook PHP SDK
+            include_once $dir . '/extlib/facebook.php';
+            return false;
+        case 'FacebookloginAction':
+        case 'FacebookfinishloginAction':
+        case 'FacebookadminpanelAction':
+        case 'FacebooksettingsAction':
+        case 'FacebookdeauthorizeAction':
+            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            return false;
+        case 'Facebookclient':
+        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?
+     */
+    function needsScripts($action)
+    {
+        static $needy = array(
+            'FacebookloginAction',
+            'FacebookfinishloginAction',
+            'FacebookadminpanelAction',
+            'FacebooksettingsAction'
+        );
+
+        if (in_array(get_class($action), $needy)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Map URLs to actions
+     *
+     * @param Net_URL_Mapper $m path-to-action mapper
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     */
+    function onRouterInitialized($m)
+    {
+        // Always add the admin panel route
+        $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
+
+        // Only add these routes if an application has been setup on
+        // Facebook for the plugin to use.
+        if ($this->hasApplication()) {
+
+            $m->connect(
+                'main/facebooklogin',
+                array('action' => 'facebooklogin')
+            );
+            $m->connect(
+                'main/facebookfinishlogin',
+                array('action' => 'facebookfinishlogin')
+            );
+            $m->connect(
+                'settings/facebook',
+                array('action' => 'facebooksettings')
+            );
+            $m->connect(
+                'facebook/deauthorize',
+                array('action' => 'facebookdeauthorize')
+            );
+
+        }
+
+        return true;
+    }
+
+    /*
+     * Add a login tab for Facebook, but only if there's a Facebook
+     * application defined for the plugin to use.
+     *
+     * @param Action &action the current action
+     *
+     * @return void
+     */
+    function onEndLoginGroupNav(&$action)
+    {
+        $action_name = $action->trimmed('action');
+
+        if ($this->hasApplication()) {
+
+            $action->menuItem(
+                common_local_url('facebooklogin'),
+                _m('MENU', 'Facebook'),
+                // TRANS: Tooltip for menu item "Facebook".
+                _m('Login or register using Facebook'),
+               'facebooklogin' === $action_name
+            );
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a Facebook tab to the admin panels
+     *
+     * @param Widget $nav Admin panel nav
+     *
+     * @return boolean hook value
+     */
+    function onEndAdminPanelNav($nav)
+    {
+        if (AdminPanelAction::canAdmin('facebook')) {
+
+            $action_name = $nav->action->trimmed('action');
+
+            $nav->out->menuItem(
+                common_local_url('facebookadminpanel'),
+                // TRANS: Menu item.
+                _m('MENU','Facebook'),
+                // TRANS: Tooltip for menu item "Facebook".
+                _m('Facebook integration configuration'),
+                $action_name == 'facebookadminpanel',
+                'nav_facebook_admin_panel'
+            );
+        }
+
+        return true;
+    }
+
+    /*
+     * Add a tab for user-level Facebook settings
+     *
+     * @param Action &action the current action
+     *
+     * @return void
+     */
+    function onEndConnectSettingsNav(&$action)
+    {
+        if ($this->hasApplication()) {
+            $action_name = $action->trimmed('action');
+
+            $action->menuItem(
+                common_local_url('facebooksettings'),
+                // TRANS: Menu item tab.
+                _m('MENU','Facebook'),
+                // TRANS: Tooltip for menu item "Facebook".
+                _m('Facebook settings'),
+                $action_name === 'facebooksettings'
+            );
+        }
+
+        return true;
+    }
+
+    /*
+     * Is there a Facebook application for the plugin to use?
+     *
+     * Checks to see if a Facebook application ID and secret
+     * have been configured and a valid Facebook API client
+     * object exists.
+     *
+     */
+    function hasApplication()
+    {
+        if (!empty($this->facebook)) {
+
+            $appId  = $this->facebook->getAppId();
+            $secret = $this->facebook->getApiSecret();
+
+            if (!empty($appId) && !empty($secret)) {
+                return true;
+            }
+
+        }
+
+        return false;
+    }
+
+    /*
+     * Output a Facebook div for the Facebook JavaSsript SDK to use
+     *
+     * @param Action $action the current action
+     *
+     */
+    function onStartShowHeader($action)
+    {
+        // output <div id="fb-root"></div> as close to <body> as possible
+        $action->element('div', array('id' => 'fb-root'));
+        return true;
+    }
+
+    /*
+     * Load the Facebook JavaScript SDK on pages that need them.
+     *
+     * @param Action $action the current action
+     *
+     */
+    function onEndShowScripts($action)
+    {
+        if ($this->needsScripts($action)) {
+
+            $action->script('https://connect.facebook.net/en_US/all.js');
+
+            $script = <<<ENDOFSCRIPT
+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)
+        }
+    }, {perms:'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'});
+});
+ENDOFSCRIPT;
+
+            $action->inlineScript(
+                sprintf($script,
+                    json_encode($this->facebook->getAppId()),
+                    json_encode($this->facebook->getSession()),
+                    common_local_url('facebookfinishlogin')
+                )
+            );
+        }
+    }
+
+    /*
+     * Log the user out of Facebook, per the Facebook authentication guide
+     *
+     * @param Action action the current action
+     */
+    function onEndLogout($action)
+    {
+        if ($this->hasApplication()) {
+            $session = $this->facebook->getSession();
+            $fbuser  = null;
+            $fbuid   = null;
+
+            if ($session) {
+                try {
+                    $fbuid  = $this->facebook->getUser();
+                    $fbuser = $this->facebook->api('/me');
+                 } catch (FacebookApiException $e) {
+                     common_log(LOG_ERROR, $e, __FILE__);
+                 }
+            }
+
+            if (!empty($fbuser)) {
+
+                $logoutUrl = $this->facebook->getLogoutUrl(
+                    array('next' => common_local_url('public'))
+                );
+
+                common_log(
+                    LOG_INFO,
+                    sprintf(
+                        "Logging user out of Facebook (fbuid = %s)",
+                        $fbuid
+                    ),
+                    __FILE__
+                );
+                common_debug("LOGOUT URL = $logoutUrl");
+                common_redirect($logoutUrl, 303);
+            }
+
+        }
+    }
+
+    /*
+     * Add fbml namespace to our HTML, so Facebook's JavaScript SDK can parse
+     * and render XFBML tags
+     *
+     * @param Action    $action   the current action
+     * @param array     $attrs    array of attributes for the HTML tag
+     *
+     * @return nothing
+     */
+    function onStartHtmlElement($action, $attrs) {
+
+        if ($this->needsScripts($action)) {
+            $attrs = array_merge(
+                $attrs,
+                array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
+            );
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a Facebook queue item for each notice
+     *
+     * @param Notice $notice      the notice
+     * @param array  &$transports the list of transports (queues)
+     *
+     * @return boolean hook return
+     */
+    function onStartEnqueueNotice($notice, &$transports)
+    {
+        if (self::hasApplication() && $notice->isLocal()) {
+            array_push($transports, 'facebook');
+        }
+        return true;
+    }
+
+    /**
+     * Register Facebook notice queue handler
+     *
+     * @param QueueManager $manager
+     *
+     * @return boolean hook return
+     */
+    function onEndInitializeQueueManager($manager)
+    {
+        if (self::hasApplication()) {
+            $manager->connect('facebook', 'FacebookQueueHandler');
+        }
+        return true;
+    }
+
+    /*
+     * Use SSL for Facebook stuff
+     *
+     * @param string $action name
+     * @param boolean $ssl outval to force SSL
+     * @return mixed hook return value
+     */
+    function onSensitiveAction($action, &$ssl)
+    {
+        $sensitive = array(
+            'facebookadminpanel',
+            'facebooksettings',
+            'facebooklogin',
+            'facebookfinishlogin'
+        );
+
+        if (in_array($action, $sensitive)) {
+            $ssl = true;
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 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
+     *
+     * @param array &$versions    plugin version descriptions
+     */
+    function onPluginVersion(&$versions)
+    {
+        $versions[] = array(
+            'name' => 'Facebook Single-Sign-On',
+            'version' => STATUSNET_VERSION,
+            'author' => 'Craig Andrews, Zach Copley',
+            'homepage' => 'http://status.net/wiki/Plugin:FacebookBridge',
+            'rawdescription' =>
+            _m('A plugin for integrating StatusNet with Facebook.')
+        );
+
+        return true;
+    }
+}
diff --git a/plugins/FacebookSSO/FacebookSSOPlugin.php b/plugins/FacebookSSO/FacebookSSOPlugin.php
deleted file mode 100644 (file)
index 19d6121..0000000
+++ /dev/null
@@ -1,457 +0,0 @@
-<?php
-/**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2010, StatusNet, Inc.
- *
- * A plugin for integrating Facebook with StatusNet. Includes single-sign-on
- * and publishing notices to Facebook using Facebook's Graph API.
- *
- * PHP version 5
- *
- * 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  Pugin
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2010 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-define("FACEBOOK_SERVICE", 2);
-
-/**
- * Main class for Facebook plugin
- *
- * @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 AGPL 3.0
- * @link      http://status.net/
- */
-class FacebookSSOPlugin 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
-
-    /**
-     * Initializer for this plugin
-     *
-     * Gets an instance of the Facebook API client object
-     *
-     * @return boolean hook value; true means continue processing, false means stop.
-     */
-    function initialize()
-    {
-        $this->facebook = Facebookclient::getFacebook(
-            $this->appId,
-            $this->apikey,
-            $this->secret
-        );
-
-        return true;
-    }
-
-    /**
-     * Load related modules when needed
-     *
-     * @param string $cls Name of the class to be loaded
-     *
-     * @return boolean hook value; true means continue processing, false means stop.
-     */
-    function onAutoload($cls)
-    {
-
-        $dir = dirname(__FILE__);
-
-        //common_debug("class = " . $cls);
-
-        switch ($cls)
-        {
-        case 'Facebook': // Facebook PHP SDK
-            include_once $dir . '/extlib/facebook.php';
-            return false;
-        case 'FacebookloginAction':
-        case 'FacebookfinishloginAction':
-        case 'FacebookadminpanelAction':
-        case 'FacebooksettingsAction':
-        case 'FacebookdeauthorizeAction':
-            include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
-            return false;
-        case 'Facebookclient':
-        case 'FacebookQueueHandler':
-            include_once $dir . '/lib/' . strtolower($cls) . '.php';
-            return false;
-        default:
-            return true;
-        }
-
-    }
-
-    /*
-     * Does this $action need the Facebook JavaScripts?
-     */
-    function needsScripts($action)
-    {
-        static $needy = array(
-            'FacebookloginAction',
-            'FacebookfinishloginAction',
-            'FacebookadminpanelAction',
-            'FacebooksettingsAction'
-        );
-
-        if (in_array(get_class($action), $needy)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Map URLs to actions
-     *
-     * @param Net_URL_Mapper $m path-to-action mapper
-     *
-     * @return boolean hook value; true means continue processing, false means stop.
-     */
-    function onRouterInitialized($m)
-    {
-        // Always add the admin panel route
-        $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
-
-        // Only add these routes if an application has been setup on
-        // Facebook for the plugin to use.
-        if ($this->hasApplication()) {
-
-            $m->connect(
-                'main/facebooklogin',
-                array('action' => 'facebooklogin')
-            );
-            $m->connect(
-                'main/facebookfinishlogin',
-                array('action' => 'facebookfinishlogin')
-            );
-            $m->connect(
-                'settings/facebook',
-                array('action' => 'facebooksettings')
-            );
-            $m->connect(
-                'facebook/deauthorize',
-                array('action' => 'facebookdeauthorize')
-            );
-
-        }
-
-        return true;
-    }
-
-    /*
-     * Add a login tab for Facebook, but only if there's a Facebook
-     * application defined for the plugin to use.
-     *
-     * @param Action &action the current action
-     *
-     * @return void
-     */
-    function onEndLoginGroupNav(&$action)
-    {
-        $action_name = $action->trimmed('action');
-
-        if ($this->hasApplication()) {
-
-            $action->menuItem(
-                common_local_url('facebooklogin'),
-                _m('MENU', 'Facebook'),
-                // TRANS: Tooltip for menu item "Facebook".
-                _m('Login or register using Facebook'),
-               'facebooklogin' === $action_name
-            );
-        }
-
-        return true;
-    }
-
-    /**
-     * Add a Facebook tab to the admin panels
-     *
-     * @param Widget $nav Admin panel nav
-     *
-     * @return boolean hook value
-     */
-    function onEndAdminPanelNav($nav)
-    {
-        if (AdminPanelAction::canAdmin('facebook')) {
-
-            $action_name = $nav->action->trimmed('action');
-
-            $nav->out->menuItem(
-                common_local_url('facebookadminpanel'),
-                // TRANS: Menu item.
-                _m('MENU','Facebook'),
-                // TRANS: Tooltip for menu item "Facebook".
-                _m('Facebook integration configuration'),
-                $action_name == 'facebookadminpanel',
-                'nav_facebook_admin_panel'
-            );
-        }
-
-        return true;
-    }
-
-    /*
-     * Add a tab for user-level Facebook settings
-     *
-     * @param Action &action the current action
-     *
-     * @return void
-     */
-    function onEndConnectSettingsNav(&$action)
-    {
-        if ($this->hasApplication()) {
-            $action_name = $action->trimmed('action');
-
-            $action->menuItem(
-                common_local_url('facebooksettings'),
-                // TRANS: Menu item tab.
-                _m('MENU','Facebook'),
-                // TRANS: Tooltip for menu item "Facebook".
-                _m('Facebook settings'),
-                $action_name === 'facebooksettings'
-            );
-        }
-
-        return true;
-    }
-
-    /*
-     * Is there a Facebook application for the plugin to use?
-     *
-     * Checks to see if a Facebook application ID and secret
-     * have been configured and a valid Facebook API client
-     * object exists.
-     *
-     */
-    function hasApplication()
-    {
-        if (!empty($this->facebook)) {
-
-            $appId  = $this->facebook->getAppId();
-            $secret = $this->facebook->getApiSecret();
-
-            if (!empty($appId) && !empty($secret)) {
-                return true;
-            }
-
-        }
-
-        return false;
-    }
-
-    /*
-     * Output a Facebook div for the Facebook JavaSsript SDK to use
-     *
-     * @param Action $action the current action
-     *
-     */
-    function onStartShowHeader($action)
-    {
-        // output <div id="fb-root"></div> as close to <body> as possible
-        $action->element('div', array('id' => 'fb-root'));
-        return true;
-    }
-
-    /*
-     * Load the Facebook JavaScript SDK on pages that need them.
-     *
-     * @param Action $action the current action
-     *
-     */
-    function onEndShowScripts($action)
-    {
-        if ($this->needsScripts($action)) {
-
-            $action->script('https://connect.facebook.net/en_US/all.js');
-
-            $script = <<<ENDOFSCRIPT
-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)
-        }
-    }, {perms:'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'});
-});
-ENDOFSCRIPT;
-
-            $action->inlineScript(
-                sprintf($script,
-                    json_encode($this->facebook->getAppId()),
-                    json_encode($this->facebook->getSession()),
-                    common_local_url('facebookfinishlogin')
-                )
-            );
-        }
-    }
-
-    /*
-     * Log the user out of Facebook, per the Facebook authentication guide
-     *
-     * @param Action action the current action
-     */
-    function onEndLogout($action)
-    {
-        if ($this->hasApplication()) {
-            $session = $this->facebook->getSession();
-            $fbuser  = null;
-            $fbuid   = null;
-
-            if ($session) {
-                try {
-                    $fbuid  = $this->facebook->getUser();
-                    $fbuser = $this->facebook->api('/me');
-                 } catch (FacebookApiException $e) {
-                     common_log(LOG_ERROR, $e, __FILE__);
-                 }
-            }
-
-            if (!empty($fbuser)) {
-
-                $logoutUrl = $this->facebook->getLogoutUrl(
-                    array('next' => common_local_url('public'))
-                );
-
-                common_log(
-                    LOG_INFO,
-                    sprintf(
-                        "Logging user out of Facebook (fbuid = %s)",
-                        $fbuid
-                    ),
-                    __FILE__
-                );
-                common_debug("LOGOUT URL = $logoutUrl");
-                common_redirect($logoutUrl, 303);
-            }
-
-        }
-    }
-
-    /*
-     * Add fbml namespace to our HTML, so Facebook's JavaScript SDK can parse
-     * and render XFBML tags
-     *
-     * @param Action    $action   the current action
-     * @param array     $attrs    array of attributes for the HTML tag
-     *
-     * @return nothing
-     */
-    function onStartHtmlElement($action, $attrs) {
-
-        if ($this->needsScripts($action)) {
-            $attrs = array_merge(
-                $attrs,
-                array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
-            );
-        }
-
-        return true;
-    }
-
-    /**
-     * Add a Facebook queue item for each notice
-     *
-     * @param Notice $notice      the notice
-     * @param array  &$transports the list of transports (queues)
-     *
-     * @return boolean hook return
-     */
-    function onStartEnqueueNotice($notice, &$transports)
-    {
-        if (self::hasApplication() && $notice->isLocal()) {
-            array_push($transports, 'facebook');
-        }
-        return true;
-    }
-
-    /**
-     * Register Facebook notice queue handler
-     *
-     * @param QueueManager $manager
-     *
-     * @return boolean hook return
-     */
-    function onEndInitializeQueueManager($manager)
-    {
-        if (self::hasApplication()) {
-            $manager->connect('facebook', 'FacebookQueueHandler');
-        }
-        return true;
-    }
-
-    /*
-     * Use SSL for Facebook stuff
-     *
-     * @param string $action name
-     * @param boolean $ssl outval to force SSL
-     * @return mixed hook return value
-     */
-    function onSensitiveAction($action, &$ssl)
-    {
-        $sensitive = array(
-            'facebookadminpanel',
-            'facebooksettings',
-            'facebooklogin',
-            'facebookfinishlogin'
-        );
-
-        if (in_array($action, $sensitive)) {
-            $ssl = true;
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    /*
-     * Add version info for this plugin
-     *
-     * @param array &$versions    plugin version descriptions
-     */
-    function onPluginVersion(&$versions)
-    {
-        $versions[] = array(
-            'name' => 'Facebook Single-Sign-On',
-            'version' => STATUSNET_VERSION,
-            'author' => 'Craig Andrews, Zach Copley',
-            'homepage' => 'http://status.net/wiki/Plugin:FacebookSSO',
-            'rawdescription' =>
-            _m('A plugin for integrating StatusNet with Facebook.')
-        );
-
-        return true;
-    }
-}
index fb4afa13bc53244f4257a8637ecef81a0fa60d54..cb816fc54a920ff118354b1e240a7aae7d22d8dd 100644 (file)
@@ -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);
             }
         }
     }
index e61f3515479b70b556b56ac4004e60b1d285ceba..2174c5ad4a020fea2ba508885195791f1d073c93 100644 (file)
@@ -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;
+     }
+
 }
index 08c237fe6e190eadf71a35cad3fb425fbb4a8e38..9a230b72410092661f411f36de33706588453e4a 100644 (file)
@@ -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 (file)
index 0000000..a6a8030
--- /dev/null
@@ -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;
+    }
+}
index cf00b55e3a5714fd0aefb566ee5e0e99754d4966..33edf5c6b125e7f2b9b78667826b9de7b354d1a4 100644 (file)
@@ -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__
+                );
+            }
+        }
+    }
+
 }