From a5dc5f9c62aec5021b31e7f202edf2de3462b6ba Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 14:58:06 -0800 Subject: Upgrade XML output scrubbing to better deal with newline and a few other chars --- lib/util.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) 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) -- cgit v1.2.3-54-g00ecf From 45e8819c1b9cc618e9b2d6678b0ff14c653a09d3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 16:35:36 -0800 Subject: Fix a bunch of notice & warning-level messages that were breaking my inter-instance communications --- plugins/OStatus/classes/Magicsig.php | 4 +++- plugins/OStatus/classes/Ostatus_profile.php | 2 +- plugins/OStatus/lib/discovery.php | 2 +- plugins/OStatus/lib/xrd.php | 11 ++++++++--- 4 files changed, 13 insertions(+), 6 deletions(-) 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..93e8934c9 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -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..1de065db9 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; -- cgit v1.2.3-54-g00ecf From 493b5479c15f0e135054fd6b961e50e690077eda Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 16:36:33 -0800 Subject: OStatus: support @example.com/path/to/profile mentions as well as @profile@example.com (latter requires webfinger, former doesn't) Plus misc warnings/notices cleanup in the submission path. --- actions/newnotice.php | 3 ++ plugins/OStatus/OStatusPlugin.php | 57 ++++++++++++++++++++++------- plugins/OStatus/classes/Ostatus_profile.php | 2 +- plugins/OStatus/lib/discovery.php | 2 +- plugins/OStatus/lib/xrd.php | 10 ++--- 5 files changed, 54 insertions(+), 20 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/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/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 7b1aec76b..668a31df4 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 { 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..48c1c88e1 100644 --- a/plugins/OStatus/lib/xrd.php +++ b/plugins/OStatus/lib/xrd.php @@ -156,20 +156,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); -- cgit v1.2.3-54-g00ecf From 680ace19665f064404ddb4723d9b41ab69c6e1bd Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 18:46:34 -0800 Subject: High-priority OStatus fixes: * PuSHing out to multiple client services could fail; only first callback got reached * Correction for re-sub request to a known sub --- plugins/OStatus/actions/pushhub.php | 2 +- plugins/OStatus/classes/HubSub.php | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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'); } -- cgit v1.2.3-54-g00ecf From 6b2d67216ef7d550921f813981f7744d1687be78 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 21:34:50 -0800 Subject: Initial Twitter bridge admin panel --- lib/default.php | 5 +- plugins/TwitterBridge/README | 32 +-- plugins/TwitterBridge/TwitterBridgePlugin.php | 129 +++++++++--- plugins/TwitterBridge/twitteradminpanel.php | 280 +++++++++++++++++++++++++ plugins/TwitterBridge/twitterauthorization.php | 2 +- 5 files changed, 403 insertions(+), 45 deletions(-) create mode 100644 plugins/TwitterBridge/twitteradminpanel.php diff --git a/lib/default.php b/lib/default.php index d849055c2..668206acf 100644 --- a/lib/default.php +++ b/lib/default.php @@ -177,8 +177,9 @@ $default = array('source' => 'StatusNet', # source attribute for Twitter 'taguri' => null), # base for tag URIs 'twitter' => - array('enabled' => true, - 'consumer_key' => null, + array('enabled' => true, + 'signin' => true, + 'consumer_key' => null, 'consumer_secret' => null), 'cache' => array('base' => null), diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README index d3bcda598..91b34eb49 100644 --- a/plugins/TwitterBridge/README +++ b/plugins/TwitterBridge/README @@ -1,7 +1,7 @@ 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) @@ -9,18 +9,14 @@ instance with Twitter. Installing it will allow your users to: 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 (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: @@ -29,6 +25,16 @@ and your Callback URL to: 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 diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index c7f57ffc7..ac08cc593 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -23,7 +23,7 @@ * @author Julien C * @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 * @author Julien C * @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,13 @@ 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')); + $m->connect('admin/twitter', array('action' => 'twitteradminpanel')); return true; } @@ -88,13 +111,14 @@ 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); + $action->menuItem( + common_local_url('twitterlogin'), + _m('Twitter'), + _m('Login or register using Twitter'), + 'twitterlogin' === $action_name + ); return true; } @@ -110,10 +134,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 +158,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 +200,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 +230,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 ' . - 'Twitter.')); + $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 ' . + 'Twitter.' + ) + ); 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 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @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 + * @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 * @author Julien C * @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 -- cgit v1.2.3-54-g00ecf From 871b3aa6c00b38e1782949e201e5cbca7fb7a524 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 21:52:31 -0800 Subject: Remove un-needed config variable for enabling/disabling Twitter integration --- lib/action.php | 2 -- lib/default.php | 3 +-- plugins/Facebook/FacebookPlugin.php | 2 -- plugins/MobileProfile/MobileProfilePlugin.php | 2 -- 4 files changed, 1 insertion(+), 8 deletions(-) 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 668206acf..7b50242ae 100644 --- a/lib/default.php +++ b/lib/default.php @@ -177,8 +177,7 @@ $default = array('source' => 'StatusNet', # source attribute for Twitter 'taguri' => null), # base for tag URIs 'twitter' => - array('enabled' => true, - 'signin' => true, + array('signin' => true, 'consumer_key' => null, 'consumer_secret' => null), 'cache' => diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index 4266b886d..8fb81aea0 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -359,8 +359,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)) { 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')); -- cgit v1.2.3-54-g00ecf From 27a49361eb8fd4dc661283214003a71de34201e8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 22:41:07 -0800 Subject: - Make 'Sign in with Twitter' optional - Updates to the Twitter bridge plugin README --- plugins/TwitterBridge/README | 86 +++++++++++++++++++-------- plugins/TwitterBridge/TwitterBridgePlugin.php | 20 ++++--- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README index 91b34eb49..a386989b7 100644 --- a/plugins/TwitterBridge/README +++ b/plugins/TwitterBridge/README @@ -5,25 +5,29 @@ instance with Twitter. Installing it will allow your users to: - 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 ------------ -OAuth (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. +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: @@ -36,18 +40,47 @@ To enable the plugin, add the following to your config.php: ); * 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 +Admin 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 +-------------------- + +As of 0.9.0 you 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 admin 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) @@ -57,13 +90,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; @@ -72,8 +105,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'; @@ -85,7 +119,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, this 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 system. 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 ac08cc593..6ce69d5e2 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -96,7 +96,11 @@ class TwitterBridgePlugin extends Plugin 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; @@ -113,12 +117,14 @@ class TwitterBridgePlugin extends Plugin { $action_name = $action->trimmed('action'); - $action->menuItem( - common_local_url('twitterlogin'), - _m('Twitter'), - _m('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; } -- cgit v1.2.3-54-g00ecf From 1df8c2a44d9784e8f01794b20fe5a014d518474b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 22:58:27 -0800 Subject: Some wording / spelling fixes --- plugins/TwitterBridge/README | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README index a386989b7..72278b32e 100644 --- a/plugins/TwitterBridge/README +++ b/plugins/TwitterBridge/README @@ -1,3 +1,6 @@ +Twitter Bridge Plugin +===================== + This Twitter "bridge" plugin allows you to integrate your StatusNet instance with Twitter. Installing it will allow your users to: @@ -44,8 +47,8 @@ To enable the plugin, add the following to your config.php: password under an older version of StatusNet, but all new Twitter bridge connections will use OAuth. -Admin panel ------------ +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, @@ -61,15 +64,15 @@ role (see: scripts/userrole.php). Sign in with Twitter -------------------- -As of 0.9.0 you StatusNet optionally allows users to register and +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 admin panel, or by adding the following -line to your config.php: +disabled via the Twitter bridge administration panel, or by adding the +following line to your config.php: $config['twitter']['signin'] = false; @@ -119,9 +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 +for larger sites and sites that wish to improve performance the script allows notices to be sent "offline" via a separate process. StatusNet will automatically use the TwitterQueueHandler if you have -enabled the queuing system. See the "Queues and daemons" section of the -main README file for more information about how to do that. +enabled the queuing subsystem. See the "Queues and daemons" section of +the main README file for more information about how to do that. -- cgit v1.2.3-54-g00ecf From 40e1b249cf1535a6074c8b32e5820c8ad6427836 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 23:31:56 -0800 Subject: Initial Facebook admin panel --- plugins/Facebook/FacebookPlugin.php | 85 ++++++++++-- plugins/Facebook/facebookadminpanel.php | 223 ++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 12 deletions(-) create mode 100644 plugins/Facebook/facebookadminpanel.php diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index 8fb81aea0..014d0d197 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -22,7 +22,7 @@ * @category Plugin * @package StatusNet * @author Zach Copley - * @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; @@ -122,6 +154,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 @@ -523,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 ' . - 'Facebook ' . - '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 ' . + 'Facebook ' . + '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 @@ +. + * + * @category Settings + * @package StatusNet + * @author Zach Copley + * @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 + * @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')); + } +} -- cgit v1.2.3-54-g00ecf