From f55ef878f603d81857509f42f50c1a28b1a8e1f2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 18 Feb 2010 01:47:44 +0000 Subject: [PATCH 01/37] Fix for cross site OMB posting problem --- lib/omb.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/omb.php b/lib/omb.php index 0f38a49369..17132a594f 100644 --- a/lib/omb.php +++ b/lib/omb.php @@ -29,11 +29,9 @@ require_once 'Auth/Yadis/Yadis.php'; function omb_oauth_consumer() { - static $con = null; - if (is_null($con)) { - $con = new OAuthConsumer(common_root_url(), ''); - } - return $con; + // Don't try to make this static. Leads to issues in + // multi-site setups - Z + return new OAuthConsumer(common_root_url(), ''); } function omb_oauth_server() From a5dc5f9c62aec5021b31e7f202edf2de3462b6ba Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 14:58:06 -0800 Subject: [PATCH 02/37] 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 d12a7920d2..7a170a5f5f 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) From 45e8819c1b9cc618e9b2d6678b0ff14c653a09d3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 16:35:36 -0800 Subject: [PATCH 03/37] 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 96900d8761..5a46aeeb6e 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 7b1aec76ba..93e8934c9e 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 388df0a28f..f8449b309e 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 16d27f8eb7..1de065db9f 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; From 493b5479c15f0e135054fd6b961e50e690077eda Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 16:36:33 -0800 Subject: [PATCH 04/37] 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 | 55 ++++++++++++++++----- plugins/OStatus/classes/Ostatus_profile.php | 2 +- plugins/OStatus/lib/discovery.php | 2 +- plugins/OStatus/lib/xrd.php | 10 ++-- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/actions/newnotice.php b/actions/newnotice.php index 78480ababb..ed0fa1b2b5 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 720dedd0a0..4ffbba45b9 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) { + $target = $wmatch[0]; + $oprofile = null; - $webfinger = $wmatch[0]; - - $this->log(LOG_INFO, "Checking Webfinger for address '$webfinger'"); - - $oprofile = Ostatus_profile::ensureWebfinger($webfinger); + 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 7b1aec76ba..668a31df4c 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 388df0a28f..f8449b309e 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 16d27f8eb7..48c1c88e15 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); From 680ace19665f064404ddb4723d9b41ab69c6e1bd Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 1 Mar 2010 18:46:34 -0800 Subject: [PATCH 05/37] 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 f33690bc49..842d65e7d2 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 e599d83a96..3120a70f9f 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'); } From 6b2d67216ef7d550921f813981f7744d1687be78 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 21:34:50 -0800 Subject: [PATCH 06/37] Initial Twitter bridge admin panel --- lib/default.php | 5 +- plugins/TwitterBridge/README | 32 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 129 ++++++-- plugins/TwitterBridge/twitteradminpanel.php | 280 ++++++++++++++++++ .../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 d849055c21..668206acf0 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 d3bcda5984..91b34eb497 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 c7f57ffc77..ac08cc593f 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 0000000000..b22e6d99fe --- /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 cabf69d7a8..c93f6666bc 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 From 871b3aa6c00b38e1782949e201e5cbca7fb7a524 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 21:52:31 -0800 Subject: [PATCH 07/37] 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 a7e0eb33ba..0918c68582 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 668206acf0..7b50242ae2 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 4266b886d9..8fb81aea02 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 cd2531fa72..f788639aed 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')); From 27a49361eb8fd4dc661283214003a71de34201e8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 22:41:07 -0800 Subject: [PATCH 08/37] - 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 91b34eb497..a386989b7a 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 ac08cc593f..6ce69d5e2b 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; } From 1df8c2a44d9784e8f01794b20fe5a014d518474b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 22:58:27 -0800 Subject: [PATCH 09/37] 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 a386989b7a..72278b32e6 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. From 40e1b249cf1535a6074c8b32e5820c8ad6427836 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 1 Mar 2010 23:31:56 -0800 Subject: [PATCH 10/37] 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 8fb81aea02..014d0d1970 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 0000000000..ae1c7302f9 --- /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')); + } +} From c25fc8a4b51466f13c41efc0565bf15f78f6cb4d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 2 Mar 2010 02:54:52 -0500 Subject: [PATCH 11/37] Show and no activity actors for user feed We only need one author for user feeds: the user themselves. So, show the user as the activity:subject, and don't repeat the same activity:actor for every notice unnecessarily. --- classes/Notice.php | 8 +++++--- lib/atomnoticefeed.php | 16 +++++++++++++--- lib/atomusernoticefeed.php | 11 +++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 3702dbcfa8..4b5dbb416f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1090,7 +1090,7 @@ class Notice extends Memcached_DataObject return $groups; } - function asAtomEntry($namespace=false, $source=false) + function asAtomEntry($namespace=false, $source=false, $author=true) { $profile = $this->getProfile(); @@ -1136,8 +1136,10 @@ class Notice extends Memcached_DataObject $xs->element('title', null, $this->content); $xs->element('summary', null, $this->content); - $xs->raw($profile->asAtomAuthor()); - $xs->raw($profile->asActivityActor()); + if ($author) { + $xs->raw($profile->asAtomAuthor()); + $xs->raw($profile->asActivityActor()); + } $xs->element('link', array('rel' => 'alternate', 'type' => 'text/html', diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index 3c3556cb95..e4df731fe0 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -107,9 +107,19 @@ class AtomNoticeFeed extends Atom10Feed */ function addEntryFromNotice($notice) { - $this->addEntryRaw($notice->asAtomEntry()); + $source = $this->showSource(); + $author = $this->showAuthor(); + + $this->addEntryRaw($notice->asAtomEntry(false, $source, $author)); } + function showSource() + { + return true; + } + + function showAuthor() + { + return true; + } } - - diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php index 2ad8de4550..6485aaa43d 100644 --- a/lib/atomusernoticefeed.php +++ b/lib/atomusernoticefeed.php @@ -61,6 +61,7 @@ class AtomUserNoticeFeed extends AtomNoticeFeed if (!empty($user)) { $profile = $user->getProfile(); $this->addAuthor($profile->nickname, $user->uri); + $this->setActivitySubject($profile->asActivityNoun('subject')); } } @@ -68,4 +69,14 @@ class AtomUserNoticeFeed extends AtomNoticeFeed { return $this->user; } + + function showSource() + { + return false; + } + + function showAuthor() + { + return false; + } } From 40ac7247979dc6f33f56b20c907d55deb6b9c815 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 2 Mar 2010 03:13:05 -0500 Subject: [PATCH 12/37] don't duplicate title in summary in Atom output per RFC4287 4.2.13 --- classes/Notice.php | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 4b5dbb416f..c31d8bd69e 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1134,7 +1134,6 @@ class Notice extends Memcached_DataObject } $xs->element('title', null, $this->content); - $xs->element('summary', null, $this->content); if ($author) { $xs->raw($profile->asAtomAuthor()); From 9816b35063b75f1aa051d8b1ecefe716d99f5dc4 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 2 Mar 2010 00:38:00 -0800 Subject: [PATCH 13/37] Update Facebook plugin README with info about new admin panel --- plugins/Facebook/README | 84 +++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/plugins/Facebook/README b/plugins/Facebook/README index bf2f4a1800..14c1d32419 100644 --- a/plugins/Facebook/README +++ b/plugins/Facebook/README @@ -1,6 +1,9 @@ -This plugin allows you to use Facebook Connect with StatusNet, provides a -Facebook application for your users, and allows them to update their -Facebook statuses from StatusNet. +Facebook Plugin +=============== + +This plugin allows you to use Facebook Connect with StatusNet, provides +a Facebook canvas application for your users, and allows them to update +their Facebook statuses from StatusNet. Facebook Connect ---------------- @@ -15,12 +18,12 @@ Facebook credentials. With Facebook Connect, your users can: Built-in Facebook Application ----------------------------- -The plugin also installs a StatusNet Facebook application that allows your -users to automatically update their Facebook statuses with their latest -notices, invite their friends to use the app (and thus your site), view -their notice timelines, and post notices -- all from within Facebook. The -application is built into the StatusNet Facebook plugin and runs on your -host. +The plugin also installs a StatusNet Facebook canvas application that +allows your users to automatically update their Facebook status with +their latest notices, invite their friends to use the app (and thus your +site), view their notice timelines and post notices -- all from within +Facebook. The application is built into the StatusNet Facebook plugin +and runs on your host. Quick setup instructions* ------------------------- @@ -29,13 +32,9 @@ Install the Facebook Developer application on Facebook: http://www.facebook.com/developers/ -Use it to create a new application and generate an API key and secret. Add a -Facebook app section of your config.php and copy in the key and secret, -e.g.: - - // Config section for the built-in Facebook application - $config['facebook']['apikey'] = 'APIKEY'; - $config['facebook']['secret'] = 'SECRET'; +Use it to create a new application and generate an API key and secret. +You will need the key and secret so cut-n-paste them into your text +editor or write them down. In Facebook's application editor, specify the following URLs for your app: @@ -67,11 +66,36 @@ can be left with default values. http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site http://wiki.developers.facebook.com/index.php/Creating_your_first_application -Finally you must activate the plugin by adding the following line to your -config.php: +Finally you must activate the plugin by adding it in your config.php +(this is where you'll need the API key and secret generated earlier): + + addPlugin( + 'Facebook', + array( + 'apikey' => 'YOUR_APIKEY', + 'secret' => 'YOUR_SECRET' + ) + ); + +Administration Panel +-------------------- + +As of StatusNet 0.9.0 you can alternatively specify the key and secret +via a Facebook administration panel from within StatusNet, in which case +you can just add: addPlugin('Facebook'); +to activate the plugin. + +NOTE: To enable the administration panel you'll need to add it to the +list of active administration panels, e.g.: + + $config['admin']['panels'][] = 'facebook'; + +and of course you'll need a user with the administrative role to access +it and input the API key and secret (see: scripts/userrole.php). + Testing It Out -------------- @@ -81,11 +105,11 @@ disconnect* to their Facebook accounts from it. To try out the plugin, fire up your browser and connect to: - http://SITE/PATH_TO_STATUSNET/main/facebooklogin + http://example.net/mublog/main/facebooklogin or, if you do not have fancy URLs turned on: - http://SITE/PATH_TO_STATUSNET/index.php/main/facebooklogin + http://example.net/mublog/index.php/main/facebooklogin You should see a page with a blue button that says: "Connect with Facebook" and you should be able to login or register. @@ -101,7 +125,7 @@ the app, you are given the option to update their Facebook status via StatusNet. * Note: Before a user can disconnect from Facebook, she must set a normal - StatusNet password. Otherwise, she might not be able to login in to her + StatusNet password. Otherwise, she might not be able to login in to her account in the future. This is usually only required for users who have used Facebook Connect to register their StatusNet account, and therefore haven't already set a local password. @@ -109,16 +133,20 @@ StatusNet. Offline Queue Handling ---------------------- -For larger sites needing better performance it's possible to enable queuing -and have users' notices posted to Facebook via a separate "offline" -FacebookQueueHandler (facebookqueuhandler.php in the Facebook plugin -directory), which will be started by the plugin along with their other -daemons when you run scripts/startdaemons.sh. See the StatusNet README for -more about queuing and daemons. +For larger sites needing better performance it's possible to enable +queuing and have users' notices posted to Facebook via a separate +"offline" process -- FacebookQueueHandler (facebookqueuhandler.php in +the Facebook plugin directory). It will run automatically if you have +enabled StatusNet's offline queueing subsystem. See the "Queues and +daemons" section in the StatusNet README for more about queuing. + TODO ---- +- Make Facebook Connect work for authentication for multi-site setups + (e.g.: *.status.net) +- Posting to Facebook user streams using only Facebook Connect - Invite Facebook friends to use your StatusNet installation via Facebook Connect - Auto-subscribe Facebook friends already using StatusNet @@ -126,4 +154,4 @@ TODO - Allow users to update their Facebook statuses once they have authenticated with Facebook Connect (no need for them to use the Facebook app if they don't want to). -- Re-design the whole thing to support multiple instances of StatusNet +- Import a user's Facebook updates into StatusNet From e2578cfad68c45ca177c51997c4cc7c0abafbd9a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 2 Mar 2010 03:40:43 -0500 Subject: [PATCH 14/37] Revert "Show and no activity actors for user feed" This reverts commit c25fc8a4b51466f13c41efc0565bf15f78f6cb4d. --- classes/Notice.php | 8 +++----- lib/atomnoticefeed.php | 16 +++------------- lib/atomusernoticefeed.php | 11 ----------- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index c31d8bd69e..7c424ee8a6 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1090,7 +1090,7 @@ class Notice extends Memcached_DataObject return $groups; } - function asAtomEntry($namespace=false, $source=false, $author=true) + function asAtomEntry($namespace=false, $source=false) { $profile = $this->getProfile(); @@ -1135,10 +1135,8 @@ class Notice extends Memcached_DataObject $xs->element('title', null, $this->content); - if ($author) { - $xs->raw($profile->asAtomAuthor()); - $xs->raw($profile->asActivityActor()); - } + $xs->raw($profile->asAtomAuthor()); + $xs->raw($profile->asActivityActor()); $xs->element('link', array('rel' => 'alternate', 'type' => 'text/html', diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index e4df731fe0..3c3556cb95 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -107,19 +107,9 @@ class AtomNoticeFeed extends Atom10Feed */ function addEntryFromNotice($notice) { - $source = $this->showSource(); - $author = $this->showAuthor(); - - $this->addEntryRaw($notice->asAtomEntry(false, $source, $author)); + $this->addEntryRaw($notice->asAtomEntry()); } - function showSource() - { - return true; - } - - function showAuthor() - { - return true; - } } + + diff --git a/lib/atomusernoticefeed.php b/lib/atomusernoticefeed.php index 6485aaa43d..2ad8de4550 100644 --- a/lib/atomusernoticefeed.php +++ b/lib/atomusernoticefeed.php @@ -61,7 +61,6 @@ class AtomUserNoticeFeed extends AtomNoticeFeed if (!empty($user)) { $profile = $user->getProfile(); $this->addAuthor($profile->nickname, $user->uri); - $this->setActivitySubject($profile->asActivityNoun('subject')); } } @@ -69,14 +68,4 @@ class AtomUserNoticeFeed extends AtomNoticeFeed { return $this->user; } - - function showSource() - { - return false; - } - - function showAuthor() - { - return false; - } } From 623faf9f2d83b8fd6134e77ad6f5dd1cedc7a5c1 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 09:57:28 -0500 Subject: [PATCH 15/37] Just a label change. Since the user already went ahead with subscribing, in this step we are just confirming the profile. --- plugins/OStatus/actions/ostatussub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php index f45e6a8d1a..e318701a24 100644 --- a/plugins/OStatus/actions/ostatussub.php +++ b/plugins/OStatus/actions/ostatussub.php @@ -112,7 +112,7 @@ class OStatusSubAction extends Action $this->submit('submit', _m('Join'), 'submit', null, _m('Join this group')); } else { - $this->submit('submit', _m('Subscribe'), 'submit', null, + $this->submit('submit', _m('Confirm'), 'submit', null, _m('Subscribe to this user')); } $this->elementEnd('fieldset'); From 350e1289af67cb2830c8ca7fc8202566ab32f2e0 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 12:33:25 -0500 Subject: [PATCH 16/37] Added event hook for before and after personal timeline content --- EVENTS.txt | 6 ++++++ actions/all.php | 22 +++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index a2b405acc8..bb4936b354 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -770,6 +770,12 @@ StartShowSubscriptionsContent: before showing the subscriptions content EndShowSubscriptionsContent: after showing the subscriptions content - $action: the current action +StartShowAllContent: before showing the all (you and friends) content +- $action: the current action + +EndShowAllContent: after showing the all (you and friends) content +- $action: the current action + StartDeleteUserForm: starting the data in the form for deleting a user - $action: action being shown - $user: user being deleted diff --git a/actions/all.php b/actions/all.php index 3eb1852147..6be0c00e3a 100644 --- a/actions/all.php +++ b/actions/all.php @@ -144,18 +144,22 @@ class AllAction extends ProfileAction function showContent() { - $nl = new NoticeList($this->notice, $this); + if (Event::handle('StartShowAllContent', array($this))) { + $nl = new NoticeList($this->notice, $this); - $cnt = $nl->show(); + $cnt = $nl->show(); - if (0 == $cnt) { - $this->showEmptyListMessage(); + if (0 == $cnt) { + $this->showEmptyListMessage(); + } + + $this->pagination( + $this->page > 1, $cnt > NOTICES_PER_PAGE, + $this->page, 'all', array('nickname' => $this->user->nickname) + ); + + Event::handle('EndShowAllContent', array($this)); } - - $this->pagination( - $this->page > 1, $cnt > NOTICES_PER_PAGE, - $this->page, 'all', array('nickname' => $this->user->nickname) - ); } function showPageTitle() From 5abff9104130ca6b49de6ab19f0359e888e1a085 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 12:34:35 -0500 Subject: [PATCH 17/37] Generalized style for entity remote subscription action --- plugins/OStatus/theme/base/css/ostatus.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/OStatus/theme/base/css/ostatus.css b/plugins/OStatus/theme/base/css/ostatus.css index feeeb47d38..e370810af7 100644 --- a/plugins/OStatus/theme/base/css/ostatus.css +++ b/plugins/OStatus/theme/base/css/ostatus.css @@ -38,11 +38,11 @@ display:none; min-width:96px; } -#subscriptions #entity_remote_subscribe { +#entity_remote_subscribe { padding:0; float:right; } -#subscriptions .entity_remote_subscribe { +.entity_remote_subscribe { float:right; } From 88c33bbb57433b3f10407f590b49d11ec4d5d530 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 12:35:27 -0500 Subject: [PATCH 18/37] Showing remote subscription button on the personal timeline --- plugins/OStatus/OStatusPlugin.php | 18 ++++++++++++++++++ plugins/OStatus/theme/base/css/ostatus.css | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 4ffbba45b9..64504547f4 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -721,6 +721,24 @@ class OStatusPlugin extends Plugin return true; } + function onStartShowAllContent($action) + { + $user = common_current_user(); + if ($user && ($user->id == $action->profile->id)) { + $action->elementStart('div', 'entity_actions'); + $action->elementStart('p', array('id' => 'entity_remote_subscribe', + 'class' => 'entity_subscribe')); + $action->element('a', array('href' => common_local_url('ostatussub'), + 'class' => 'entity_remote_subscribe') + , _m('Subscribe to remote user')); + $action->elementEnd('p'); + $action->elementEnd('div'); + } + + return true; + } + + /** * Ping remote profiles with updates to this profile. * Salmon pings are queued for background processing. diff --git a/plugins/OStatus/theme/base/css/ostatus.css b/plugins/OStatus/theme/base/css/ostatus.css index e370810af7..68470f4e41 100644 --- a/plugins/OStatus/theme/base/css/ostatus.css +++ b/plugins/OStatus/theme/base/css/ostatus.css @@ -46,3 +46,7 @@ float:right; .entity_remote_subscribe { float:right; } + +#all #entity_remote_subscribe { +margin-top:-52px; +} From e3c2b8e0b50d750ad35cb313726fd2706684c09b Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 12:41:18 -0500 Subject: [PATCH 19/37] Refactored remote subscribe action in OStatus --- plugins/OStatus/OStatusPlugin.php | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 64504547f4..7b6d22acf3 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -706,22 +706,19 @@ class OStatusPlugin extends Plugin function onStartShowSubscriptionsContent($action) { - $user = common_current_user(); - if ($user && ($user->id == $action->profile->id)) { - $action->elementStart('div', 'entity_actions'); - $action->elementStart('p', array('id' => 'entity_remote_subscribe', - 'class' => 'entity_subscribe')); - $action->element('a', array('href' => common_local_url('ostatussub'), - 'class' => 'entity_remote_subscribe') - , _m('Subscribe to remote user')); - $action->elementEnd('p'); - $action->elementEnd('div'); - } + $this->showEntityRemoteSubscribe($action); return true; } function onStartShowAllContent($action) + { + $this->showEntityRemoteSubscribe($action); + + return true; + } + + function showEntityRemoteSubscribe($action) { $user = common_current_user(); if ($user && ($user->id == $action->profile->id)) { @@ -734,11 +731,8 @@ class OStatusPlugin extends Plugin $action->elementEnd('p'); $action->elementEnd('div'); } - - return true; } - /** * Ping remote profiles with updates to this profile. * Salmon pings are queued for background processing. From 1550d1004d44c8223b2280a35ca2f64ed61178ef Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 15:24:07 -0500 Subject: [PATCH 20/37] Don't need to float the anchor --- plugins/OStatus/theme/base/css/ostatus.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/OStatus/theme/base/css/ostatus.css b/plugins/OStatus/theme/base/css/ostatus.css index 68470f4e41..13e30ef5df 100644 --- a/plugins/OStatus/theme/base/css/ostatus.css +++ b/plugins/OStatus/theme/base/css/ostatus.css @@ -43,10 +43,6 @@ padding:0; float:right; } -.entity_remote_subscribe { -float:right; -} - #all #entity_remote_subscribe { margin-top:-52px; } From 4113f28851020509163a31ab20658429d86c3134 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 15:25:32 -0500 Subject: [PATCH 21/37] Added Subscribe button to remote user entity actions in profie lists --- lib/profilelist.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/profilelist.php b/lib/profilelist.php index 693cd64492..4f1e84a6af 100644 --- a/lib/profilelist.php +++ b/lib/profilelist.php @@ -273,13 +273,18 @@ class ProfileListItem extends Widget $usf = new UnsubscribeForm($this->out, $this->profile); $usf->show(); } else { - // Is it a local user? can't remote sub from a list - // XXX: make that possible! $other = User::staticGet('id', $this->profile->id); if (!empty($other)) { $sf = new SubscribeForm($this->out, $this->profile); $sf->show(); } + else { + $url = common_local_url('remotesubscribe', + array('nickname' => $this->profile->nickname)); + $this->out->element('a', array('href' => $url, + 'class' => 'entity_remote_subscribe'), + _('Subscribe')); + } } $this->out->elementEnd('li'); } From c30f95c55cb4fcdde53c0549335d29ed6849cf95 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 22 Jan 2010 10:12:26 -0500 Subject: [PATCH 22/37] Updated some references to the long gnone "isEnclosure" function to the new "getEnclosure" --- classes/File.php | 2 ++ lib/apiaction.php | 9 +++++---- lib/util.php | 15 ++++----------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/classes/File.php b/classes/File.php index 91b12d2e28..189e04ce02 100644 --- a/classes/File.php +++ b/classes/File.php @@ -279,6 +279,8 @@ class File extends Memcached_DataObject if($oembed->modified) $enclosure->modified=$oembed->modified; unset($oembed->size); } + } else { + return false; } } } diff --git a/lib/apiaction.php b/lib/apiaction.php index 8049c09016..f71432e032 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -291,11 +291,12 @@ class ApiAction extends Action $twitter_status['attachments'] = array(); foreach ($attachments as $attachment) { - if ($attachment->isEnclosure()) { + $enclosure_o=$attachment->getEnclosure(); + if ($attachment_enclosure) { $enclosure = array(); - $enclosure['url'] = $attachment->url; - $enclosure['mimetype'] = $attachment->mimetype; - $enclosure['size'] = $attachment->size; + $enclosure['url'] = $enclosure_o->url; + $enclosure['mimetype'] = $enclosure_o->mimetype; + $enclosure['size'] = $enclosure_o->size; $twitter_status['attachments'][] = $enclosure; } } diff --git a/lib/util.php b/lib/util.php index 439db581a5..add1b0ae67 100644 --- a/lib/util.php +++ b/lib/util.php @@ -770,20 +770,13 @@ function common_linkify($url) { } if (!empty($f)) { - if ($f->isEnclosure()) { + if ($f->getEnclosure()) { $is_attachment = true; $attachment_id = $f->id; - } else { - $foe = File_oembed::staticGet('file_id', $f->id); - if (!empty($foe)) { - // if it has OEmbed info, it's an attachment, too - $is_attachment = true; - $attachment_id = $f->id; - $thumb = File_thumbnail::staticGet('file_id', $f->id); - if (!empty($thumb)) { - $has_thumb = true; - } + $thumb = File_thumbnail::staticGet('file_id', $f->id); + if (!empty($thumb)) { + $has_thumb = true; } } } From 09705a1e989d202859d637f8a84383f365b7906a Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 22 Jan 2010 10:40:21 -0500 Subject: [PATCH 23/37] stupid mistake... let's not talk about this. --- lib/apiaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/apiaction.php b/lib/apiaction.php index f71432e032..eef0ba637d 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -292,7 +292,7 @@ class ApiAction extends Action foreach ($attachments as $attachment) { $enclosure_o=$attachment->getEnclosure(); - if ($attachment_enclosure) { + if ($enclosure_o) { $enclosure = array(); $enclosure['url'] = $enclosure_o->url; $enclosure['mimetype'] = $enclosure_o->mimetype; From 9f94d6defa0d2536cb1f20a4c1c44ff78fd3f039 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 2 Mar 2010 19:15:24 -0500 Subject: [PATCH 24/37] Changed the geo location cookie Expire to Session. --- js/util.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index 949aec957d..d08c46fe64 100644 --- a/js/util.js +++ b/js/util.js @@ -53,7 +53,7 @@ var SN = { // StatusNet NoticeLocationNs: 'notice_data-location_ns', NoticeGeoName: 'notice_data-geo_name', NoticeDataGeo: 'notice_data-geo', - NoticeDataGeoCookie: 'notice_data-geo_cookie', + NoticeDataGeoCookie: 'NoticeDataGeo', NoticeDataGeoSelected: 'notice_data-geo_selected', StatusNetInstance:'StatusNetInstance' } @@ -494,7 +494,7 @@ var SN = { // StatusNet $('#'+SN.C.S.NoticeLocationId).val(''); $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); - $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); + $.cookie(SN.C.S.NoticeDataGeoCookie, 'disabled', { path: '/' }); } function getJSONgeocodeURL(geocodeURL, data) { @@ -537,7 +537,7 @@ var SN = { // StatusNet NDG: true }; - $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/', expires: SN.U.GetFullYear(2029, 0, 1) }); + $.cookie(SN.C.S.NoticeDataGeoCookie, JSON.stringify(cookieValue), { path: '/' }); }); } From 79ffebb51b1141791d5ee7478e3a7beaa9fe8faa Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 2 Mar 2010 16:30:09 -0800 Subject: [PATCH 25/37] OStatus: save file records for enclosures Also stripping id from foreign HTML messages (could interfere with UI) and disabled failing attachment popup for a.attachment links that don't have a proper id, so you can click through instead of getting an error. Issues: * any other links aren't marked and saved * inconsistent behavior between local and remote attachments (local displays in lightbox, remote doesn't) * if the enclosure'd object isn't referenced in the content, you won't be offered a link to it in our UI --- classes/File.php | 7 ++++++ classes/Notice.php | 28 +++++++++++++++++++-- js/util.js | 7 ++++-- lib/activity.php | 5 ++++ plugins/OStatus/classes/Ostatus_profile.php | 12 +++++++-- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/classes/File.php b/classes/File.php index 189e04ce02..1b8ef1b3ee 100644 --- a/classes/File.php +++ b/classes/File.php @@ -286,5 +286,12 @@ class File extends Memcached_DataObject } return $enclosure; } + + // quick back-compat hack, since there's still code using this + function isEnclosure() + { + $enclosure = $this->getEnclosure(); + return !empty($enclosure); + } } diff --git a/classes/Notice.php b/classes/Notice.php index 63dc968972..c1263c7821 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -211,6 +211,8 @@ class Notice extends Memcached_DataObject * extracting ! tags from content * array 'tags' list of hashtag strings to save with the notice * in place of extracting # tags from content + * array 'urls' list of attached/referred URLs to save with the + * notice in place of extracting links from content * @fixme tag override * * @return Notice @@ -380,8 +382,11 @@ class Notice extends Memcached_DataObject $notice->saveTags(); } - // @fixme pass in data for URLs too? - $notice->saveUrls(); + if (isset($urls)) { + $notice->saveKnownUrls($urls); + } else { + $notice->saveUrls(); + } // Prepare inbox delivery, may be queued to background. $notice->distribute(); @@ -427,6 +432,25 @@ class Notice extends Memcached_DataObject common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id); } + /** + * Save the given URLs as related links/attachments to the db + * + * follow redirects and save all available file information + * (mimetype, date, size, oembed, etc.) + * + * @return void + */ + function saveKnownUrls($urls) + { + // @fixme validation? + foreach ($urls as $url) { + File::processNew($url, $this->id); + } + } + + /** + * @private callback + */ function saveUrl($data) { list($url, $notice_id) = $data; File::processNew($url, $notice_id); diff --git a/js/util.js b/js/util.js index d08c46fe64..3efda0d7b9 100644 --- a/js/util.js +++ b/js/util.js @@ -423,8 +423,11 @@ var SN = { // StatusNet }; notice.find('a.attachment').click(function() { - $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); - return false; + var attachId = ($(this).attr('id').substring('attachment'.length + 1)); + if (attachId) { + $().jOverlay({url: $('address .url')[0].href+'attachment/' + attachId + '/ajax'}); + return false; + } }); if ($('#shownotice').length == 0) { diff --git a/lib/activity.php b/lib/activity.php index b201532138..ce14fa2546 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -1044,6 +1044,7 @@ class Activity public $id; // ID of the activity public $title; // title of the activity public $categories = array(); // list of AtomCategory objects + public $enclosures = array(); // list of enclosure URL references /** * Turns a regular old Atom into a magical activity @@ -1140,6 +1141,10 @@ class Activity $this->categories[] = new AtomCategory($catEl); } } + + foreach (ActivityUtils::getLinks($entry, 'enclosure') as $link) { + $this->enclosures[] = $link->getAttribute('href'); + } } /** diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index a33e95d932..059c19e7c2 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -550,7 +550,8 @@ class Ostatus_profile extends Memcached_DataObject 'rendered' => $rendered, 'replies' => array(), 'groups' => array(), - 'tags' => array()); + 'tags' => array(), + 'urls' => array()); // Check for optional attributes... @@ -595,6 +596,12 @@ class Ostatus_profile extends Memcached_DataObject } } + // Atom enclosures -> attachment URLs + foreach ($activity->enclosures as $href) { + // @fixme save these locally or....? + $options['urls'][] = $href; + } + try { $saved = Notice::saveNew($oprofile->profile_id, $content, @@ -620,7 +627,8 @@ class Ostatus_profile extends Memcached_DataObject protected function purify($html) { require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; - $config = array('safe' => 1); + $config = array('safe' => 1, + 'deny_attribute' => 'id,style,on*'); return htmLawed($html, $config); } From ca21f1da8603be348c1a34a8bcc3e7610b9a395d Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 2 Mar 2010 16:49:29 -0800 Subject: [PATCH 26/37] - Have Twitter bridge check for a global key and secret if it can't find one in the local config - Refuse to work at all if the consumer key and secret aren't set --- plugins/TwitterBridge/README | 17 +++- plugins/TwitterBridge/TwitterBridgePlugin.php | 98 +++++++++++++------ plugins/TwitterBridge/twitteroauthclient.php | 21 +++- 3 files changed, 101 insertions(+), 35 deletions(-) diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README index 72278b32e6..5117cf69ac 100644 --- a/plugins/TwitterBridge/README +++ b/plugins/TwitterBridge/README @@ -20,7 +20,7 @@ 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. +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: @@ -42,11 +42,26 @@ To enable the plugin, add the following to your config.php: ) ); +or just: + + addPlugin('TwitterBridge'); + +if you want to set the consumer key and secret from the Twitter bridge +administration panel. (The Twitter bridge wont work at all +unless you configure it with a consumer key and secret.) + * Note: The plugin will still push notices to Twitter for users who 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. +** For multi-site setups you can also set a global consumer key and and + secret. The Twitter bridge will fall back on the global key pair if + it can't find a local pair, e.g.: + + $config['twitter']['global_consumer_key'] = 'YOUR_CONSUMER_KEY' + $config['twitter']['global_consumer_secret'] = 'YOUR_CONSUMER_SECRET' + Administration panel -------------------- diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 6ce69d5e2b..bc702e745b 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -79,6 +79,30 @@ class TwitterBridgePlugin extends Plugin } } + /** + * Check to see if there is a consumer key and secret defined + * for Twitter integration. + * + * @return boolean result + */ + + static function hasKeys() + { + $key = common_config('twitter', 'consumer_key'); + $secret = common_config('twitter', 'consumer_secret'); + + if (empty($key) && empty($secret)) { + $key = common_config('twitter', 'global_consumer_key'); + $secret = common_config('twitter', 'global_consumer_secret'); + } + + if (!empty($key) && !empty($secret)) { + return true; + } + + return false; + } + /** * Add Twitter-related paths to the router table * @@ -91,14 +115,22 @@ class TwitterBridgePlugin extends Plugin function onRouterInitialized($m) { - $m->connect( - 'twitter/authorization', - array('action' => 'twitterauthorization') - ); - $m->connect('settings/twitter', array('action' => 'twittersettings')); - - if (common_config('twitter', 'signin')) { - $m->connect('main/twitterlogin', array('action' => 'twitterlogin')); + if (self::hasKeys()) { + $m->connect( + 'twitter/authorization', + array('action' => 'twitterauthorization') + ); + $m->connect( + 'settings/twitter', array( + 'action' => 'twittersettings' + ) + ); + if (common_config('twitter', 'signin')) { + $m->connect( + 'main/twitterlogin', + array('action' => 'twitterlogin') + ); + } } $m->connect('admin/twitter', array('action' => 'twitteradminpanel')); @@ -117,7 +149,7 @@ class TwitterBridgePlugin extends Plugin { $action_name = $action->trimmed('action'); - if (common_config('twitter', 'signin')) { + if (self::hasKeys() && common_config('twitter', 'signin')) { $action->menuItem( common_local_url('twitterlogin'), _m('Twitter'), @@ -138,15 +170,16 @@ class TwitterBridgePlugin extends Plugin */ function onEndConnectSettingsNav(&$action) { - $action_name = $action->trimmed('action'); - - $action->menuItem( - common_local_url('twittersettings'), - _m('Twitter'), - _m('Twitter integration options'), - $action_name === 'twittersettings' - ); + if (self::hasKeys()) { + $action_name = $action->trimmed('action'); + $action->menuItem( + common_local_url('twittersettings'), + _m('Twitter'), + _m('Twitter integration options'), + $action_name === 'twittersettings' + ); + } return true; } @@ -188,12 +221,12 @@ class TwitterBridgePlugin extends Plugin */ function onStartEnqueueNotice($notice, &$transports) { - // Avoid a possible loop - - if ($notice->source != 'twitter') { - array_push($transports, 'twitter'); + if (self::hasKeys()) { + // Avoid a possible loop + if ($notice->source != 'twitter') { + array_push($transports, 'twitter'); + } } - return true; } @@ -206,18 +239,19 @@ class TwitterBridgePlugin extends Plugin */ function onGetValidDaemons($daemons) { - array_push( - $daemons, - INSTALLDIR - . '/plugins/TwitterBridge/daemons/synctwitterfriends.php' - ); - - if (common_config('twitterimport', 'enabled')) { + if (self::hasKeys()) { array_push( $daemons, INSTALLDIR - . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php' + . '/plugins/TwitterBridge/daemons/synctwitterfriends.php' ); + if (common_config('twitterimport', 'enabled')) { + array_push( + $daemons, + INSTALLDIR + . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php' + ); + } } return true; @@ -232,7 +266,9 @@ class TwitterBridgePlugin extends Plugin */ function onEndInitializeQueueManager($manager) { - $manager->connect('twitter', 'TwitterQueueHandler'); + if (self::hasKeys()) { + $manager->connect('twitter', 'TwitterQueueHandler'); + } return true; } diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index ba45b533dc..93f6aadd1e 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -22,7 +22,7 @@ * @category Integration * @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/ */ @@ -61,8 +61,23 @@ class TwitterOAuthClient extends OAuthClient $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); + if (empty($consumer_key) && empty($consumer_secret)) { + $consumer_key = common_config( + 'twitter', + 'global_consumer_key' + ); + $consumer_secret = common_config( + 'twitter', + 'global_consumer_secret' + ); + } + + parent::__construct( + $consumer_key, + $consumer_secret, + $oauth_token, + $oauth_token_secret + ); } // XXX: the following two functions are to support the horrible hack From 08422dfa17a5c52d51f21087be0f1d8d602ed0af Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 2 Mar 2010 16:53:37 -0800 Subject: [PATCH 27/37] Remove double word from Twitter bridge README --- plugins/TwitterBridge/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README index 5117cf69ac..d0d34b7ef2 100644 --- a/plugins/TwitterBridge/README +++ b/plugins/TwitterBridge/README @@ -55,7 +55,7 @@ unless you configure it with a consumer key and secret.) password under an older version of StatusNet, but all new Twitter bridge connections will use OAuth. -** For multi-site setups you can also set a global consumer key and and +** For multi-site setups you can also set a global consumer key and secret. The Twitter bridge will fall back on the global key pair if it can't find a local pair, e.g.: From 32c08f53de83cbc512b0e69fc0994601f67d9582 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 3 Mar 2010 01:49:14 +0000 Subject: [PATCH 28/37] Show global key and secret, if defined, in Twitter bridge admin panel --- plugins/TwitterBridge/TwitterBridgePlugin.php | 16 +++---- plugins/TwitterBridge/twitteradminpanel.php | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index bc702e745b..1a0a69682a 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -88,15 +88,15 @@ class TwitterBridgePlugin extends Plugin static function hasKeys() { - $key = common_config('twitter', 'consumer_key'); - $secret = common_config('twitter', 'consumer_secret'); + $ckey = common_config('twitter', 'consumer_key'); + $csecret = common_config('twitter', 'consumer_secret'); - if (empty($key) && empty($secret)) { - $key = common_config('twitter', 'global_consumer_key'); - $secret = common_config('twitter', 'global_consumer_secret'); + if (empty($ckey) && empty($csecret)) { + $ckey = common_config('twitter', 'global_consumer_key'); + $csecret = common_config('twitter', 'global_consumer_secret'); } - if (!empty($key) && !empty($secret)) { + if (!empty($ckey) && !empty($csecret)) { return true; } @@ -115,6 +115,8 @@ class TwitterBridgePlugin extends Plugin function onRouterInitialized($m) { + $m->connect('admin/twitter', array('action' => 'twitteradminpanel')); + if (self::hasKeys()) { $m->connect( 'twitter/authorization', @@ -133,8 +135,6 @@ class TwitterBridgePlugin extends Plugin } } - $m->connect('admin/twitter', array('action' => 'twitteradminpanel')); - return true; } diff --git a/plugins/TwitterBridge/twitteradminpanel.php b/plugins/TwitterBridge/twitteradminpanel.php index b22e6d99fe..0ed53bc05b 100644 --- a/plugins/TwitterBridge/twitteradminpanel.php +++ b/plugins/TwitterBridge/twitteradminpanel.php @@ -225,6 +225,49 @@ class TwitterAdminPanelForm extends AdminForm ); $this->unli(); + $globalConsumerKey = common_config('twitter', 'global_consumer_key'); + $globalConsumerSec = common_config('twitter', 'global_consumer_secret'); + + if (!empty($globalConsumerKey)) { + $this->li(); + $this->out->element( + 'label', + array('for' => 'global_consumer_key'), + '' + ); + $this->out->element( + 'input', + array( + 'name' => 'global_consumer_key', + 'type' => 'text', + 'id' => 'global_consumer_key', + 'value' => $globalConsumerKey, + 'disabled' => 'true' + ) + ); + $this->out->element('p', 'form_guide', _('Global consumer key')); + $this->unli(); + + $this->li(); + $this->out->element( + 'label', + array('for' => 'global_consumer_secret'), + '' + ); + $this->out->element( + 'input', + array( + 'name' => 'global_consumer_secret', + 'type' => 'text', + 'id' => 'global_consumer_secret', + 'value' => $globalConsumerSec, + 'disabled' => 'true' + ) + ); + $this->out->element('p', 'form_guide', _('Global consumer secret')); + $this->unli(); + } + $this->li(); $this->input( 'source', From f7ba5566bc1e2bad262b948c92d8167e27e147bc Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 2 Mar 2010 18:27:37 -0800 Subject: [PATCH 29/37] Make Facebook plugin look for API key and secret before doing anything --- plugins/Facebook/FacebookPlugin.php | 161 +++++++++++++++++----------- 1 file changed, 99 insertions(+), 62 deletions(-) diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index 014d0d1970..90ed7351f3 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -79,6 +79,25 @@ class FacebookPlugin extends Plugin } } + /** + * Check to see if there is an API key and secret defined + * for Facebook integration. + * + * @return boolean result + */ + + static function hasKeys() + { + $apiKey = common_config('facebook', 'apikey'); + $apiSecret = common_config('facebook', 'secret'); + + if (!empty($apiKey) && !empty($apiSecret)) { + return true; + } + + return false; + } + /** * Add Facebook app actions to the router table * @@ -91,23 +110,26 @@ class FacebookPlugin extends Plugin function onStartInitializeRouter($m) { - - // Facebook App stuff - - $m->connect('facebook/app', array('action' => 'facebookhome')); - $m->connect('facebook/app/index.php', array('action' => 'facebookhome')); - $m->connect('facebook/app/settings.php', - 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 + if (self::hasKeys()) { - $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); - $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); - $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); - $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); + // Facebook App stuff + + $m->connect('facebook/app', array('action' => 'facebookhome')); + $m->connect('facebook/app/index.php', array('action' => 'facebookhome')); + $m->connect('facebook/app/settings.php', + array('action' => 'facebooksettings')); + $m->connect('facebook/app/invite.php', array('action' => 'facebookinvite')); + $m->connect('facebook/app/remove', array('action' => 'facebookremove')); + + // Facebook Connect stuff + + $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); + $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); + $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); + $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); + } return true; } @@ -338,6 +360,9 @@ class FacebookPlugin extends Plugin function reqFbScripts($action) { + if (!self::hasKeys()) { + return false; + } // If you're logged in w/FB Connect, you always need the FB stuff @@ -410,42 +435,45 @@ class FacebookPlugin extends Plugin function onStartPrimaryNav($action) { - $user = common_current_user(); + if (self::hasKeys()) { - $connect = 'FBConnectSettings'; - if (common_config('xmpp', 'enabled')) { - $connect = 'imsettings'; - } else if (common_config('sms', 'enabled')) { - $connect = 'smssettings'; - } + $user = common_current_user(); - if (!empty($user)) { + $connect = 'FBConnectSettings'; + if (common_config('xmpp', 'enabled')) { + $connect = 'imsettings'; + } else if (common_config('sms', 'enabled')) { + $connect = 'smssettings'; + } - $fbuid = $this->loggedIn(); + if (!empty($user)) { - if (!empty($fbuid)) { + $fbuid = $this->loggedIn(); - /* Default FB silhouette pic for FB users who haven't - uploaded a profile pic yet. */ + if (!empty($fbuid)) { - $silhouetteUrl = - 'http://static.ak.fbcdn.net/pics/q_silhouette.gif'; + /* Default FB silhouette pic for FB users who haven't + uploaded a profile pic yet. */ - $url = $this->getProfilePicURL($fbuid); + $silhouetteUrl = + 'http://static.ak.fbcdn.net/pics/q_silhouette.gif'; - $action->elementStart('li', array('id' => 'nav_fb')); + $url = $this->getProfilePicURL($fbuid); - $action->element('img', array('id' => 'fbc_profile-pic', - 'src' => (!empty($url)) ? $url : $silhouetteUrl, - 'alt' => 'Facebook Connect User', - 'width' => '16'), ''); + $action->elementStart('li', array('id' => 'nav_fb')); - $iconurl = common_path('plugins/Facebook/fbfavicon.ico'); - $action->element('img', array('id' => 'fb_favicon', - 'src' => $iconurl)); + $action->element('img', array('id' => 'fbc_profile-pic', + 'src' => (!empty($url)) ? $url : $silhouetteUrl, + 'alt' => 'Facebook Connect User', + 'width' => '16'), ''); - $action->elementEnd('li'); + $iconurl = common_path('plugins/Facebook/fbfavicon.ico'); + $action->element('img', array('id' => 'fb_favicon', + 'src' => $iconurl)); + $action->elementEnd('li'); + + } } } @@ -462,14 +490,15 @@ class FacebookPlugin extends Plugin function onEndLoginGroupNav(&$action) { + if (self::hasKeys()) { - $action_name = $action->trimmed('action'); - - $action->menuItem(common_local_url('FBConnectLogin'), - _m('Facebook'), - _m('Login or register using Facebook'), - 'FBConnectLogin' === $action_name); + $action_name = $action->trimmed('action'); + $action->menuItem(common_local_url('FBConnectLogin'), + _m('Facebook'), + _m('Login or register using Facebook'), + 'FBConnectLogin' === $action_name); + } return true; } @@ -483,13 +512,15 @@ class FacebookPlugin extends Plugin function onEndConnectSettingsNav(&$action) { - $action_name = $action->trimmed('action'); + if (self::hasKeys()) { - $action->menuItem(common_local_url('FBConnectSettings'), - _m('Facebook'), - _m('Facebook Connect Settings'), - $action_name === 'FBConnectSettings'); + $action_name = $action->trimmed('action'); + $action->menuItem(common_local_url('FBConnectSettings'), + _m('Facebook'), + _m('Facebook Connect Settings'), + $action_name === 'FBConnectSettings'); + } return true; } @@ -503,20 +534,22 @@ class FacebookPlugin extends Plugin function onStartLogout($action) { - $action->logout(); - $fbuid = $this->loggedIn(); + if (self::hasKeys()) { - if (!empty($fbuid)) { - try { - $facebook = getFacebook(); - $facebook->expire_session(); - } catch (Exception $e) { - common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . - 'Could\'t logout of Facebook: ' . - $e->getMessage()); + $action->logout(); + $fbuid = $this->loggedIn(); + + if (!empty($fbuid)) { + try { + $facebook = getFacebook(); + $facebook->expire_session(); + } catch (Exception $e) { + common_log(LOG_WARNING, 'Facebook Connect Plugin - ' . + 'Could\'t logout of Facebook: ' . + $e->getMessage()); + } } } - return true; } @@ -562,7 +595,9 @@ class FacebookPlugin extends Plugin function onStartEnqueueNotice($notice, &$transports) { - array_push($transports, 'facebook'); + if (self::hasKeys()) { + array_push($transports, 'facebook'); + } return true; } @@ -575,7 +610,9 @@ class FacebookPlugin extends Plugin */ function onEndInitializeQueueManager($manager) { - $manager->connect('facebook', 'FacebookQueueHandler'); + if (self::hasKeys()) { + $manager->connect('facebook', 'FacebookQueueHandler'); + } return true; } From 1a4652b1ade6ea4de842835a7d2a011845a1cd62 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 3 Mar 2010 10:32:54 -0500 Subject: [PATCH 30/37] Changed label text for remote subscription to something similar. Given that this button will be used within context of subscriptions, 'New' works along with the '+' icon. --- plugins/OStatus/OStatusPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 7b6d22acf3..b5e6867c87 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -727,7 +727,7 @@ class OStatusPlugin extends Plugin 'class' => 'entity_subscribe')); $action->element('a', array('href' => common_local_url('ostatussub'), 'class' => 'entity_remote_subscribe') - , _m('Subscribe to remote user')); + , _m('New')); $action->elementEnd('p'); $action->elementEnd('div'); } From 06fb1124f5cd328bc0b3028a0cc0499b1c916653 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 3 Mar 2010 11:14:39 -0500 Subject: [PATCH 31/37] Added event hooks for start and end of subscriptions mini list --- EVENTS.txt | 6 ++++++ lib/profileaction.php | 35 +++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index bb4936b354..a40855134e 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -776,6 +776,12 @@ StartShowAllContent: before showing the all (you and friends) content EndShowAllContent: after showing the all (you and friends) content - $action: the current action +StartShowSubscriptionsMiniList: at the start of subscriptions mini list +- $action: the current action + +EndShowSubscriptionsMiniList: at the end of subscriptions mini list +- $action: the current action + StartDeleteUserForm: starting the data in the form for deleting a user - $action: action being shown - $user: user being deleted diff --git a/lib/profileaction.php b/lib/profileaction.php index 2d4d232655..2bda8b07c9 100644 --- a/lib/profileaction.php +++ b/lib/profileaction.php @@ -106,27 +106,30 @@ class ProfileAction extends OwnerDesignAction $this->elementStart('div', array('id' => 'entity_subscriptions', 'class' => 'section')); - $this->element('h2', null, _('Subscriptions')); + if (Event::handle('StartShowSubscriptionsMiniList', array($this))) { + $this->element('h2', null, _('Subscriptions')); - $cnt = 0; + $cnt = 0; - if (!empty($profile)) { - $pml = new ProfileMiniList($profile, $this); - $cnt = $pml->show(); - if ($cnt == 0) { - $this->element('p', null, _('(None)')); + if (!empty($profile)) { + $pml = new ProfileMiniList($profile, $this); + $cnt = $pml->show(); + if ($cnt == 0) { + $this->element('p', null, _('(None)')); + } } - } - if ($cnt > PROFILES_PER_MINILIST) { - $this->elementStart('p'); - $this->element('a', array('href' => common_local_url('subscriptions', - array('nickname' => $this->profile->nickname)), - 'class' => 'more'), - _('All subscriptions')); - $this->elementEnd('p'); - } + if ($cnt > PROFILES_PER_MINILIST) { + $this->elementStart('p'); + $this->element('a', array('href' => common_local_url('subscriptions', + array('nickname' => $this->profile->nickname)), + 'class' => 'more'), + _('All subscriptions')); + $this->elementEnd('p'); + } + Event::handle('EndShowSubscriptionsMiniList', array($this)); + } $this->elementEnd('div'); } From ea10805e3f68f5781321e7c93144242478081a1f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 3 Mar 2010 11:22:21 -0500 Subject: [PATCH 32/37] Moved the remote subscription button to subscription mini list --- plugins/OStatus/OStatusPlugin.php | 2 +- plugins/OStatus/theme/base/css/ostatus.css | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index b5e6867c87..eb777c6b2f 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -711,7 +711,7 @@ class OStatusPlugin extends Plugin return true; } - function onStartShowAllContent($action) + function onEndShowSubscriptionsMiniList($action) { $this->showEntityRemoteSubscribe($action); diff --git a/plugins/OStatus/theme/base/css/ostatus.css b/plugins/OStatus/theme/base/css/ostatus.css index 13e30ef5df..40cdfcef13 100644 --- a/plugins/OStatus/theme/base/css/ostatus.css +++ b/plugins/OStatus/theme/base/css/ostatus.css @@ -41,8 +41,22 @@ min-width:96px; #entity_remote_subscribe { padding:0; float:right; +position:relative; } -#all #entity_remote_subscribe { -margin-top:-52px; +.section .entity_actions { +margin-bottom:0; +} + +.section #entity_remote_subscribe .entity_remote_subscribe { +border-color:#AAAAAA; +} + +.section #entity_remote_subscribe .dialogbox { +width:405px; +} + + +.aside #entity_subscriptions .more { +float:left; } From b65ee23e8293c301f8b142320eabff6c4acdfcfe Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 3 Mar 2010 12:01:38 -0500 Subject: [PATCH 33/37] Added event hooks for group subscribe --- EVENTS.txt | 8 ++++++++ actions/showgroup.php | 24 +++++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index a40855134e..2c3863f228 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -363,6 +363,14 @@ EndProfileRemoteSubscribe: After showing the link to remote subscription - $userprofile: UserProfile widget - &$profile: the profile being shown +StartGroupSubscribe: Before showing the link to remote subscription +- $action: the current action +- $group: the group being shown + +EndGroupSubscribe: After showing the link to remote subscription +- $action: the current action +- $group: the group being shown + StartProfilePageProfileSection: Starting to show the section of the profile page with the actual profile data; hook to prevent showing the profile (e.g.) diff --git a/actions/showgroup.php b/actions/showgroup.php index 4e1fcb6c7b..a1dc3865bd 100644 --- a/actions/showgroup.php +++ b/actions/showgroup.php @@ -300,20 +300,22 @@ class ShowgroupAction extends GroupDesignAction $this->elementStart('div', 'entity_actions'); $this->element('h2', null, _('Group actions')); $this->elementStart('ul'); - $this->elementStart('li', 'entity_subscribe'); - $cur = common_current_user(); - if ($cur) { - if ($cur->isMember($this->group)) { - $lf = new LeaveForm($this, $this->group); - $lf->show(); - } else if (!Group_block::isBlocked($this->group, $cur->getProfile())) { - $jf = new JoinForm($this, $this->group); - $jf->show(); + if (Event::handle('StartGroupSubscribe', array($this, $this->group))) { + $this->elementStart('li', 'entity_subscribe'); + $cur = common_current_user(); + if ($cur) { + if ($cur->isMember($this->group)) { + $lf = new LeaveForm($this, $this->group); + $lf->show(); + } else if (!Group_block::isBlocked($this->group, $cur->getProfile())) { + $jf = new JoinForm($this, $this->group); + $jf->show(); + } } + $this->elementEnd('li'); + Event::handle('EndGroupSubscribe', array($this, $this->group)); } - $this->elementEnd('li'); - $this->elementEnd('ul'); $this->elementEnd('div'); } From 11750e832f994013b2fbce860bd24d24f49a14db Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 3 Mar 2010 12:02:10 -0500 Subject: [PATCH 34/37] Added remote join action for group profile --- plugins/OStatus/OStatusPlugin.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index eb777c6b2f..014fb0b380 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -210,6 +210,26 @@ class OStatusPlugin extends Plugin return false; } + function onStartGroupSubscribe($output, $group) + { + $cur = common_current_user(); + + if (empty($cur)) { + // Add an OStatus subscribe + $output->elementStart('li', 'entity_subscribe'); + $url = common_local_url('ostatusinit', + array('nickname' => $group->nickname)); + $output->element('a', array('href' => $url, + 'class' => 'entity_remote_subscribe'), + _m('Join')); + + $output->elementEnd('li'); + } + + return false; + } + + /** * Check if we've got remote replies to send via Salmon. * From ffa1931c9dafea385e8f30c53ea079e2425a0786 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 3 Mar 2010 09:31:14 -0800 Subject: [PATCH 35/37] Avoid warning/notice spew in XRD parser. Not all DOM nodes are elements. --- plugins/OStatus/lib/xrd.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php index 85df26c54c..f00e1f8096 100644 --- a/plugins/OStatus/lib/xrd.php +++ b/plugins/OStatus/lib/xrd.php @@ -149,9 +149,11 @@ class XRD $link['href'] = $element->getAttribute('href'); $link['template'] = $element->getAttribute('template'); foreach ($element->childNodes as $node) { - switch($node->tagName) { - case 'Title': - $link['title'][] = $node->nodeValue; + if ($node instanceof DOMElement) { + switch($node->tagName) { + case 'Title': + $link['title'][] = $node->nodeValue; + } } } From 1e63fda6695c8ab06f2cd8181de26f03f7f7a5d4 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 3 Mar 2010 09:32:25 -0800 Subject: [PATCH 36/37] Clean up OStatus mentions finding; separate regexes keeps the code paths a bit clearer. Also switched to hitting HTTP profile first; as the common case it'll be faster. --- plugins/OStatus/OStatusPlugin.php | 86 +++++++++++++++---------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 7b6d22acf3..f435d6283f 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -233,70 +233,70 @@ class OStatusPlugin extends Plugin function onEndFindMentions($sender, $text, &$mentions) { - 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', + $matches = array(); + + // Webfinger matches: @user@example.com + if (preg_match_all('!(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)!', $text, $wmatches, - PREG_OFFSET_CAPTURE); - - foreach ($wmatches[1] as $wmatch) { - $target = $wmatch[0]; - $oprofile = null; - - if (strpos($target, '/') === false) { - $this->log(LOG_INFO, "Checking Webfinger for address '$target'"); + PREG_OFFSET_CAPTURE)) { + foreach ($wmatches[1] as $wmatch) { + list($target, $pos) = $wmatch; + $this->log(LOG_INFO, "Checking webfinger '$target'"); try { $oprofile = Ostatus_profile::ensureWebfinger($target); + if ($oprofile && !$oprofile->isGroup()) { + $profile = $oprofile->localProfile(); + $matches[$pos] = array('mentioned' => array($profile), + 'text' => $target, + 'position' => $pos, + 'url' => $profile->profileurl); + } } catch (Exception $e) { $this->log(LOG_ERR, "Webfinger check failed: " . $e->getMessage()); } - } else { - $schemes = array('https', 'http'); + } + } + + // Profile matches: @example.com/mublog/user + if (preg_match_all('!(?:^|\s+)@((?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+(?:/\w+)+)!', + $text, + $wmatches, + PREG_OFFSET_CAPTURE)) { + foreach ($wmatches[1] as $wmatch) { + list($target, $pos) = $wmatch; + $schemes = array('http', 'https'); foreach ($schemes as $scheme) { $url = "$scheme://$target"; $this->log(LOG_INFO, "Checking profile address '$url'"); try { $oprofile = Ostatus_profile::ensureProfile($url); - if ($oprofile) { - continue; + if ($oprofile && !$oprofile->isGroup()) { + $profile = $oprofile->localProfile(); + $matches[$pos] = array('mentioned' => array($profile), + 'text' => $target, + 'position' => $pos, + 'url' => $profile->profileurl); + break; } } 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 '$target'"); - } else { - - $this->log(LOG_INFO, "Ostatus_profile found for address '$target'"); - - if ($oprofile->isGroup()) { - continue; - } - $profile = $oprofile->localProfile(); - - $pos = $wmatch[1]; - foreach ($mentions as $i => $other) { - // If we share a common prefix with a local user, override it! - if ($other['position'] == $pos) { - unset($mentions[$i]); - } - } - $mentions[] = array('mentioned' => array($profile), - 'text' => $target, - 'position' => $pos, - 'url' => $profile->profileurl); + foreach ($mentions as $i => $other) { + // If we share a common prefix with a local user, override it! + $pos = $other['position']; + if (isset($matches[$pos])) { + $mentions[$i] = $matches[$pos]; + unset($matches[$pos]); } } + foreach ($matches as $mention) { + $mentions[] = $mention; + } return true; } From f3cea2430497e751bc7776fe3abf0603e2b55f8b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 3 Mar 2010 09:36:26 -0800 Subject: [PATCH 37/37] Fix for hcard parsing: typo caused notice spew accessing unset array index --- plugins/OStatus/classes/Ostatus_profile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 059c19e7c2..7ab031aa59 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1500,7 +1500,7 @@ class Ostatus_profile extends Memcached_DataObject if (array_key_exists('url', $hcard)) { if (is_string($hcard['url'])) { $hints['homepage'] = $hcard['url']; - } else if (is_array($hcard['adr'])) { + } else if (is_array($hcard['url'])) { // HACK get the last one; that's how our hcards look $hints['homepage'] = $hcard['url'][count($hcard['url'])-1]; }