summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/newnotice.php3
-rw-r--r--lib/action.php2
-rw-r--r--lib/default.php4
-rw-r--r--lib/util.php24
-rw-r--r--plugins/Facebook/FacebookPlugin.php87
-rw-r--r--plugins/Facebook/facebookadminpanel.php223
-rw-r--r--plugins/MobileProfile/MobileProfilePlugin.php2
-rw-r--r--plugins/OStatus/OStatusPlugin.php57
-rw-r--r--plugins/OStatus/actions/pushhub.php2
-rw-r--r--plugins/OStatus/classes/HubSub.php8
-rw-r--r--plugins/OStatus/classes/Magicsig.php4
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php4
-rw-r--r--plugins/OStatus/lib/discovery.php2
-rw-r--r--plugins/OStatus/lib/xrd.php21
-rw-r--r--plugins/TwitterBridge/README105
-rw-r--r--plugins/TwitterBridge/TwitterBridgePlugin.php137
-rw-r--r--plugins/TwitterBridge/twitteradminpanel.php280
-rw-r--r--plugins/TwitterBridge/twitterauthorization.php2
18 files changed, 857 insertions, 110 deletions
diff --git a/actions/newnotice.php b/actions/newnotice.php
index 78480abab..ed0fa1b2b 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -294,6 +294,9 @@ class NewnoticeAction extends Action
if ($profile) {
$content = '@' . $profile->nickname . ' ';
}
+ } else {
+ // @fixme most of these bits above aren't being passed on above
+ $inreplyto = null;
}
$notice_form = new NoticeForm($this, '', $content, null, $inreplyto);
diff --git a/lib/action.php b/lib/action.php
index a7e0eb33b..0918c6858 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -425,8 +425,6 @@ class Action extends HTMLOutputter // lawsuit
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
- } else if (common_config('twitter', 'enabled')) {
- $connect = 'twittersettings';
}
$this->elementStart('dl', array('id' => 'site_nav_global_primary'));
diff --git a/lib/default.php b/lib/default.php
index d849055c2..7b50242ae 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -177,8 +177,8 @@ $default =
array('source' => 'StatusNet', # source attribute for Twitter
'taguri' => null), # base for tag URIs
'twitter' =>
- array('enabled' => true,
- 'consumer_key' => null,
+ array('signin' => true,
+ 'consumer_key' => null,
'consumer_secret' => null),
'cache' =>
array('base' => null),
diff --git a/lib/util.php b/lib/util.php
index d12a7920d..7a170a5f5 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -809,8 +809,28 @@ function common_shorten_links($text)
function common_xml_safe_str($str)
{
- // Neutralize control codes and surrogates
- return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
+ // Replace common eol and extra whitespace input chars
+ $unWelcome = array(
+ "\t", // tab
+ "\n", // newline
+ "\r", // cr
+ "\0", // null byte eos
+ "\x0B" // vertical tab
+ );
+
+ $replacement = array(
+ ' ', // single space
+ ' ',
+ '', // nothing
+ '',
+ ' '
+ );
+
+ $str = str_replace($unWelcome, $replacement, $str);
+
+ // Neutralize any additional control codes and UTF-16 surrogates
+ // (Twitter uses '*')
+ return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
}
function common_tag_link($tag)
diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php
index 4266b886d..014d0d197 100644
--- a/plugins/Facebook/FacebookPlugin.php
+++ b/plugins/Facebook/FacebookPlugin.php
@@ -22,7 +22,7 @@
* @category Plugin
* @package StatusNet
* @author Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
+ * @copyright 2009-2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@@ -32,12 +32,12 @@ if (!defined('STATUSNET')) {
}
define("FACEBOOK_CONNECT_SERVICE", 3);
-define('FACEBOOKPLUGIN_VERSION', '0.9');
require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
/**
- * Facebook plugin to add a StatusNet Facebook application
+ * Facebook plugin to add a StatusNet Facebook canvas application
+ * and allow registration and authentication via Facebook Connect
*
* @category Plugin
* @package StatusNet
@@ -49,6 +49,36 @@ require_once INSTALLDIR . '/plugins/Facebook/facebookutil.php';
class FacebookPlugin extends Plugin
{
+ const VERSION = STATUSNET_VERSION;
+
+ /**
+ * Initializer for the plugin.
+ */
+
+ function initialize()
+ {
+ // Allow the key and secret to be passed in
+ // Control panel will override
+
+ if (isset($this->apikey)) {
+ $key = common_config('facebook', 'apikey');
+ if (empty($key)) {
+ Config::save('facebook', 'apikey', $this->apikey);
+ }
+ }
+
+ if (isset($this->secret)) {
+ $secret = common_config('facebook', 'secret');
+ if (empty($secret)) {
+ Config::save(
+ 'facebook',
+ 'secret',
+ $this->secret
+ );
+ }
+ }
+ }
+
/**
* Add Facebook app actions to the router table
*
@@ -70,6 +100,7 @@ class FacebookPlugin extends Plugin
array('action' => 'facebooksettings'));
$m->connect('facebook/app/invite.php', array('action' => 'facebookinvite'));
$m->connect('facebook/app/remove', array('action' => 'facebookremove'));
+ $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
// Facebook Connect stuff
@@ -98,6 +129,7 @@ class FacebookPlugin extends Plugin
case 'FacebookinviteAction':
case 'FacebookremoveAction':
case 'FacebooksettingsAction':
+ case 'FacebookadminpanelAction':
include_once INSTALLDIR . '/plugins/Facebook/' .
strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
@@ -123,6 +155,32 @@ class FacebookPlugin extends Plugin
}
/**
+ * 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'),
+ _m('Facebook'),
+ _m('Facebook integration configuration'),
+ $action_name == 'facebookadminpanel',
+ 'nav_facebook_admin_panel'
+ );
+ }
+
+ return true;
+ }
+
+ /**
* Override normal HTML output to force the content type to
* text/html and add in xmlns:fb
*
@@ -359,8 +417,6 @@ class FacebookPlugin extends Plugin
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
- } else if (common_config('twitter', 'enabled')) {
- $connect = 'twittersettings';
}
if (!empty($user)) {
@@ -525,15 +581,18 @@ class FacebookPlugin extends Plugin
function onPluginVersion(&$versions)
{
- $versions[] = array('name' => 'Facebook',
- 'version' => FACEBOOKPLUGIN_VERSION,
- 'author' => 'Zach Copley',
- 'homepage' => 'http://status.net/wiki/Plugin:Facebook',
- 'rawdescription' =>
- _m('The Facebook plugin allows you to integrate ' .
- 'your StatusNet instance with ' .
- '<a href="http://facebook.com/">Facebook</a> ' .
- 'and Facebook Connect.'));
+ $versions[] = array(
+ 'name' => 'Facebook',
+ 'version' => self::VERSION,
+ 'author' => 'Zach Copley',
+ 'homepage' => 'http://status.net/wiki/Plugin:Facebook',
+ 'rawdescription' => _m(
+ 'The Facebook plugin allows you to integrate ' .
+ 'your StatusNet instance with ' .
+ '<a href="http://facebook.com/">Facebook</a> ' .
+ 'and Facebook Connect.'
+ )
+ );
return true;
}
diff --git a/plugins/Facebook/facebookadminpanel.php b/plugins/Facebook/facebookadminpanel.php
new file mode 100644
index 000000000..ae1c7302f
--- /dev/null
+++ b/plugins/Facebook/facebookadminpanel.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Facebook integration administration panel
+ *
+ * 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 StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Administer global Facebook integration settings
+ *
+ * @category Admin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class FacebookadminpanelAction extends AdminPanelAction
+{
+ /**
+ * Returns the page title
+ *
+ * @return string page title
+ */
+
+ function title()
+ {
+ return _m('Facebook');
+ }
+
+ /**
+ * Instructions for using this form.
+ *
+ * @return string instructions
+ */
+
+ function getInstructions()
+ {
+ return _m('Facebook integration settings');
+ }
+
+ /**
+ * Show the Facebook admin panel form
+ *
+ * @return void
+ */
+
+ function showForm()
+ {
+ $form = new FacebookAdminPanelForm($this);
+ $form->show();
+ return;
+ }
+
+ /**
+ * Save settings from the form
+ *
+ * @return void
+ */
+
+ function saveSettings()
+ {
+ static $settings = array(
+ 'facebook' => array('apikey', 'secret'),
+ );
+
+ $values = array();
+
+ foreach ($settings as $section => $parts) {
+ foreach ($parts as $setting) {
+ $values[$section][$setting]
+ = $this->trimmed($setting);
+ }
+ }
+
+ // This throws an exception on validation errors
+
+ $this->validate($values);
+
+ // assert(all values are valid);
+
+ $config = new Config();
+
+ $config->query('BEGIN');
+
+ foreach ($settings as $section => $parts) {
+ foreach ($parts as $setting) {
+ Config::save($section, $setting, $values[$section][$setting]);
+ }
+ }
+
+ $config->query('COMMIT');
+
+ return;
+ }
+
+ function validate(&$values)
+ {
+ // Validate consumer key and secret (can't be too long)
+
+ if (mb_strlen($values['facebook']['apikey']) > 255) {
+ $this->clientError(
+ _m("Invalid Facebook API key. Max length is 255 characters.")
+ );
+ }
+
+ if (mb_strlen($values['facebook']['secret']) > 255) {
+ $this->clientError(
+ _m("Invalid Facebook API secret. Max length is 255 characters.")
+ );
+ }
+ }
+}
+
+class FacebookAdminPanelForm extends AdminForm
+{
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'facebookadminpanel';
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string class of the form
+ */
+
+ function formClass()
+ {
+ return 'form_settings';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('facebookadminpanel');
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->elementStart(
+ 'fieldset',
+ array('id' => 'settings_facebook-application')
+ );
+ $this->out->element('legend', null, _m('Facebook application settings'));
+ $this->out->elementStart('ul', 'form_data');
+
+ $this->li();
+ $this->input(
+ 'apikey',
+ _m('API key'),
+ _m('API key provided by Facebook'),
+ 'facebook'
+ );
+ $this->unli();
+
+ $this->li();
+ $this->input(
+ 'secret',
+ _m('Secret'),
+ _m('API secret provided by Facebook'),
+ 'facebook'
+ );
+ $this->unli();
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('fieldset');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Save'), 'submit', null, _('Save Facebook settings'));
+ }
+}
diff --git a/plugins/MobileProfile/MobileProfilePlugin.php b/plugins/MobileProfile/MobileProfilePlugin.php
index cd2531fa7..f788639ae 100644
--- a/plugins/MobileProfile/MobileProfilePlugin.php
+++ b/plugins/MobileProfile/MobileProfilePlugin.php
@@ -312,8 +312,6 @@ class MobileProfilePlugin extends WAP20Plugin
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
- } else if (common_config('twitter', 'enabled')) {
- $connect = 'twittersettings';
}
$action->elementStart('ul', array('id' => 'site_nav_global_primary'));
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php
index 720dedd0a..4ffbba45b 100644
--- a/plugins/OStatus/OStatusPlugin.php
+++ b/plugins/OStatus/OStatusPlugin.php
@@ -222,31 +222,62 @@ class OStatusPlugin extends Plugin
}
/**
- *
+ * Find any explicit remote mentions. Accepted forms:
+ * Webfinger: @user@example.com
+ * Profile link: @example.com/mublog/user
+ * @param Profile $sender (os user?)
+ * @param string $text input markup text
+ * @param array &$mention in/out param: set of found mentions
+ * @return boolean hook return value
*/
function onEndFindMentions($sender, $text, &$mentions)
{
- preg_match_all('/(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)/',
+ preg_match_all('!(?:^|\s+)
+ @( # Webfinger:
+ (?:\w+\.)*\w+ # user
+ @ # @
+ (?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ # domain
+ | # Profile:
+ (?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+ # domain
+ (?:/\w+)+ # /path1(/path2...)
+ )!x',
$text,
$wmatches,
PREG_OFFSET_CAPTURE);
foreach ($wmatches[1] as $wmatch) {
-
- $webfinger = $wmatch[0];
-
- $this->log(LOG_INFO, "Checking Webfinger for address '$webfinger'");
-
- $oprofile = Ostatus_profile::ensureWebfinger($webfinger);
+ $target = $wmatch[0];
+ $oprofile = null;
+
+ if (strpos($target, '/') === false) {
+ $this->log(LOG_INFO, "Checking Webfinger for address '$target'");
+ try {
+ $oprofile = Ostatus_profile::ensureWebfinger($target);
+ } catch (Exception $e) {
+ $this->log(LOG_ERR, "Webfinger check failed: " . $e->getMessage());
+ }
+ } else {
+ $schemes = array('https', 'http');
+ foreach ($schemes as $scheme) {
+ $url = "$scheme://$target";
+ $this->log(LOG_INFO, "Checking profile address '$url'");
+ try {
+ $oprofile = Ostatus_profile::ensureProfile($url);
+ if ($oprofile) {
+ continue;
+ }
+ } catch (Exception $e) {
+ $this->log(LOG_ERR, "Profile check failed: " . $e->getMessage());
+ }
+ }
+ }
if (empty($oprofile)) {
-
- $this->log(LOG_INFO, "No Ostatus_profile found for address '$webfinger'");
-
+ $this->log(LOG_INFO, "No Ostatus_profile found for address '$target'");
} else {
- $this->log(LOG_INFO, "Ostatus_profile found for address '$webfinger'");
+ $this->log(LOG_INFO, "Ostatus_profile found for address '$target'");
if ($oprofile->isGroup()) {
continue;
@@ -261,7 +292,7 @@ class OStatusPlugin extends Plugin
}
}
$mentions[] = array('mentioned' => array($profile),
- 'text' => $wmatch[0],
+ 'text' => $target,
'position' => $pos,
'url' => $profile->profileurl);
}
diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php
index f33690bc4..842d65e7d 100644
--- a/plugins/OStatus/actions/pushhub.php
+++ b/plugins/OStatus/actions/pushhub.php
@@ -104,7 +104,7 @@ class PushHubAction extends Action
throw new ClientException("Invalid hub.secret $secret; must be under 200 bytes.");
}
- $sub = HubSub::staticGet($sub->topic, $sub->callback);
+ $sub = HubSub::staticGet($topic, $callback);
if (!$sub) {
// Creating a new one!
$sub = new HubSub();
diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php
index e599d83a9..3120a70f9 100644
--- a/plugins/OStatus/classes/HubSub.php
+++ b/plugins/OStatus/classes/HubSub.php
@@ -260,9 +260,15 @@ class HubSub extends Memcached_DataObject
$retries = intval(common_config('ostatus', 'hub_retries'));
}
- $data = array('sub' => clone($this),
+ // We dare not clone() as when the clone is discarded it'll
+ // destroy the result data for the parent query.
+ // @fixme use clone() again when it's safe to copy an
+ // individual item from a multi-item query again.
+ $sub = HubSub::staticGet($this->topic, $this->callback);
+ $data = array('sub' => $sub,
'atom' => $atom,
'retries' => $retries);
+ common_log(LOG_INFO, "Queuing PuSH: $this->topic to $this->callback");
$qm = QueueManager::get();
$qm->enqueue($data, 'hubout');
}
diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php
index 96900d876..5a46aeeb6 100644
--- a/plugins/OStatus/classes/Magicsig.php
+++ b/plugins/OStatus/classes/Magicsig.php
@@ -146,8 +146,10 @@ class Magicsig extends Memcached_DataObject
$mod = base64_url_decode($matches[1]);
$exp = base64_url_decode($matches[2]);
- if ($matches[4]) {
+ if (!empty($matches[4])) {
$private_exp = base64_url_decode($matches[4]);
+ } else {
+ $private_exp = false;
}
$params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public');
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 7b1aec76b..a33e95d93 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -698,7 +698,7 @@ class Ostatus_profile extends Memcached_DataObject
{
// Get the canonical feed URI and check it
$discover = new FeedDiscovery();
- if ($hints['feedurl']) {
+ if (isset($hints['feedurl'])) {
$feeduri = $hints['feedurl'];
$feeduri = $discover->discoverFromFeedURL($feeduri);
} else {
@@ -1145,7 +1145,7 @@ class Ostatus_profile extends Memcached_DataObject
if (!empty($poco)) {
$url = $poco->getPrimaryURL();
- if ($url->type == 'homepage') {
+ if ($url && $url->type == 'homepage') {
$homepage = $url->value;
}
}
diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php
index 388df0a28..f8449b309 100644
--- a/plugins/OStatus/lib/discovery.php
+++ b/plugins/OStatus/lib/discovery.php
@@ -94,7 +94,7 @@ class Discovery
$links = call_user_func(array($class, 'discover'), $uri);
if ($link = Discovery::getService($links, Discovery::LRDD_REL)) {
// Load the LRDD XRD
- if ($link['template']) {
+ if (!empty($link['template'])) {
$xrd_uri = Discovery::applyTemplate($link['template'], $uri);
} else {
$xrd_uri = $link['href'];
diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php
index 16d27f8eb..85df26c54 100644
--- a/plugins/OStatus/lib/xrd.php
+++ b/plugins/OStatus/lib/xrd.php
@@ -53,17 +53,22 @@ class XRD
$xrd = new XRD();
$dom = new DOMDocument();
- $dom->loadXML($xml);
+ if (!$dom->loadXML($xml)) {
+ throw new Exception("Invalid XML");
+ }
$xrd_element = $dom->getElementsByTagName('XRD')->item(0);
// Check for host-meta host
- $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue;
+ $host = $xrd_element->getElementsByTagName('Host')->item(0);
if ($host) {
- $xrd->host = $host;
+ $xrd->host = $host->nodeValue;
}
// Loop through other elements
foreach ($xrd_element->childNodes as $node) {
+ if (!($node instanceof DOMElement)) {
+ continue;
+ }
switch ($node->tagName) {
case 'Expires':
$xrd->expires = $node->nodeValue;
@@ -156,20 +161,20 @@ class XRD
function saveLink($doc, $link)
{
$link_element = $doc->createElement('Link');
- if ($link['rel']) {
+ if (!empty($link['rel'])) {
$link_element->setAttribute('rel', $link['rel']);
}
- if ($link['type']) {
+ if (!empty($link['type'])) {
$link_element->setAttribute('type', $link['type']);
}
- if ($link['href']) {
+ if (!empty($link['href'])) {
$link_element->setAttribute('href', $link['href']);
}
- if ($link['template']) {
+ if (!empty($link['template'])) {
$link_element->setAttribute('template', $link['template']);
}
- if (is_array($link['title'])) {
+ if (!empty($link['title']) && is_array($link['title'])) {
foreach($link['title'] as $title) {
$title = $doc->createElement('Title', $title);
$link_element->appendChild($title);
diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README
index d3bcda598..72278b32e 100644
--- a/plugins/TwitterBridge/README
+++ b/plugins/TwitterBridge/README
@@ -1,47 +1,89 @@
+Twitter Bridge Plugin
+=====================
+
This Twitter "bridge" plugin allows you to integrate your StatusNet
instance with Twitter. Installing it will allow your users to:
- - automatically post notices to thier Twitter accounts
+ - automatically post notices to their Twitter accounts
- automatically subscribe to other Twitter users who are also using
your StatusNet install, if possible (requires running a daemon)
- import their Twitter friends' tweets (requires running a daemon)
+ - allow users to authenticate using Twitter ('Sign in with Twitter')
Installation
------------
-To enable the plugin, add the following to your config.php:
-
- addPlugin("TwitterBridge");
-
-OAuth is used to to access protected resources on Twitter (as opposed to
-HTTP Basic Auth)*. To use Twitter bridging you will need to register
-your instance of StatusNet as an application on Twitter
-(http://twitter.com/apps), and update the following variables in your
-config.php with the consumer key and secret Twitter generates for you:
-
- $config['twitter']['consumer_key'] = 'YOURKEY';
- $config['twitter']['consumer_secret'] = 'YOURSECRET';
+OAuth 1.0a (http://oauth.net) is used to to access protected resources
+on Twitter (as opposed to HTTP Basic Auth)*. To use Twitter bridging
+you will need to register your instance of StatusNet as an application
+on Twitter (http://twitter.com/apps). During the application
+registration process your application will be assigned a "consumer" key
+and secret, which the plugin will use to make OAuth requests to Twitter.
+You can either pass the consumer key and secret in when you enable the
+plugin, or set it using the Twitter administration panel.
When registering your application with Twitter set the type to "Browser"
and your Callback URL to:
http://example.org/mublog/twitter/authorization
-The default access type should be, "Read & Write".
+(Change "example.org" to your site domain and "mublog" to your site
+path.)
+
+The default access type should be "Read & Write".
+
+To enable the plugin, add the following to your config.php:
+
+ addPlugin(
+ 'TwitterBridge',
+ array(
+ 'consumer_key' => 'YOUR_CONSUMER_KEY',
+ 'consumer_secret' => 'YOUR_CONSUMER_SECRET'
+ )
+ );
* Note: The plugin will still push notices to Twitter for users who
- have previously setup the Twitter bridge using their Twitter name and
- password under an older versions of StatusNet, but all new Twitter
+ have previously set up the Twitter bridge using their Twitter name and
+ password under an older version of StatusNet, but all new Twitter
bridge connections will use OAuth.
-Deamons
+Administration panel
+--------------------
+
+As of StatusNet 0.9.0 there is a new administration panel that allows
+you to configure Twitter bridge settings within StatusNet itself,
+instead of having to specify them manually in your config.php. To enable
+the administration panel, you will need to add it to the list of active
+administration panels. You can do this via your config.php. E.g.:
+
+ $config['admin']['panels'][] = 'twitter';
+
+And to access it, you'll need to use a user with the "administrator"
+role (see: scripts/userrole.php).
+
+Sign in with Twitter
+--------------------
+
+With 0.9.0, StatusNet optionally allows users to register and
+authenticate using their Twitter credentials via the "Sign in with
+Twitter" pattern described here:
+
+ http://apiwiki.twitter.com/Sign-in-with-Twitter
+
+The option is _on_ by default when you install the plugin, but it can
+disabled via the Twitter bridge administration panel, or by adding the
+following line to your config.php:
+
+ $config['twitter']['signin'] = false;
+
+Daemons
-------
-For friend syncing and importing notices running two additional daemon
-scripts is necessary (synctwitterfriends.php and
-twitterstatusfetcher.php).
+For friend syncing and importing Twitter tweets, running two
+additional daemon scripts is necessary: synctwitterfriends.php and
+twitterstatusfetcher.php.
-In the daemons subidrectory of the plugin are three scripts:
+In the daemons subdirectory of the plugin are three scripts:
* Twitter Friends Syncing (daemons/synctwitterfriends.php)
@@ -51,13 +93,13 @@ subscribe to "friends" (people they "follow") on Twitter who also have
accounts on your StatusNet system, and who have previously set up a link
for automatically posting notices to Twitter.
-The plugin will try to start this daemon when you run
-scripts/startdaemons.sh.
+The plugin will start this daemon when you run scripts/startdaemons.sh.
* Importing statuses from Twitter (daemons/twitterstatusfetcher.php)
-To allow your users to import their friends' Twitter statuses, you will
-need to enable the bidirectional Twitter bridge in your config.php:
+You can allow uses to enable importing of your friends' Twitter
+timelines either in the Twitter bridge administration panel or in your
+config.php using the following configuration line:
$config['twitterimport']['enabled'] = true;
@@ -66,8 +108,9 @@ other daemons when you run scripts/startdaemons.sh.
Additionally, you will want to set the integration source variable,
which will keep notices posted to Twitter via StatusNet from looping
-back. The integration source should be set to the name of your
-application, exactly as you specified it on the settings page for your
+back. You can do this in the Twitter bridge administration panel, or
+via config.php. The integration source should be set to the name of your
+application _exactly_ as you specified it on the settings page for your
StatusNet application on Twitter, e.g.:
$config['integration']['source'] = 'YourApp';
@@ -79,7 +122,9 @@ set up Twitter bridging.
It's not strictly necessary to run this queue handler, and sites that
haven't enabled queuing are still able to push notices to Twitter, but
-for larger sites and sites that wish to improve performance, this
-script allows notices to be sent "offline" via a separate process.
+for larger sites and sites that wish to improve performance the script
+allows notices to be sent "offline" via a separate process.
-The plugin will start this script when you run scripts/startdaemons.sh.
+StatusNet will automatically use the TwitterQueueHandler if you have
+enabled the queuing subsystem. See the "Queues and daemons" section of
+the main README file for more information about how to do that.
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index c7f57ffc7..6ce69d5e2 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -23,7 +23,7 @@
* @author Julien C <chaumond@gmail.com>
* @copyright 2009-2010 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/
+ * @link http://status.net/
*/
if (!defined('STATUSNET')) {
@@ -32,8 +32,6 @@ if (!defined('STATUSNET')) {
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
-define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
-
/**
* Plugin for sending and importing Twitter statuses
*
@@ -44,19 +42,41 @@ define('TWITTERBRIDGEPLUGIN_VERSION', '0.9');
* @author Zach Copley <zach@status.net>
* @author Julien C <chaumond@gmail.com>
* @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://status.net/
* @link http://twitter.com/
*/
class TwitterBridgePlugin extends Plugin
{
+
+ const VERSION = STATUSNET_VERSION;
+
/**
* Initializer for the plugin.
*/
- function __construct()
+ function initialize()
{
- parent::__construct();
+ // Allow the key and secret to be passed in
+ // Control panel will override
+
+ if (isset($this->consumer_key)) {
+ $key = common_config('twitter', 'consumer_key');
+ if (empty($key)) {
+ Config::save('twitter', 'consumer_key', $this->consumer_key);
+ }
+ }
+
+ if (isset($this->consumer_secret)) {
+ $secret = common_config('twitter', 'consumer_secret');
+ if (empty($secret)) {
+ Config::save(
+ 'twitter',
+ 'consumer_secret',
+ $this->consumer_secret
+ );
+ }
+ }
}
/**
@@ -71,10 +91,17 @@ class TwitterBridgePlugin extends Plugin
function onRouterInitialized($m)
{
- $m->connect('twitter/authorization',
- array('action' => 'twitterauthorization'));
+ $m->connect(
+ 'twitter/authorization',
+ array('action' => 'twitterauthorization')
+ );
$m->connect('settings/twitter', array('action' => 'twittersettings'));
- $m->connect('main/twitterlogin', array('action' => 'twitterlogin'));
+
+ if (common_config('twitter', 'signin')) {
+ $m->connect('main/twitterlogin', array('action' => 'twitterlogin'));
+ }
+
+ $m->connect('admin/twitter', array('action' => 'twitteradminpanel'));
return true;
}
@@ -88,13 +115,16 @@ class TwitterBridgePlugin extends Plugin
*/
function onEndLoginGroupNav(&$action)
{
-
$action_name = $action->trimmed('action');
- $action->menuItem(common_local_url('twitterlogin'),
- _('Twitter'),
- _('Login or register using Twitter'),
- 'twitterlogin' === $action_name);
+ if (common_config('twitter', 'signin')) {
+ $action->menuItem(
+ common_local_url('twitterlogin'),
+ _m('Twitter'),
+ _m('Login or register using Twitter'),
+ 'twitterlogin' === $action_name
+ );
+ }
return true;
}
@@ -110,10 +140,12 @@ class TwitterBridgePlugin extends Plugin
{
$action_name = $action->trimmed('action');
- $action->menuItem(common_local_url('twittersettings'),
- _m('Twitter'),
- _m('Twitter integration options'),
- $action_name === 'twittersettings');
+ $action->menuItem(
+ common_local_url('twittersettings'),
+ _m('Twitter'),
+ _m('Twitter integration options'),
+ $action_name === 'twittersettings'
+ );
return true;
}
@@ -132,6 +164,7 @@ class TwitterBridgePlugin extends Plugin
case 'TwittersettingsAction':
case 'TwitterauthorizationAction':
case 'TwitterloginAction':
+ case 'TwitteradminpanelAction':
include_once INSTALLDIR . '/plugins/TwitterBridge/' .
strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
@@ -173,12 +206,18 @@ class TwitterBridgePlugin extends Plugin
*/
function onGetValidDaemons($daemons)
{
- array_push($daemons, INSTALLDIR .
- '/plugins/TwitterBridge/daemons/synctwitterfriends.php');
+ array_push(
+ $daemons,
+ INSTALLDIR
+ . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'
+ );
if (common_config('twitterimport', 'enabled')) {
- array_push($daemons, INSTALLDIR
- . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php');
+ array_push(
+ $daemons,
+ INSTALLDIR
+ . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'
+ );
}
return true;
@@ -197,17 +236,55 @@ class TwitterBridgePlugin extends Plugin
return true;
}
+ /**
+ * Add a Twitter tab to the admin panel
+ *
+ * @param Widget $nav Admin panel nav
+ *
+ * @return boolean hook value
+ */
+
+ function onEndAdminPanelNav($nav)
+ {
+ if (AdminPanelAction::canAdmin('twitter')) {
+
+ $action_name = $nav->action->trimmed('action');
+
+ $nav->out->menuItem(
+ common_local_url('twitteradminpanel'),
+ _m('Twitter'),
+ _m('Twitter bridge configuration'),
+ $action_name == 'twitteradminpanel',
+ 'nav_twitter_admin_panel'
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Plugin version data
+ *
+ * @param array &$versions array of version blocks
+ *
+ * @return boolean hook value
+ */
+
function onPluginVersion(&$versions)
{
- $versions[] = array('name' => 'TwitterBridge',
- 'version' => TWITTERBRIDGEPLUGIN_VERSION,
- 'author' => 'Zach Copley',
- 'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge',
- 'rawdescription' =>
- _m('The Twitter "bridge" plugin allows you to integrate ' .
- 'your StatusNet instance with ' .
- '<a href="http://twitter.com/">Twitter</a>.'));
+ $versions[] = array(
+ 'name' => 'TwitterBridge',
+ 'version' => self::VERSION,
+ 'author' => 'Zach Copley, Julien C',
+ 'homepage' => 'http://status.net/wiki/Plugin:TwitterBridge',
+ 'rawdescription' => _m(
+ 'The Twitter "bridge" plugin allows you to integrate ' .
+ 'your StatusNet instance with ' .
+ '<a href="http://twitter.com/">Twitter</a>.'
+ )
+ );
return true;
}
}
+
diff --git a/plugins/TwitterBridge/twitteradminpanel.php b/plugins/TwitterBridge/twitteradminpanel.php
new file mode 100644
index 000000000..b22e6d99f
--- /dev/null
+++ b/plugins/TwitterBridge/twitteradminpanel.php
@@ -0,0 +1,280 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Twitter bridge administration panel
+ *
+ * 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 StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2010 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Administer global Twitter bridge settings
+ *
+ * @category Admin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ */
+
+class TwitteradminpanelAction extends AdminPanelAction
+{
+ /**
+ * Returns the page title
+ *
+ * @return string page title
+ */
+
+ function title()
+ {
+ return _m('Twitter');
+ }
+
+ /**
+ * Instructions for using this form.
+ *
+ * @return string instructions
+ */
+
+ function getInstructions()
+ {
+ return _m('Twitter bridge settings');
+ }
+
+ /**
+ * Show the Twitter admin panel form
+ *
+ * @return void
+ */
+
+ function showForm()
+ {
+ $form = new TwitterAdminPanelForm($this);
+ $form->show();
+ return;
+ }
+
+ /**
+ * Save settings from the form
+ *
+ * @return void
+ */
+
+ function saveSettings()
+ {
+ static $settings = array(
+ 'twitter' => array('consumer_key', 'consumer_secret'),
+ 'integration' => array('source')
+ );
+
+ static $booleans = array(
+ 'twitter' => array('signin'),
+ 'twitterimport' => array('enabled')
+ );
+
+ $values = array();
+
+ foreach ($settings as $section => $parts) {
+ foreach ($parts as $setting) {
+ $values[$section][$setting]
+ = $this->trimmed($setting);
+ }
+ }
+
+ foreach ($booleans as $section => $parts) {
+ foreach ($parts as $setting) {
+ $values[$section][$setting]
+ = ($this->boolean($setting)) ? 1 : 0;
+ }
+ }
+
+ // This throws an exception on validation errors
+
+ $this->validate($values);
+
+ // assert(all values are valid);
+
+ $config = new Config();
+
+ $config->query('BEGIN');
+
+ foreach ($settings as $section => $parts) {
+ foreach ($parts as $setting) {
+ Config::save($section, $setting, $values[$section][$setting]);
+ }
+ }
+
+ foreach ($booleans as $section => $parts) {
+ foreach ($parts as $setting) {
+ Config::save($section, $setting, $values[$section][$setting]);
+ }
+ }
+
+ $config->query('COMMIT');
+
+ return;
+ }
+
+ function validate(&$values)
+ {
+ // Validate consumer key and secret (can't be too long)
+
+ if (mb_strlen($values['twitter']['consumer_key']) > 255) {
+ $this->clientError(
+ _m("Invalid consumer key. Max length is 255 characters.")
+ );
+ }
+
+ if (mb_strlen($values['twitter']['consumer_secret']) > 255) {
+ $this->clientError(
+ _m("Invalid consumer secret. Max length is 255 characters.")
+ );
+ }
+ }
+}
+
+class TwitterAdminPanelForm extends AdminForm
+{
+ /**
+ * ID of the form
+ *
+ * @return int ID of the form
+ */
+
+ function id()
+ {
+ return 'twitteradminpanel';
+ }
+
+ /**
+ * class of the form
+ *
+ * @return string class of the form
+ */
+
+ function formClass()
+ {
+ return 'form_settings';
+ }
+
+ /**
+ * Action of the form
+ *
+ * @return string URL of the action
+ */
+
+ function action()
+ {
+ return common_local_url('twitteradminpanel');
+ }
+
+ /**
+ * Data elements of the form
+ *
+ * @return void
+ */
+
+ function formData()
+ {
+ $this->out->elementStart(
+ 'fieldset',
+ array('id' => 'settings_twitter-application')
+ );
+ $this->out->element('legend', null, _m('Twitter application settings'));
+ $this->out->elementStart('ul', 'form_data');
+
+ $this->li();
+ $this->input(
+ 'consumer_key',
+ _m('Consumer key'),
+ _m('Consumer key assigned by Twitter'),
+ 'twitter'
+ );
+ $this->unli();
+
+ $this->li();
+ $this->input(
+ 'consumer_secret',
+ _m('Consumer secret'),
+ _m('Consumer secret assigned by Twitter'),
+ 'twitter'
+ );
+ $this->unli();
+
+ $this->li();
+ $this->input(
+ 'source',
+ _m('Integration source'),
+ _m('Name of your Twitter application'),
+ 'integration'
+ );
+ $this->unli();
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('fieldset');
+
+ $this->out->elementStart(
+ 'fieldset',
+ array('id' => 'settings_twitter-options')
+ );
+ $this->out->element('legend', null, _m('Options'));
+
+ $this->out->elementStart('ul', 'form_data');
+
+ $this->li();
+
+ $this->out->checkbox(
+ 'signin', _m('Enable "Sign-in with Twitter"'),
+ (bool) $this->value('signin', 'twitter'),
+ _m('Allow users to login with their Twitter credentials')
+ );
+ $this->unli();
+
+ $this->li();
+ $this->out->checkbox(
+ 'enabled', _m('Enable Twitter import'),
+ (bool) $this->value('enabled', 'twitterimport'),
+ _m('Allow users to import their Twitter friends\' timelines')
+ );
+ $this->unli();
+
+ $this->out->elementEnd('ul');
+
+ $this->out->elementEnd('fieldset');
+ }
+
+ /**
+ * Action elements
+ *
+ * @return void
+ */
+
+ function formActions()
+ {
+ $this->out->submit('submit', _('Save'), 'submit', null, _('Save Twitter settings'));
+ }
+}
diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php
index cabf69d7a..c93f6666b 100644
--- a/plugins/TwitterBridge/twitterauthorization.php
+++ b/plugins/TwitterBridge/twitterauthorization.php
@@ -47,7 +47,7 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
* @author Zach Copley <zach@status.net>
* @author Julien C <chaumond@gmail.com>
* @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://status.net/
*
*/
class TwitterauthorizationAction extends Action