]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Pluginized Twitter settings stuff
authorZach Copley <zach@controlyourself.ca>
Tue, 25 Aug 2009 21:52:25 +0000 (14:52 -0700)
committerZach Copley <zach@controlyourself.ca>
Tue, 25 Aug 2009 21:52:25 +0000 (14:52 -0700)
actions/twitterauthorization.php [deleted file]
actions/twittersettings.php [deleted file]
lib/connectsettingsaction.php
lib/router.php
lib/twitteroauthclient.php [deleted file]
plugins/TwitterBridge/TwitterBridgePlugin.php [new file with mode: 0644]
plugins/TwitterBridge/twitterauthorization.php [new file with mode: 0644]
plugins/TwitterBridge/twitteroauthclient.php [new file with mode: 0644]
plugins/TwitterBridge/twittersettings.php [new file with mode: 0644]

diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php
deleted file mode 100644 (file)
index b04f353..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-<?php
-/**
- * Laconica, the distributed open-source microblogging tool
- *
- * Class for doing OAuth authentication against Twitter
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Twitter
- * @package   Laconica
- * @author    Zach Copely <zach@controlyourself.ca>
- * @copyright 2009 Control Yourself, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
-    exit(1);
-}
-
-/**
- * Class for doing OAuth authentication against Twitter
- *
- * Peforms the OAuth "dance" between Laconica and Twitter -- requests a token,
- * authorizes it, and exchanges it for an access token.  It also creates a link
- * (Foreign_link) between the Laconica user and Twitter user and stores the
- * access token and secret in the link.
- *
- * @category Twitter
- * @package  Laconica
- * @author   Zach Copley <zach@controlyourself.ca>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://laconi.ca/
- *
- */
-class TwitterauthorizationAction extends Action
-{
-    /**
-     * Initialize class members. Looks for 'oauth_token' parameter.
-     *
-     * @param array $args misc. arguments
-     *
-     * @return boolean true
-     */
-    function prepare($args)
-    {
-        parent::prepare($args);
-
-        $this->oauth_token = $this->arg('oauth_token');
-
-        return true;
-    }
-
-    /**
-     * Handler method
-     *
-     * @param array $args is ignored since it's now passed in in prepare()
-     *
-     * @return nothing
-     */
-    function handle($args)
-    {
-        parent::handle($args);
-
-        if (!common_logged_in()) {
-            $this->clientError(_('Not logged in.'), 403);
-        }
-
-        $user  = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        // If there's already a foreign link record, it means we already
-        // have an access token, and this is unecessary. So go back.
-
-        if (isset($flink)) {
-            common_redirect(common_local_url('twittersettings'));
-        }
-
-        // $this->oauth_token is only populated once Twitter authorizes our
-        // request token. If it's empty we're at the beginning of the auth
-        // process
-
-        if (empty($this->oauth_token)) {
-            $this->authorizeRequestToken();
-        } else {
-            $this->saveAccessToken();
-        }
-    }
-
-    /**
-     * Asks Twitter for a request token, and then redirects to Twitter
-     * to authorize it.
-     *
-     * @return nothing
-     */
-    function authorizeRequestToken()
-    {
-        try {
-
-            // Get a new request token and authorize it
-
-            $client  = new TwitterOAuthClient();
-            $req_tok =
-              $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
-
-            // Sock the request token away in the session temporarily
-
-            $_SESSION['twitter_request_token']        = $req_tok->key;
-            $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
-
-            $auth_link = $client->getAuthorizeLink($req_tok);
-
-        } catch (TwitterOAuthClientException $e) {
-            $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
-                           $e->getCode(), $e->getMessage());
-            $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        common_redirect($auth_link);
-    }
-
-    /**
-     * Called when Twitter returns an authorized request token. Exchanges
-     * it for an access token and stores it.
-     *
-     * @return nothing
-     */
-    function saveAccessToken()
-    {
-
-        // Check to make sure Twitter returned the same request
-        // token we sent them
-
-        if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
-            $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        try {
-
-            $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
-                $_SESSION['twitter_request_token_secret']);
-
-            // Exchange the request token for an access token
-
-            $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
-
-            // Test the access token and get the user's Twitter info
-
-            $client       = new TwitterOAuthClient($atok->key, $atok->secret);
-            $twitter_user = $client->verifyCredentials();
-
-        } catch (OAuthClientException $e) {
-            $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
-                           $e->getCode(), $e->getMessage());
-            $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        // Save the access token and Twitter user info
-
-        $this->saveForeignLink($atok, $twitter_user);
-
-        // Clean up the the mess we made in the session
-
-        unset($_SESSION['twitter_request_token']);
-        unset($_SESSION['twitter_request_token_secret']);
-
-        common_redirect(common_local_url('twittersettings'));
-    }
-
-    /**
-     * Saves a Foreign_link between Twitter user and local user,
-     * which includes the access token and secret.
-     *
-     * @param OAuthToken $access_token the access token to save
-     * @param mixed      $twitter_user twitter API user object
-     *
-     * @return nothing
-     */
-    function saveForeignLink($access_token, $twitter_user)
-    {
-        $user = common_current_user();
-
-        $flink = new Foreign_link();
-
-        $flink->user_id     = $user->id;
-        $flink->foreign_id  = $twitter_user->id;
-        $flink->service     = TWITTER_SERVICE;
-
-        $creds = TwitterOAuthClient::packToken($access_token);
-
-        $flink->credentials = $creds;
-        $flink->created     = common_sql_now();
-
-        // Defaults: noticesync on, everything else off
-
-        $flink->set_flags(true, false, false, false);
-
-        $flink_id = $flink->insert();
-
-        if (empty($flink_id)) {
-            common_log_db_error($flink, 'INSERT', __FILE__);
-                $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        save_twitter_user($twitter_user->id, $twitter_user->screen_name);
-    }
-
-}
-
diff --git a/actions/twittersettings.php b/actions/twittersettings.php
deleted file mode 100644 (file)
index 0859ab9..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-<?php
-/**
- * Laconica, the distributed open-source microblogging tool
- *
- * Settings for Twitter integration
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Settings
- * @package   Laconica
- * @author    Evan Prodromou <evan@controlyourself.ca>
- * @copyright 2008-2009 Control Yourself, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/connectsettingsaction.php';
-require_once INSTALLDIR.'/lib/twitter.php';
-
-/**
- * Settings for Twitter integration
- *
- * @category Settings
- * @package  Laconica
- * @author   Evan Prodromou <evan@controlyourself.ca>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://laconi.ca/
- *
- * @see      SettingsAction
- */
-
-class TwittersettingsAction extends ConnectSettingsAction
-{
-    /**
-     * Title of the page
-     *
-     * @return string Title of the page
-     */
-
-    function title()
-    {
-        return _('Twitter settings');
-    }
-
-    /**
-     * Instructions for use
-     *
-     * @return instructions for use
-     */
-
-    function getInstructions()
-    {
-        return _('Connect your Twitter account to share your updates ' .
-                 'with your Twitter friends and vice-versa.');
-    }
-
-    /**
-     * Content area of the page
-     *
-     * Shows a form for associating a Twitter account with this
-     * Laconica account. Also lets the user set preferences.
-     *
-     * @return void
-     */
-
-    function showContent()
-    {
-        if (!common_config('twitter', 'enabled')) {
-            $this->element('div', array('class' => 'error'),
-                           _('Twitter is not available.'));
-            return;
-        }
-
-        $user = common_current_user();
-
-        $profile = $user->getProfile();
-
-        $fuser = null;
-
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        if (!empty($flink)) {
-            $fuser = $flink->getForeignUser();
-        }
-
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'form_settings_twitter',
-                                          'class' => 'form_settings',
-                                          'action' =>
-                                          common_local_url('twittersettings')));
-
-        $this->hidden('token', common_session_token());
-
-        $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
-
-        if (empty($fuser)) {
-            $this->elementStart('ul', 'form_data');
-            $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
-            $this->element('a', array('href' => common_local_url('twitterauthorization')),
-                           'Connect my Twitter account');
-            $this->elementEnd('li');
-            $this->elementEnd('ul');
-
-            $this->elementEnd('fieldset');
-        } else {
-            $this->element('legend', null, _('Twitter account'));
-            $this->elementStart('p', array('id' => 'form_confirmed'));
-            $this->element('a', array('href' => $fuser->uri), $fuser->nickname);
-            $this->elementEnd('p');
-            $this->element('p', 'form_note',
-                           _('Connected Twitter account'));
-
-            $this->submit('remove', _('Remove'));
-
-            $this->elementEnd('fieldset');
-
-            $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
-
-            $this->element('legend', null, _('Preferences'));
-            $this->elementStart('ul', 'form_data');
-            $this->elementStart('li');
-            $this->checkbox('noticesend',
-                            _('Automatically send my notices to Twitter.'),
-                            ($flink) ?
-                            ($flink->noticesync & FOREIGN_NOTICE_SEND) :
-                            true);
-            $this->elementEnd('li');
-            $this->elementStart('li');
-            $this->checkbox('replysync',
-                            _('Send local "@" replies to Twitter.'),
-                            ($flink) ?
-                            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
-                            true);
-            $this->elementEnd('li');
-            $this->elementStart('li');
-            $this->checkbox('friendsync',
-                            _('Subscribe to my Twitter friends here.'),
-                            ($flink) ?
-                            ($flink->friendsync & FOREIGN_FRIEND_RECV) :
-                            false);
-            $this->elementEnd('li');
-
-            if (common_config('twitterbridge','enabled')) {
-                $this->elementStart('li');
-                $this->checkbox('noticerecv',
-                                _('Import my Friends Timeline.'),
-                                ($flink) ?
-                                ($flink->noticesync & FOREIGN_NOTICE_RECV) :
-                                false);
-                $this->elementEnd('li');
-
-                // preserve setting even if bidrection bridge toggled off
-
-                if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
-                    $this->hidden('noticerecv', true, 'noticerecv');
-                }
-            }
-
-            $this->elementEnd('ul');
-
-            if ($flink) {
-                $this->submit('save', _('Save'));
-            } else {
-                $this->submit('add', _('Add'));
-            }
-
-            $this->elementEnd('fieldset');
-        }
-
-        $this->elementEnd('form');
-    }
-
-    /**
-     * Handle posts to this form
-     *
-     * Based on the button that was pressed, muxes out to other functions
-     * to do the actual task requested.
-     *
-     * All sub-functions reload the form with a message -- success or failure.
-     *
-     * @return void
-     */
-
-    function handlePost()
-    {
-        // CSRF protection
-        $token = $this->trimmed('token');
-        if (!$token || $token != common_session_token()) {
-            $this->showForm(_('There was a problem with your session token. '.
-                              'Try again, please.'));
-            return;
-        }
-
-        if ($this->arg('save')) {
-            $this->savePreferences();
-        } else if ($this->arg('remove')) {
-            $this->removeTwitterAccount();
-        } else {
-            $this->showForm(_('Unexpected form submission.'));
-        }
-    }
-
-    /**
-     * Disassociate an existing Twitter account from this account
-     *
-     * @return void
-     */
-
-    function removeTwitterAccount()
-    {
-        $user = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        $result = $flink->delete();
-
-        if (empty($result)) {
-            common_log_db_error($flink, 'DELETE', __FILE__);
-            $this->serverError(_('Couldn\'t remove Twitter user.'));
-            return;
-        }
-
-        $this->showForm(_('Twitter account removed.'), true);
-    }
-
-    /**
-     * Save user's Twitter-bridging preferences
-     *
-     * @return void
-     */
-
-    function savePreferences()
-    {
-        $noticesend = $this->boolean('noticesend');
-        $noticerecv = $this->boolean('noticerecv');
-        $friendsync = $this->boolean('friendsync');
-        $replysync  = $this->boolean('replysync');
-
-        $user = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        if (empty($flink)) {
-            common_log_db_error($flink, 'SELECT', __FILE__);
-            $this->showForm(_('Couldn\'t save Twitter preferences.'));
-            return;
-        }
-
-        $original = clone($flink);
-        $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
-        $result = $flink->update($original);
-
-        if ($result === false) {
-            common_log_db_error($flink, 'UPDATE', __FILE__);
-            $this->showForm(_('Couldn\'t save Twitter preferences.'));
-            return;
-        }
-
-        $this->showForm(_('Twitter preferences saved.'), true);
-    }
-
-}
index 02c468a359b7f166ce012e85ef9db5d0b9f17959..7902931e03eac6e3f8e4108932c1b55aca755e18 100644 (file)
@@ -98,34 +98,37 @@ class ConnectSettingsNav extends Widget
 
     function show()
     {
-        # action => array('prompt', 'title')
-        $menu = array();
-        if (common_config('xmpp', 'enabled')) {
-            $menu['imsettings'] =
-              array(_('IM'),
-                    _('Updates by instant messenger (IM)'));
-        }
-        if (common_config('sms', 'enabled')) {
-            $menu['smssettings'] =
-              array(_('SMS'),
-                    _('Updates by SMS'));
-        }
-        if (common_config('twitter', 'enabled')) {
-            $menu['twittersettings'] =
-              array(_('Twitter'),
-                    _('Twitter integration options'));
-        }
-
         $action_name = $this->action->trimmed('action');
         $this->action->elementStart('ul', array('class' => 'nav'));
 
-        foreach ($menu as $menuaction => $menudesc) {
-            $this->action->menuItem(common_local_url($menuaction),
-                                   $menudesc[0],
-                                   $menudesc[1],
-                                   $action_name === $menuaction);
+        if (Event::handle('StartConnectSettingsNav', array(&$this->action))) {
+
+            # action => array('prompt', 'title')
+            $menu = array();
+            if (common_config('xmpp', 'enabled')) {
+                $menu['imsettings'] =
+                  array(_('IM'),
+                        _('Updates by instant messenger (IM)'));
+            }
+            if (common_config('sms', 'enabled')) {
+                $menu['smssettings'] =
+                  array(_('SMS'),
+                        _('Updates by SMS'));
+            }
+
+            foreach ($menu as $menuaction => $menudesc) {
+                $this->action->menuItem(common_local_url($menuaction),
+                        $menudesc[0],
+                        $menudesc[1],
+                        $action_name === $menuaction);
+            }
+
+            Event::handle('EndConnectSettingsNav', array(&$this->action));
         }
 
         $this->action->elementEnd('ul');
     }
+
 }
+
+
index 08bc0566dd3ed5799553799cbaaa94b696ba194b..5bffdb16bf3a6ba2e2976e492fd4983c3df8b651 100644 (file)
@@ -86,10 +86,6 @@ class Router
 
         $m->connect('doc/:title', array('action' => 'doc'));
 
-        // Twitter
-
-        $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
-
         // facebook
 
         $m->connect('facebook', array('action' => 'facebookhome'));
@@ -136,7 +132,7 @@ class Router
         // settings
 
         foreach (array('profile', 'avatar', 'password', 'im',
-                       'email', 'sms', 'twitter', 'userdesign', 'other') as $s) {
+                       'email', 'sms', 'userdesign', 'other') as $s) {
             $m->connect('settings/'.$s, array('action' => $s.'settings'));
         }
 
diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php
deleted file mode 100644 (file)
index b7dc4a8..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-<?php
-/**
- * Laconica, the distributed open-source microblogging tool
- *
- * Class for doing OAuth calls against Twitter
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Integration
- * @package   Laconica
- * @author    Zach Copley <zach@controlyourself.ca>
- * @copyright 2008 Control Yourself, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
-    exit(1);
-}
-
-/**
- * Class for talking to the Twitter API with OAuth.
- *
- * @category Integration
- * @package  Laconica
- * @author   Zach Copley <zach@controlyourself.ca>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://laconi.ca/
- *
- */
-class TwitterOAuthClient extends OAuthClient
-{
-    public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
-    public static $authorizeURL    = 'https://twitter.com/oauth/authorize';
-    public static $accessTokenURL  = 'https://twitter.com/oauth/access_token';
-
-    /**
-     * Constructor
-     *
-     * @param string $oauth_token        the user's token
-     * @param string $oauth_token_secret the user's token secret
-     *
-     * @return nothing
-     */
-    function __construct($oauth_token = null, $oauth_token_secret = null)
-    {
-        $consumer_key    = common_config('twitter', 'consumer_key');
-        $consumer_secret = common_config('twitter', 'consumer_secret');
-
-        parent::__construct($consumer_key, $consumer_secret,
-                            $oauth_token, $oauth_token_secret);
-    }
-
-    // XXX: the following two functions are to support the horrible hack
-    // of using the credentils field in Foreign_link to store both
-    // the access token and token secret.  This hack should go away with
-    // 0.9, in which we can make DB changes and add a new column for the
-    // token itself.
-
-    static function packToken($token)
-    {
-        return implode(chr(0), array($token->key, $token->secret));
-    }
-
-    static function unpackToken($str)
-    {
-        $vals = explode(chr(0), $str);
-        return new OAuthToken($vals[0], $vals[1]);
-    }
-
-    /**
-     * Builds a link to Twitter's endpoint for authorizing a request token
-     *
-     * @param OAuthToken $request_token token to authorize
-     *
-     * @return the link
-     */
-    function getAuthorizeLink($request_token)
-    {
-        return parent::getAuthorizeLink(self::$authorizeURL,
-                                        $request_token,
-                                        common_local_url('twitterauthorization'));
-    }
-
-    /**
-     * Calls Twitter's /account/verify_credentials API method
-     *
-     * @return mixed the Twitter user
-     */
-    function verifyCredentials()
-    {
-        $url          = 'https://twitter.com/account/verify_credentials.json';
-        $response     = $this->oAuthGet($url);
-        $twitter_user = json_decode($response);
-        return $twitter_user;
-    }
-
-    /**
-     * Calls Twitter's /stutuses/update API method
-     *
-     * @param string $status                text of the status
-     * @param int    $in_reply_to_status_id optional id of the status it's
-     *                                      a reply to
-     *
-     * @return mixed the status
-     */
-    function statusesUpdate($status, $in_reply_to_status_id = null)
-    {
-        $url      = 'https://twitter.com/statuses/update.json';
-        $params   = array('status' => $status,
-            'in_reply_to_status_id' => $in_reply_to_status_id);
-        $response = $this->oAuthPost($url, $params);
-        $status   = json_decode($response);
-        return $status;
-    }
-
-    /**
-     * Calls Twitter's /stutuses/friends_timeline API method
-     *
-     * @param int $since_id show statuses after this id
-     * @param int $max_id   show statuses before this id
-     * @param int $cnt      number of statuses to show
-     * @param int $page     page number
-     *
-     * @return mixed an array of statuses
-     */
-    function statusesFriendsTimeline($since_id = null, $max_id = null,
-                                     $cnt = null, $page = null)
-    {
-
-        $url    = 'https://twitter.com/statuses/friends_timeline.json';
-        $params = array('since_id' => $since_id,
-                        'max_id' => $max_id,
-                        'count' => $cnt,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->oAuthGet($url);
-        $statuses = json_decode($response);
-        return $statuses;
-    }
-
-    /**
-     * Calls Twitter's /stutuses/friends API method
-     *
-     * @param int $id          id of the user whom you wish to see friends of
-     * @param int $user_id     numerical user id
-     * @param int $screen_name screen name
-     * @param int $page        page number
-     *
-     * @return mixed an array of twitter users and their latest status
-     */
-    function statusesFriends($id = null, $user_id = null, $screen_name = null,
-                             $page = null)
-    {
-        $url = "https://twitter.com/statuses/friends.json";
-
-        $params = array('id' => $id,
-                        'user_id' => $user_id,
-                        'screen_name' => $screen_name,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->oAuthGet($url);
-        $friends  = json_decode($response);
-        return $friends;
-    }
-
-    /**
-     * Calls Twitter's /stutuses/friends/ids API method
-     *
-     * @param int $id          id of the user whom you wish to see friends of
-     * @param int $user_id     numerical user id
-     * @param int $screen_name screen name
-     * @param int $page        page number
-     *
-     * @return mixed a list of ids, 100 per page
-     */
-    function friendsIds($id = null, $user_id = null, $screen_name = null,
-                         $page = null)
-    {
-        $url = "https://twitter.com/friends/ids.json";
-
-        $params = array('id' => $id,
-                        'user_id' => $user_id,
-                        'screen_name' => $screen_name,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->oAuthGet($url);
-        $ids      = json_decode($response);
-        return $ids;
-    }
-
-}
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
new file mode 100644 (file)
index 0000000..f7daa9a
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Plugin for sending and importing Twitter statuses
+ *
+ * This class allows users to link their Twitter accounts
+ *
+ * @category Plugin
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ * @link     http://twitter.com/
+ */
+
+class TwitterBridgePlugin extends Plugin
+{
+    /**
+     * Initializer for the plugin.
+     */
+
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Add Twitter-related paths to the router table
+     *
+     * Hook for RouterInitialized event.
+     *
+     * @return boolean hook return
+     */
+
+    function onRouterInitialized(&$m)
+    {
+        $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
+        $m->connect('settings/twitter', array('action' => 'twittersettings'));
+
+        return true;
+    }
+
+    function onEndConnectSettingsNav(&$action)
+    {
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('twittersettings'),
+                          _('Twitter'),
+                          _('Twitter integration options'),
+                          $action_name === 'twittersettings');
+
+        return true;
+    }
+
+    function onAutoload($cls)
+    {
+        switch ($cls)
+        {
+         case 'TwittersettingsAction':
+         case 'TwitterauthorizationAction':
+            require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+            return false;
+         case 'TwitterOAuthClient':
+            require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroAuthclient.php');
+            return false;
+         default:
+            return true;
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php
new file mode 100644 (file)
index 0000000..b04f353
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Class for doing OAuth authentication against Twitter
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Twitter
+ * @package   Laconica
+ * @author    Zach Copely <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Class for doing OAuth authentication against Twitter
+ *
+ * Peforms the OAuth "dance" between Laconica and Twitter -- requests a token,
+ * authorizes it, and exchanges it for an access token.  It also creates a link
+ * (Foreign_link) between the Laconica user and Twitter user and stores the
+ * access token and secret in the link.
+ *
+ * @category Twitter
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ */
+class TwitterauthorizationAction extends Action
+{
+    /**
+     * Initialize class members. Looks for 'oauth_token' parameter.
+     *
+     * @param array $args misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->oauth_token = $this->arg('oauth_token');
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $args is ignored since it's now passed in in prepare()
+     *
+     * @return nothing
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'), 403);
+        }
+
+        $user  = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        // If there's already a foreign link record, it means we already
+        // have an access token, and this is unecessary. So go back.
+
+        if (isset($flink)) {
+            common_redirect(common_local_url('twittersettings'));
+        }
+
+        // $this->oauth_token is only populated once Twitter authorizes our
+        // request token. If it's empty we're at the beginning of the auth
+        // process
+
+        if (empty($this->oauth_token)) {
+            $this->authorizeRequestToken();
+        } else {
+            $this->saveAccessToken();
+        }
+    }
+
+    /**
+     * Asks Twitter for a request token, and then redirects to Twitter
+     * to authorize it.
+     *
+     * @return nothing
+     */
+    function authorizeRequestToken()
+    {
+        try {
+
+            // Get a new request token and authorize it
+
+            $client  = new TwitterOAuthClient();
+            $req_tok =
+              $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
+
+            // Sock the request token away in the session temporarily
+
+            $_SESSION['twitter_request_token']        = $req_tok->key;
+            $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
+
+            $auth_link = $client->getAuthorizeLink($req_tok);
+
+        } catch (TwitterOAuthClientException $e) {
+            $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
+                           $e->getCode(), $e->getMessage());
+            $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        common_redirect($auth_link);
+    }
+
+    /**
+     * Called when Twitter returns an authorized request token. Exchanges
+     * it for an access token and stores it.
+     *
+     * @return nothing
+     */
+    function saveAccessToken()
+    {
+
+        // Check to make sure Twitter returned the same request
+        // token we sent them
+
+        if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
+            $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        try {
+
+            $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
+                $_SESSION['twitter_request_token_secret']);
+
+            // Exchange the request token for an access token
+
+            $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
+
+            // Test the access token and get the user's Twitter info
+
+            $client       = new TwitterOAuthClient($atok->key, $atok->secret);
+            $twitter_user = $client->verifyCredentials();
+
+        } catch (OAuthClientException $e) {
+            $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
+                           $e->getCode(), $e->getMessage());
+            $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        // Save the access token and Twitter user info
+
+        $this->saveForeignLink($atok, $twitter_user);
+
+        // Clean up the the mess we made in the session
+
+        unset($_SESSION['twitter_request_token']);
+        unset($_SESSION['twitter_request_token_secret']);
+
+        common_redirect(common_local_url('twittersettings'));
+    }
+
+    /**
+     * Saves a Foreign_link between Twitter user and local user,
+     * which includes the access token and secret.
+     *
+     * @param OAuthToken $access_token the access token to save
+     * @param mixed      $twitter_user twitter API user object
+     *
+     * @return nothing
+     */
+    function saveForeignLink($access_token, $twitter_user)
+    {
+        $user = common_current_user();
+
+        $flink = new Foreign_link();
+
+        $flink->user_id     = $user->id;
+        $flink->foreign_id  = $twitter_user->id;
+        $flink->service     = TWITTER_SERVICE;
+
+        $creds = TwitterOAuthClient::packToken($access_token);
+
+        $flink->credentials = $creds;
+        $flink->created     = common_sql_now();
+
+        // Defaults: noticesync on, everything else off
+
+        $flink->set_flags(true, false, false, false);
+
+        $flink_id = $flink->insert();
+
+        if (empty($flink_id)) {
+            common_log_db_error($flink, 'INSERT', __FILE__);
+                $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        save_twitter_user($twitter_user->id, $twitter_user->screen_name);
+    }
+
+}
+
diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php
new file mode 100644 (file)
index 0000000..b7dc4a8
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Class for doing OAuth calls against Twitter
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Integration
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Class for talking to the Twitter API with OAuth.
+ *
+ * @category Integration
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ */
+class TwitterOAuthClient extends OAuthClient
+{
+    public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
+    public static $authorizeURL    = 'https://twitter.com/oauth/authorize';
+    public static $accessTokenURL  = 'https://twitter.com/oauth/access_token';
+
+    /**
+     * Constructor
+     *
+     * @param string $oauth_token        the user's token
+     * @param string $oauth_token_secret the user's token secret
+     *
+     * @return nothing
+     */
+    function __construct($oauth_token = null, $oauth_token_secret = null)
+    {
+        $consumer_key    = common_config('twitter', 'consumer_key');
+        $consumer_secret = common_config('twitter', 'consumer_secret');
+
+        parent::__construct($consumer_key, $consumer_secret,
+                            $oauth_token, $oauth_token_secret);
+    }
+
+    // XXX: the following two functions are to support the horrible hack
+    // of using the credentils field in Foreign_link to store both
+    // the access token and token secret.  This hack should go away with
+    // 0.9, in which we can make DB changes and add a new column for the
+    // token itself.
+
+    static function packToken($token)
+    {
+        return implode(chr(0), array($token->key, $token->secret));
+    }
+
+    static function unpackToken($str)
+    {
+        $vals = explode(chr(0), $str);
+        return new OAuthToken($vals[0], $vals[1]);
+    }
+
+    /**
+     * Builds a link to Twitter's endpoint for authorizing a request token
+     *
+     * @param OAuthToken $request_token token to authorize
+     *
+     * @return the link
+     */
+    function getAuthorizeLink($request_token)
+    {
+        return parent::getAuthorizeLink(self::$authorizeURL,
+                                        $request_token,
+                                        common_local_url('twitterauthorization'));
+    }
+
+    /**
+     * Calls Twitter's /account/verify_credentials API method
+     *
+     * @return mixed the Twitter user
+     */
+    function verifyCredentials()
+    {
+        $url          = 'https://twitter.com/account/verify_credentials.json';
+        $response     = $this->oAuthGet($url);
+        $twitter_user = json_decode($response);
+        return $twitter_user;
+    }
+
+    /**
+     * Calls Twitter's /stutuses/update API method
+     *
+     * @param string $status                text of the status
+     * @param int    $in_reply_to_status_id optional id of the status it's
+     *                                      a reply to
+     *
+     * @return mixed the status
+     */
+    function statusesUpdate($status, $in_reply_to_status_id = null)
+    {
+        $url      = 'https://twitter.com/statuses/update.json';
+        $params   = array('status' => $status,
+            'in_reply_to_status_id' => $in_reply_to_status_id);
+        $response = $this->oAuthPost($url, $params);
+        $status   = json_decode($response);
+        return $status;
+    }
+
+    /**
+     * Calls Twitter's /stutuses/friends_timeline API method
+     *
+     * @param int $since_id show statuses after this id
+     * @param int $max_id   show statuses before this id
+     * @param int $cnt      number of statuses to show
+     * @param int $page     page number
+     *
+     * @return mixed an array of statuses
+     */
+    function statusesFriendsTimeline($since_id = null, $max_id = null,
+                                     $cnt = null, $page = null)
+    {
+
+        $url    = 'https://twitter.com/statuses/friends_timeline.json';
+        $params = array('since_id' => $since_id,
+                        'max_id' => $max_id,
+                        'count' => $cnt,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->oAuthGet($url);
+        $statuses = json_decode($response);
+        return $statuses;
+    }
+
+    /**
+     * Calls Twitter's /stutuses/friends API method
+     *
+     * @param int $id          id of the user whom you wish to see friends of
+     * @param int $user_id     numerical user id
+     * @param int $screen_name screen name
+     * @param int $page        page number
+     *
+     * @return mixed an array of twitter users and their latest status
+     */
+    function statusesFriends($id = null, $user_id = null, $screen_name = null,
+                             $page = null)
+    {
+        $url = "https://twitter.com/statuses/friends.json";
+
+        $params = array('id' => $id,
+                        'user_id' => $user_id,
+                        'screen_name' => $screen_name,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->oAuthGet($url);
+        $friends  = json_decode($response);
+        return $friends;
+    }
+
+    /**
+     * Calls Twitter's /stutuses/friends/ids API method
+     *
+     * @param int $id          id of the user whom you wish to see friends of
+     * @param int $user_id     numerical user id
+     * @param int $screen_name screen name
+     * @param int $page        page number
+     *
+     * @return mixed a list of ids, 100 per page
+     */
+    function friendsIds($id = null, $user_id = null, $screen_name = null,
+                         $page = null)
+    {
+        $url = "https://twitter.com/friends/ids.json";
+
+        $params = array('id' => $id,
+                        'user_id' => $user_id,
+                        'screen_name' => $screen_name,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->oAuthGet($url);
+        $ids      = json_decode($response);
+        return $ids;
+    }
+
+}
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
new file mode 100644 (file)
index 0000000..a3e02e1
--- /dev/null
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Settings for Twitter integration
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Settings
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/connectsettingsaction.php';
+require_once INSTALLDIR.'/lib/twitter.php';
+
+/**
+ * Settings for Twitter integration
+ *
+ * @category Settings
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ * @see      SettingsAction
+ */
+
+class TwittersettingsAction extends ConnectSettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Twitter settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Connect your Twitter account to share your updates ' .
+                 'with your Twitter friends and vice-versa.');
+    }
+
+    /**
+     * Content area of the page
+     *
+     * Shows a form for associating a Twitter account with this
+     * Laconica account. Also lets the user set preferences.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+
+        $user = common_current_user();
+
+        $profile = $user->getProfile();
+
+        $fuser = null;
+
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        if (!empty($flink)) {
+            $fuser = $flink->getForeignUser();
+        }
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_twitter',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('twittersettings')));
+
+        $this->hidden('token', common_session_token());
+
+        $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
+
+        if (empty($fuser)) {
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
+            $this->element('a', array('href' => common_local_url('twitterauthorization')),
+                           'Connect my Twitter account');
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+
+            $this->elementEnd('fieldset');
+        } else {
+            $this->element('legend', null, _('Twitter account'));
+            $this->elementStart('p', array('id' => 'form_confirmed'));
+            $this->element('a', array('href' => $fuser->uri), $fuser->nickname);
+            $this->elementEnd('p');
+            $this->element('p', 'form_note',
+                           _('Connected Twitter account'));
+
+            $this->submit('remove', _('Remove'));
+
+            $this->elementEnd('fieldset');
+
+            $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
+
+            $this->element('legend', null, _('Preferences'));
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li');
+            $this->checkbox('noticesend',
+                            _('Automatically send my notices to Twitter.'),
+                            ($flink) ?
+                            ($flink->noticesync & FOREIGN_NOTICE_SEND) :
+                            true);
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->checkbox('replysync',
+                            _('Send local "@" replies to Twitter.'),
+                            ($flink) ?
+                            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
+                            true);
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->checkbox('friendsync',
+                            _('Subscribe to my Twitter friends here.'),
+                            ($flink) ?
+                            ($flink->friendsync & FOREIGN_FRIEND_RECV) :
+                            false);
+            $this->elementEnd('li');
+
+            if (common_config('twitterbridge','enabled')) {
+                $this->elementStart('li');
+                $this->checkbox('noticerecv',
+                                _('Import my Friends Timeline.'),
+                                ($flink) ?
+                                ($flink->noticesync & FOREIGN_NOTICE_RECV) :
+                                false);
+                $this->elementEnd('li');
+
+                // preserve setting even if bidrection bridge toggled off
+
+                if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
+                    $this->hidden('noticerecv', true, 'noticerecv');
+                }
+            }
+
+            $this->elementEnd('ul');
+
+            if ($flink) {
+                $this->submit('save', _('Save'));
+            } else {
+                $this->submit('add', _('Add'));
+            }
+
+            $this->elementEnd('fieldset');
+        }
+
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Handle posts to this form
+     *
+     * Based on the button that was pressed, muxes out to other functions
+     * to do the actual task requested.
+     *
+     * All sub-functions reload the form with a message -- success or failure.
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('save')) {
+            $this->savePreferences();
+        } else if ($this->arg('remove')) {
+            $this->removeTwitterAccount();
+        } else {
+            $this->showForm(_('Unexpected form submission.'));
+        }
+    }
+
+    /**
+     * Disassociate an existing Twitter account from this account
+     *
+     * @return void
+     */
+
+    function removeTwitterAccount()
+    {
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        $result = $flink->delete();
+
+        if (empty($result)) {
+            common_log_db_error($flink, 'DELETE', __FILE__);
+            $this->serverError(_('Couldn\'t remove Twitter user.'));
+            return;
+        }
+
+        $this->showForm(_('Twitter account removed.'), true);
+    }
+
+    /**
+     * Save user's Twitter-bridging preferences
+     *
+     * @return void
+     */
+
+    function savePreferences()
+    {
+        $noticesend = $this->boolean('noticesend');
+        $noticerecv = $this->boolean('noticerecv');
+        $friendsync = $this->boolean('friendsync');
+        $replysync  = $this->boolean('replysync');
+
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        if (empty($flink)) {
+            common_log_db_error($flink, 'SELECT', __FILE__);
+            $this->showForm(_('Couldn\'t save Twitter preferences.'));
+            return;
+        }
+
+        $original = clone($flink);
+        $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
+        $result = $flink->update($original);
+
+        if ($result === false) {
+            common_log_db_error($flink, 'UPDATE', __FILE__);
+            $this->showForm(_('Couldn\'t save Twitter preferences.'));
+            return;
+        }
+
+        $this->showForm(_('Twitter preferences saved.'), true);
+    }
+
+}