summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvan Prodromou <evan@status.net>2010-01-06 23:23:30 -0800
committerEvan Prodromou <evan@status.net>2010-01-06 23:23:30 -0800
commita55939f3b1540cf86b6edcf4f0c8ff79fb5f6a20 (patch)
treeb8ed9dc90ba728e36ca3ca77bed71f45a77d6dd4
parente1c7851a067d4d8201126816884b9992720010f5 (diff)
parent5d13126c88a8143a1628cc52c2dae4581f134273 (diff)
Merge branch '0.9.x' of git@gitorious.org:statusnet/mainline into 0.9.x
-rw-r--r--actions/apigroupleave.php8
-rw-r--r--actions/leavegroup.php4
-rw-r--r--actions/twitapisearchatom.php9
-rw-r--r--classes/Avatar.php2
-rw-r--r--classes/Config.php2
-rw-r--r--classes/Fave.php2
-rw-r--r--classes/File_to_post.php2
-rw-r--r--classes/Group_block.php2
-rw-r--r--classes/Group_inbox.php2
-rw-r--r--classes/Group_member.php2
-rw-r--r--classes/Memcached_DataObject.php17
-rw-r--r--classes/Notice_inbox.php2
-rw-r--r--classes/Notice_tag.php2
-rw-r--r--classes/Profile.php1
-rw-r--r--classes/Profile_role.php2
-rw-r--r--classes/Queue_item.php2
-rw-r--r--classes/Subscription.php2
-rw-r--r--doc-src/sms11
-rw-r--r--lib/command.php74
-rw-r--r--lib/htmloutputter.php2
-rw-r--r--lib/jsonsearchresultslist.php10
-rw-r--r--lib/schema.php4
-rw-r--r--plugins/LdapAuthorization/LdapAuthorizationPlugin.php1
-rw-r--r--plugins/Minify/MinifyPlugin.php2
-rw-r--r--plugins/OpenID/User_openid_trustroot.php2
-rw-r--r--plugins/RSSCloud/LoggingAggregator.php140
-rw-r--r--plugins/RSSCloud/README54
-rw-r--r--plugins/RSSCloud/RSSCloudNotifier.php240
-rw-r--r--plugins/RSSCloud/RSSCloudPlugin.php279
-rwxr-xr-xplugins/RSSCloud/RSSCloudQueueHandler.php78
-rw-r--r--plugins/RSSCloud/RSSCloudRequestNotify.php347
-rw-r--r--plugins/RSSCloud/RSSCloudSubscription.php79
-rw-r--r--plugins/Recaptcha/RecaptchaPlugin.php3
-rw-r--r--plugins/UserFlag/UserFlagPlugin.php51
-rw-r--r--plugins/UserFlag/User_flag_profile.php2
-rwxr-xr-xscripts/console.php2
-rwxr-xr-xscripts/stopdaemons.sh2
-rw-r--r--theme/cloudy/css/display.css16
38 files changed, 1364 insertions, 98 deletions
diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php
index 514a3a557..5627bfc14 100644
--- a/actions/apigroupleave.php
+++ b/actions/apigroupleave.php
@@ -108,7 +108,7 @@ class ApiGroupLeaveAction extends ApiAuthAction
$member = new Group_member();
$member->group_id = $this->group->id;
- $member->profile_id = $this->auth->id;
+ $member->profile_id = $this->auth_user->id;
if (!$member->find(true)) {
$this->serverError(_('You are not a member of this group.'));
@@ -118,12 +118,12 @@ class ApiGroupLeaveAction extends ApiAuthAction
$result = $member->delete();
if (!$result) {
- common_log_db_error($member, 'INSERT', __FILE__);
+ common_log_db_error($member, 'DELETE', __FILE__);
$this->serverError(
sprintf(
- _('Could not remove user %s to group %s.'),
+ _('Could not remove user %s from group %s.'),
$this->user->nickname,
- $this->$group->nickname
+ $this->group->nickname
)
);
return;
diff --git a/actions/leavegroup.php b/actions/leavegroup.php
index 08fce1509..90c85e1a4 100644
--- a/actions/leavegroup.php
+++ b/actions/leavegroup.php
@@ -123,8 +123,8 @@ class LeavegroupAction extends Action
$result = $member->delete();
if (!$result) {
- common_log_db_error($member, 'INSERT', __FILE__);
- $this->serverError(sprintf(_('Could not remove user %s to group %s'),
+ common_log_db_error($member, 'DELETE', __FILE__);
+ $this->serverError(sprintf(_('Could not remove user %s from group %s'),
$cur->nickname, $this->group->nickname));
}
diff --git a/actions/twitapisearchatom.php b/actions/twitapisearchatom.php
index 1cb8d7efe..baed2a0c7 100644
--- a/actions/twitapisearchatom.php
+++ b/actions/twitapisearchatom.php
@@ -208,7 +208,14 @@ class TwitapisearchatomAction extends ApiAction
$this->showFeed();
foreach ($notices as $n) {
- $this->showEntry($n);
+
+ $profile = $n->getProfile();
+
+ // Don't show notices from deleted users
+
+ if (!empty($profile)) {
+ $this->showEntry($n);
+ }
}
$this->endAtom();
diff --git a/classes/Avatar.php b/classes/Avatar.php
index 8d6424e8b..91bde0f04 100644
--- a/classes/Avatar.php
+++ b/classes/Avatar.php
@@ -37,7 +37,7 @@ class Avatar extends Memcached_DataObject
}
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Avatar', $kv);
}
diff --git a/classes/Config.php b/classes/Config.php
index 6d914ca1f..43b99587f 100644
--- a/classes/Config.php
+++ b/classes/Config.php
@@ -120,7 +120,7 @@ class Config extends Memcached_DataObject
return $result;
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Config', $kv);
}
diff --git a/classes/Fave.php b/classes/Fave.php
index 11e876ff1..8113c8e16 100644
--- a/classes/Fave.php
+++ b/classes/Fave.php
@@ -32,7 +32,7 @@ class Fave extends Memcached_DataObject
return $fave;
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Fave', $kv);
}
diff --git a/classes/File_to_post.php b/classes/File_to_post.php
index e3db91b20..72a42b088 100644
--- a/classes/File_to_post.php
+++ b/classes/File_to_post.php
@@ -62,7 +62,7 @@ class File_to_post extends Memcached_DataObject
}
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('File_to_post', $kv);
}
diff --git a/classes/Group_block.php b/classes/Group_block.php
index de2cf5f6e..9f4d59295 100644
--- a/classes/Group_block.php
+++ b/classes/Group_block.php
@@ -40,7 +40,7 @@ class Group_block extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Group_block', $kv);
}
diff --git a/classes/Group_inbox.php b/classes/Group_inbox.php
index 1af7439f7..2a0787e38 100644
--- a/classes/Group_inbox.php
+++ b/classes/Group_inbox.php
@@ -20,7 +20,7 @@ class Group_inbox extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Group_inbox', $kv);
}
diff --git a/classes/Group_member.php b/classes/Group_member.php
index 3c23a991f..069b2c7a1 100644
--- a/classes/Group_member.php
+++ b/classes/Group_member.php
@@ -21,7 +21,7 @@ class Group_member extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Group_member', $kv);
}
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php
index 15ca34821..4e3cc5678 100644
--- a/classes/Memcached_DataObject.php
+++ b/classes/Memcached_DataObject.php
@@ -90,17 +90,16 @@ class Memcached_DataObject extends DB_DataObject
unset($i);
}
$i = Memcached_DataObject::getcached($cls, $k, $v);
- if ($i !== false) { // false == cache miss
- return $i;
- } else {
+ if ($i === false) { // false == cache miss
$i = DB_DataObject::factory($cls);
if (empty($i)) {
- return false;
+ $i = false;
+ return $i;
}
$result = $i->get($k, $v);
if ($result) {
+ // Hit!
$i->encache();
- return $i;
} else {
// save the fact that no such row exists
$c = self::memcache();
@@ -108,12 +107,16 @@ class Memcached_DataObject extends DB_DataObject
$ck = self::cachekey($cls, $k, $v);
$c->set($ck, null);
}
- return false;
+ $i = false;
}
}
+ return $i;
}
- function &pkeyGet($cls, $kv)
+ /**
+ * @fixme Should this return false on lookup fail to match staticGet?
+ */
+ function pkeyGet($cls, $kv)
{
$i = Memcached_DataObject::multicache($cls, $kv);
if ($i !== false) { // false == cache miss
diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php
index d3ddad656..e350e6e2f 100644
--- a/classes/Notice_inbox.php
+++ b/classes/Notice_inbox.php
@@ -101,7 +101,7 @@ class Notice_inbox extends Memcached_DataObject
return $ids;
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Notice_inbox', $kv);
}
diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php
index 02740280f..79231f0b0 100644
--- a/classes/Notice_tag.php
+++ b/classes/Notice_tag.php
@@ -96,7 +96,7 @@ class Notice_tag extends Memcached_DataObject
}
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Notice_tag', $kv);
}
diff --git a/classes/Profile.php b/classes/Profile.php
index 03196447b..25d908dbf 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -504,6 +504,7 @@ class Profile extends Memcached_DataObject
'Reply',
'Group_member',
);
+ Event::handle('ProfileDeleteRelated', array($this, &$related));
foreach ($related as $cls) {
$inst = new $cls();
diff --git a/classes/Profile_role.php b/classes/Profile_role.php
index afa7fb74e..74aca3730 100644
--- a/classes/Profile_role.php
+++ b/classes/Profile_role.php
@@ -43,7 +43,7 @@ class Profile_role extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Profile_role', $kv);
}
diff --git a/classes/Queue_item.php b/classes/Queue_item.php
index 295c321b5..9c673540d 100644
--- a/classes/Queue_item.php
+++ b/classes/Queue_item.php
@@ -55,7 +55,7 @@ class Queue_item extends Memcached_DataObject
return null;
}
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Queue_item', $kv);
}
diff --git a/classes/Subscription.php b/classes/Subscription.php
index fedfd5f19..faf1331cd 100644
--- a/classes/Subscription.php
+++ b/classes/Subscription.php
@@ -46,7 +46,7 @@ class Subscription extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Subscription', $kv);
}
diff --git a/doc-src/sms b/doc-src/sms
index 1a3064318..2c20921c3 100644
--- a/doc-src/sms
+++ b/doc-src/sms
@@ -56,13 +56,4 @@ You can use the following commands with %%site.name%%.
* sub &lt;nickname&gt; - same as 'follow'
* unsub &lt;nickname&gt; - same as 'leave'
* last &lt;nickname&gt; - same as 'get'
-* on &lt;nickname&gt; - not yet implemented.
-* off &lt;nickname&gt; - not yet implemented.
-* nudge &lt;nickname&gt; - not yet implemented.
-* invite &lt;phone number&gt; - not yet implemented.
-* track &lt;word&gt; - not yet implemented.
-* untrack &lt;word&gt; - not yet implemented.
-* track off - not yet implemented.
-* untrack all - not yet implemented.
-* tracks - not yet implemented.
-* tracking - not yet implemented.
+* nudge &lt;nickname&gt; - remind a user to update.
diff --git a/lib/command.php b/lib/command.php
index 67140c348..ad2e0bb97 100644
--- a/lib/command.php
+++ b/lib/command.php
@@ -742,42 +742,42 @@ class HelpCommand extends Command
function execute($channel)
{
$channel->output($this->user,
- _("Commands:\n".
- "on - turn on notifications\n".
- "off - turn off notifications\n".
- "help - show this help\n".
- "follow <nickname> - subscribe to user\n".
- "groups - lists the groups you have joined\n".
- "subscriptions - list the people you follow\n".
- "subscribers - list the people that follow you\n".
- "leave <nickname> - unsubscribe from user\n".
- "d <nickname> <text> - direct message to user\n".
- "get <nickname> - get last notice from user\n".
- "whois <nickname> - get profile info on user\n".
- "fav <nickname> - add user's last notice as a 'fave'\n".
- "fav #<notice_id> - add notice with the given id as a 'fave'\n".
- "repeat #<notice_id> - repeat a notice with a given id\n".
- "repeat <nickname> - repeat the last notice from user\n".
- "reply #<notice_id> - reply to notice with a given id\n".
- "reply <nickname> - reply to the last notice from user\n".
- "join <group> - join group\n".
- "login - Get a link to login to the web interface\n".
- "drop <group> - leave group\n".
- "stats - get your stats\n".
- "stop - same as 'off'\n".
- "quit - same as 'off'\n".
- "sub <nickname> - same as 'follow'\n".
- "unsub <nickname> - same as 'leave'\n".
- "last <nickname> - same as 'get'\n".
- "on <nickname> - not yet implemented.\n".
- "off <nickname> - not yet implemented.\n".
- "nudge <nickname> - remind a user to update.\n".
- "invite <phone number> - not yet implemented.\n".
- "track <word> - not yet implemented.\n".
- "untrack <word> - not yet implemented.\n".
- "track off - not yet implemented.\n".
- "untrack all - not yet implemented.\n".
- "tracks - not yet implemented.\n".
- "tracking - not yet implemented.\n"));
+ _("Commands:")."\n".
+ _("on - turn on notifications")."\n".
+ _("off - turn off notifications")."\n".
+ _("help - show this help")."\n".
+ _("follow <nickname> - subscribe to user")."\n".
+ _("groups - lists the groups you have joined")."\n".
+ _("subscriptions - list the people you follow")."\n".
+ _("subscribers - list the people that follow you")."\n".
+ _("leave <nickname> - unsubscribe from user")."\n".
+ _("d <nickname> <text> - direct message to user")."\n".
+ _("get <nickname> - get last notice from user")."\n".
+ _("whois <nickname> - get profile info on user")."\n".
+ _("fav <nickname> - add user's last notice as a 'fave'")."\n".
+ _("fav #<notice_id> - add notice with the given id as a 'fave'")."\n".
+ _("repeat #<notice_id> - repeat a notice with a given id")."\n".
+ _("repeat <nickname> - repeat the last notice from user")."\n".
+ _("reply #<notice_id> - reply to notice with a given id")."\n".
+ _("reply <nickname> - reply to the last notice from user")."\n".
+ _("join <group> - join group")."\n".
+ #_("login - Get a link to login to the web interface")."\n".
+ _("drop <group> - leave group")."\n".
+ _("stats - get your stats")."\n".
+ _("stop - same as 'off'")."\n".
+ _("quit - same as 'off'")."\n".
+ _("sub <nickname> - same as 'follow'")."\n".
+ _("unsub <nickname> - same as 'leave'")."\n".
+ _("last <nickname> - same as 'get'")."\n".
+ #_("on <nickname> - not yet implemented.")."\n".
+ #_("off <nickname> - not yet implemented.")."\n".
+ _("nudge <nickname> - remind a user to update.")."\n");
+ #_("invite <phone number> - not yet implemented.")."\n".
+ #_("track <word> - not yet implemented.")."\n".
+ #_("untrack <word> - not yet implemented.")."\n".
+ #_("track off - not yet implemented.")."\n".
+ #_("untrack all - not yet implemented.")."\n".
+ #_("tracks - not yet implemented.")."\n".
+ #_("tracking - not yet implemented.")."\n"
}
}
diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php
index 2091c6e2c..31660ce95 100644
--- a/lib/htmloutputter.php
+++ b/lib/htmloutputter.php
@@ -352,7 +352,7 @@ class HTMLOutputter extends XMLOutputter
{
if(Event::handle('StartScriptElement', array($this,&$src,&$type))) {
$url = parse_url($src);
- if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
+ if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment']))
{
$src = common_path($src) . '?version=' . STATUSNET_VERSION;
}
diff --git a/lib/jsonsearchresultslist.php b/lib/jsonsearchresultslist.php
index 569bfa873..0d72ddf7a 100644
--- a/lib/jsonsearchresultslist.php
+++ b/lib/jsonsearchresultslist.php
@@ -105,8 +105,14 @@ class JSONSearchResultsList
break;
}
- $item = new ResultItem($this->notice);
- array_push($this->results, $item);
+ $profile = $this->notice->getProfile();
+
+ // Don't show notices from deleted users
+
+ if (!empty($profile)) {
+ $item = new ResultItem($this->notice);
+ array_push($this->results, $item);
+ }
}
$time_end = microtime(true);
diff --git a/lib/schema.php b/lib/schema.php
index 6fe442d56..a7f64ebed 100644
--- a/lib/schema.php
+++ b/lib/schema.php
@@ -528,6 +528,10 @@ class Schema
$sql .= " auto_increment ";
}
+ if (!empty($cd->extra)) {
+ $sql .= "{$cd->extra} ";
+ }
+
return $sql;
}
}
diff --git a/plugins/LdapAuthorization/LdapAuthorizationPlugin.php b/plugins/LdapAuthorization/LdapAuthorizationPlugin.php
index 7673e61ef..e5e22c0dd 100644
--- a/plugins/LdapAuthorization/LdapAuthorizationPlugin.php
+++ b/plugins/LdapAuthorization/LdapAuthorizationPlugin.php
@@ -52,7 +52,6 @@ class LdapAuthorizationPlugin extends AuthorizationPlugin
public $attributes = array();
function onInitializePlugin(){
- parent::onInitializePlugin();
if(!isset($this->host)){
throw new Exception("must specify a host");
}
diff --git a/plugins/Minify/MinifyPlugin.php b/plugins/Minify/MinifyPlugin.php
index 71fade19a..718bfd163 100644
--- a/plugins/Minify/MinifyPlugin.php
+++ b/plugins/Minify/MinifyPlugin.php
@@ -84,7 +84,7 @@ class MinifyPlugin extends Plugin
function onStartScriptElement($action,&$src,&$type) {
$url = parse_url($src);
- if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
+ if( empty($url['scheme']) && empty($url['host']) && empty($url['query']) && empty($url['fragment']))
{
$src = $this->minifyUrl($src);
}
diff --git a/plugins/OpenID/User_openid_trustroot.php b/plugins/OpenID/User_openid_trustroot.php
index 44288945b..0b411b8f7 100644
--- a/plugins/OpenID/User_openid_trustroot.php
+++ b/plugins/OpenID/User_openid_trustroot.php
@@ -22,7 +22,7 @@ class User_openid_trustroot extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('User_openid_trustroot', $kv);
}
diff --git a/plugins/RSSCloud/LoggingAggregator.php b/plugins/RSSCloud/LoggingAggregator.php
new file mode 100644
index 000000000..e37eed16a
--- /dev/null
+++ b/plugins/RSSCloud/LoggingAggregator.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * This test class pretends to be an RSS aggregator. It logs notifications
+ * from the cloud.
+ *
+ * PHP version 5
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Dummy aggregator that acts as a proper notification handler. It
+ * doesn't do anything but respond correctly when notified via
+ * REST. Mostly, this is just and action I used to develop the plugin
+ * and easily test things end-to-end. I'm leaving it in here as it
+ * may be useful for developing the plugin further.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+class LoggingAggregatorAction extends Action
+{
+
+ var $challenge = null;
+ var $url = null;
+
+ /**
+ * Initialization.
+ *
+ * @param array $args Web and URL arguments
+ *
+ * @return boolean false if user doesn't exist
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->url = $this->arg('url');
+ $this->challenge = $this->arg('challenge');
+
+ common_debug("args = " . var_export($this->args, true));
+ common_debug('url = ' . $this->url . ' challenge = ' . $this->challenge);
+
+ return true;
+ }
+
+ /**
+ * Handle the request
+ *
+ * @param array $args $_REQUEST data (unused)
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if (empty($this->url)) {
+ $this->showError('Hey, you have to provide a url parameter.');
+ return;
+ }
+
+ if (!empty($this->challenge)) {
+
+ // must be a GET
+
+ if ($_SERVER['REQUEST_METHOD'] != 'GET') {
+ $this->showError('This resource requires an HTTP GET.');
+ return;
+ }
+
+ header('Content-Type: text/xml');
+ echo $this->challenge;
+
+ } else {
+
+ // must be a POST
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->showError('This resource requires an HTTP POST.');
+ return;
+ }
+
+ header('Content-Type: text/xml');
+ Echo "<notifyResult success='true' msg='Thanks for the update.' />\n";
+ }
+
+ $this->ip = $_SERVER['REMOTE_ADDR'];
+
+ common_log(LOG_INFO, 'RSSCloud Logging Aggregator - ' .
+ $this->ip . ' claims the feed at ' .
+ $this->url . ' has been updated.');
+ }
+
+ /**
+ * Show an XML error when things go badly
+ *
+ * @param string $msg the error message
+ *
+ * @return void
+ */
+
+ function showError($msg)
+ {
+ header('HTTP/1.1 400 Bad Request');
+ header('Content-Type: text/xml');
+ echo "<?xml version='1.0'?>\n";
+ echo "<notifyResult success='false' msg='$msg' />\n";
+ }
+
+} \ No newline at end of file
diff --git a/plugins/RSSCloud/README b/plugins/RSSCloud/README
new file mode 100644
index 000000000..1237e3e0e
--- /dev/null
+++ b/plugins/RSSCloud/README
@@ -0,0 +1,54 @@
+This plugin enables RSSCloud (http://rsscloud.org/) publishing and
+subscription handling for RSS 2.0 profile feeds (i.e:
+http://SITE/PATH/api/statuses/user_timeline/USERNAME.rss). When the
+plugin is enabled, StatusNet acts as both the publisher and hub ('writer' and
+'cloud' in RSSCloud parlance), but only for local StatusNet feeds. It's
+not possible to use it as a general purpose hub -- for instance you can't
+subscribe and get updates to a Wordpress feed from StatusNet using this
+plugin.
+
+To use the plugin, add the following to your config.php:
+
+ addPlugin('RSSCloud');
+
+Enabling the plugin will add a <cloud> element to your RSS 2.0 profile feeds
+that looks like this:
+
+ <cloud domain="SITE" port="80" path="/main/rsscloud/request_notify"
+ registerProcedure="" protocol="http-post"/>
+
+Aggregators may subscribe by sending a proper REST RSSCloud subscription
+request (the optional 'domain' parameter with challenge is supported).
+Subscribing aggregators will be notified ('pinged') when users they have
+subscribed to post new notices. Currently, REST is the only protocol
+supported for notifications.
+
+Deamon
+------
+
+There's also a daemon for offline processing of queued notices with
+RSSCloud destinations, which will start automatically if/when you run
+scripts/startdaemons.sh.
+
+Notes
+-----
+
+- Again, only RSS 2.0 profile feeds may be subscribed to, and they have
+ to be the ones with user names in them, like:
+ http://SITE/PATH/api/statuses/user_timeline/USERNAME.rss
+- Subscriptions are deleted after three notification failures in a row
+ (not sure this is optimal).
+- The plugin includes a dummy LoggingAggregator class that can be used
+ for end-to-end testing. You probably don't want to mess with it.
+
+TODO
+----
+
+- Figure out why the RSSCloudSubcription can't ->delete() or ->update()
+- Support pinging via XML-RPC and SOAP
+- Automatically delete subscriptions? Point of reference: Dave's hub
+ implementation auto-deletes them after 25 hours. WordPress never deletes them.
+- Support additional feed URL addresses for the same feed (e.g.: by numeric ID,
+ ?user_id=xxx, etc.)
+- Support additional feeds that make sense (e.g: replies)?
+- Possibly use "rssCloud" (like Dave) instead of "RSSCloud" everywhere
diff --git a/plugins/RSSCloud/RSSCloudNotifier.php b/plugins/RSSCloud/RSSCloudNotifier.php
new file mode 100644
index 000000000..d454691c8
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudNotifier.php
@@ -0,0 +1,240 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class to ping an rssCloud endpoint when a feed has been updated
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+/**
+ * Class for notifying cloud-enabled RSS aggregators that StatusNet
+ * feeds have been updated.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+class RSSCloudNotifier
+{
+ const MAX_FAILURES = 3;
+
+ /**
+ * Send an HTTP GET to the notification handler with a
+ * challenge string to see if it repsonds correctly.
+ *
+ * @param string $endpoint URL of the notification handler
+ * @param string $feed the feed being subscribed to
+ *
+ * @return boolean success
+ */
+ function challenge($endpoint, $feed)
+ {
+ $code = common_confirmation_code(128);
+ $params = array('url' => $feed, 'challenge' => $code);
+ $url = $endpoint . '?' . http_build_query($params);
+
+ try {
+ $client = new HTTPClient();
+ $response = $client->get($url);
+ } catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_INFO,
+ 'RSSCloud plugin - failure testing notify handler ' .
+ $endpoint . ' - ' . $e->getMessage());
+ return false;
+ }
+
+ // Check response is betweet 200 and 299 and body contains challenge data
+
+ $status = $response->getStatus();
+ $body = $response->getBody();
+
+ if ($status >= 200 && $status < 300) {
+
+ // NOTE: the spec says that the body must contain the string
+ // challenge. It doesn't say that the body must contain the
+ // challenge string ONLY, although that seems to be the way
+ // the other implementors have interpreted it.
+
+ if (strpos($body, $code) !== false) {
+ common_log(LOG_INFO, 'RSSCloud plugin - ' .
+ "success testing notify handler: $endpoint");
+ return true;
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - ' .
+ 'challenge/repsonse failed for notify handler ' .
+ $endpoint);
+ common_debug('body = ' . var_export($body, true));
+ return false;
+ }
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - ' .
+ "failure testing notify handler: $endpoint " .
+ ' - got HTTP ' . $status);
+ common_debug('body = ' . var_export($body, true));
+ return false;
+ }
+ }
+
+ /**
+ * HTTP POST a notification that a feed has been updated
+ * ('ping the cloud').
+ *
+ * @param String $endpoint URL of the notification handler
+ * @param String $feed the feed being subscribed to
+ *
+ * @return boolean success
+ */
+ function postUpdate($endpoint, $feed)
+ {
+
+ $headers = array();
+ $postdata = array('url' => $feed);
+
+ try {
+ $client = new HTTPClient();
+ $response = $client->post($endpoint, $headers, $postdata);
+ } catch (HTTP_Request2_Exception $e) {
+ common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
+ $endpoint . ' that feed ' . $feed .
+ ' has changed: ' . $e->getMessage());
+ return false;
+ }
+
+ $status = $response->getStatus();
+
+ if ($status >= 200 && $status < 300) {
+ common_log(LOG_INFO, 'RSSCloud plugin - success notifying ' .
+ $endpoint . ' that feed ' . $feed . ' has changed.');
+ return true;
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
+ $endpoint . ' that feed ' . $feed .
+ ' has changed: got HTTP ' . $status);
+ return false;
+ }
+ }
+
+ /**
+ * Notify all subscribers to a profile feed that it has changed.
+ *
+ * @param Profile $profile the profile whose feed has been
+ * updated
+ *
+ * @return boolean success
+ */
+ function notify($profile)
+ {
+ $feed = common_path('api/statuses/user_timeline/') .
+ $profile->nickname . '.rss';
+
+ $cloudSub = new RSSCloudSubscription();
+
+ $cloudSub->subscribed = $profile->id;
+
+ if ($cloudSub->find()) {
+ while ($cloudSub->fetch()) {
+ $result = $this->postUpdate($cloudSub->url, $feed);
+ if ($result == false) {
+ $this->handleFailure($cloudSub);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle problems posting cloud notifications. Increment the failure
+ * count, or delete the subscription if the maximum number of failures
+ * is exceeded.
+ *
+ * XXX: Redo with proper DB_DataObject methods once I figure out what
+ * what the problem is with pluginized DB_DataObjects. -Z
+ *
+ * @param RSSCloudSubscription $cloudSub the subscription in question
+ *
+ * @return boolean success
+ */
+ function handleFailure($cloudSub)
+ {
+ $failCnt = $cloudSub->failures + 1;
+
+ if ($failCnt == self::MAX_FAILURES) {
+
+ common_log(LOG_INFO,
+ 'Deleting RSSCloud subcription ' .
+ '(max failure count reached), profile: ' .
+ $cloudSub->subscribed .
+ ' handler: ' .
+ $cloudSub->url);
+
+ // XXX: WTF! ->delete() doesn't work. Clearly, there are some issues with
+ // the DB_DataObject, or my understanding of it. Have to drop into SQL.
+
+ // $result = $cloudSub->delete();
+
+ $qry = 'DELETE from rsscloud_subscription' .
+ ' WHERE subscribed = ' . $cloudSub->subscribed .
+ ' AND url = \'' . $cloudSub->url . '\'';
+
+ $result = $cloudSub->query($qry);
+
+ if (!$result) {
+ common_log_db_error($cloudSub, 'DELETE', __FILE__);
+ common_log(LOG_ERR, 'Could not delete RSSCloud subscription.');
+ }
+
+ } else {
+
+ common_debug('Updating failure count on RSSCloud subscription. ' .
+ $failCnt);
+
+ $failCnt = $cloudSub->failures + 1;
+
+ // XXX: ->update() not working either, gar!
+
+ $qry = 'UPDATE rsscloud_subscription' .
+ ' SET failures = ' . $failCnt .
+ ' WHERE subscribed = ' . $cloudSub->subscribed .
+ ' AND url = \'' . $cloudSub->url . '\'';
+
+ $result = $cloudSub->query($qry);
+
+ if (!$result) {
+ common_log_db_error($cloudsub, 'UPDATE', __FILE__);
+ common_log(LOG_ERR,
+ 'Could not update failure ' .
+ 'count on RSSCloud subscription');
+ }
+ }
+ }
+
+}
+
diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php
new file mode 100644
index 000000000..4b9812a47
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudPlugin.php
@@ -0,0 +1,279 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to support RSSCloud
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @copyright 2009 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);
+}
+
+/**
+ * Plugin class for adding RSSCloud capabilities to StatusNet
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+
+class RSSCloudPlugin extends Plugin
+{
+ /**
+ * Our friend, the constructor
+ *
+ * @return void
+ */
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Setup the info for the subscription handler. Allow overriding
+ * to point at another cloud hub (not currently used).
+ *
+ * @return void
+ */
+
+ function onInitializePlugin()
+ {
+ $this->domain = common_config('rsscloud', 'domain');
+ $this->port = common_config('rsscloud', 'port');
+ $this->path = common_config('rsscloud', 'path');
+ $this->funct = common_config('rsscloud', 'function');
+ $this->protocol = common_config('rsscloud', 'protocol');
+
+ // set defaults
+
+ $local_server = parse_url(common_path('main/rsscloud/request_notify'));
+
+ if (empty($this->domain)) {
+ $this->domain = $local_server['host'];
+ }
+
+ if (empty($this->port)) {
+ $this->port = '80';
+ }
+
+ if (empty($this->path)) {
+ $this->path = $local_server['path'];
+ }
+
+ if (empty($this->funct)) {
+ $this->funct = '';
+ }
+
+ if (empty($this->protocol)) {
+ $this->protocol = 'http-post';
+ }
+ }
+
+ /**
+ * Add RSSCloud-related paths to the router table
+ *
+ * Hook for RouterInitialized event.
+ *
+ * @param Mapper &$m URL parser and mapper
+ *
+ * @return boolean hook return
+ */
+
+ function onRouterInitialized(&$m)
+ {
+ $m->connect('/main/rsscloud/request_notify',
+ array('action' => 'RSSCloudRequestNotify'));
+
+ // XXX: This is just for end-to-end testing. Uncomment if you need to pretend
+ // to be a cloud hub for some reason.
+ //$m->connect('/main/rsscloud/notify',
+ // array('action' => 'LoggingAggregator'));
+
+ return true;
+ }
+
+ /**
+ * Automatically load the actions and libraries used by
+ * the RSSCloud plugin
+ *
+ * @param Class $cls the class
+ *
+ * @return boolean hook return
+ *
+ */
+
+ function onAutoload($cls)
+ {
+ switch ($cls)
+ {
+ case 'RSSCloudSubscription':
+ include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudSubscription.php';
+ return false;
+ case 'RSSCloudNotifier':
+ include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php';
+ return false;
+ case 'RSSCloudRequestNotifyAction':
+ case 'LoggingAggregatorAction':
+ include_once INSTALLDIR . '/plugins/RSSCloud/' .
+ mb_substr($cls, 0, -6) . '.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Add a <cloud> element to the RSS feed (after the rss <channel>
+ * element is started).
+ *
+ * @param Action $action the ApiAction
+ *
+ * @return void
+ */
+
+ function onStartApiRss($action)
+ {
+ if (get_class($action) == 'ApiTimelineUserAction') {
+
+ $attrs = array('domain' => $this->domain,
+ 'port' => $this->port,
+ 'path' => $this->path,
+ 'registerProcedure' => $this->funct,
+ 'protocol' => $this->protocol);
+
+ // Dipping into XMLWriter to avoid a full end element (</cloud>).
+
+ $action->xw->startElement('cloud');
+ foreach ($attrs as $name => $value) {
+ $action->xw->writeAttribute($name, $value);
+ }
+
+ $action->xw->endElement();
+ }
+ }
+
+ /**
+ * Add an RSSCloud queue item for each notice
+ *
+ * @param Notice $notice the notice
+ * @param array &$transports the list of transports (queues)
+ *
+ * @return boolean hook return
+ */
+
+ function onStartEnqueueNotice($notice, &$transports)
+ {
+ array_push($transports, 'rsscloud');
+ return true;
+ }
+
+ /**
+ * broadcast the message when not using queuehandler
+ *
+ * @param Notice &$notice the notice
+ * @param array $queue destination queue
+ *
+ * @return boolean hook return
+ */
+
+ function onUnqueueHandleNotice(&$notice, $queue)
+ {
+ if (($queue == 'rsscloud') && ($this->_isLocal($notice))) {
+
+ common_debug('broadcasting rssCloud bound notice ' . $notice->id);
+
+ $profile = $notice->getProfile();
+
+ $notifier = new RSSCloudNotifier();
+ $notifier->notify($profile);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine whether the notice was locally created
+ *
+ * @param Notice $notice the notice in question
+ *
+ * @return boolean locality
+ */
+
+ function _isLocal($notice)
+ {
+ return ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC);
+ }
+
+ /**
+ * Create the rsscloud_subscription table if it's not
+ * already in the DB
+ *
+ * @return boolean hook return
+ */
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+ $schema->ensureTable('rsscloud_subscription',
+ array(new ColumnDef('subscribed', 'integer',
+ null, false, 'PRI'),
+ new ColumnDef('url', 'varchar',
+ '255', false, 'PRI'),
+ new ColumnDef('failures', 'integer',
+ null, false, null, 0),
+ new ColumnDef('created', 'datetime',
+ null, false),
+ new ColumnDef('modified', 'timestamp',
+ null, false, null,
+ 'CURRENT_TIMESTAMP',
+ 'on update CURRENT_TIMESTAMP')
+ ));
+ return true;
+ }
+
+ /**
+ * Add RSSCloudQueueHandler to the list of valid daemons to
+ * start
+ *
+ * @param array $daemons the list of daemons to run
+ *
+ * @return boolean hook return
+ *
+ */
+
+ function onGetValidDaemons($daemons)
+ {
+ array_push($daemons, INSTALLDIR .
+ '/plugins/RSSCloud/RSSCloudQueueHandler.php');
+ return true;
+ }
+
+}
+
diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php
new file mode 100755
index 000000000..693dd27c1
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudQueueHandler.php
@@ -0,0 +1,78 @@
+#!/usr/bin/env php
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..'));
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_ENJIT_HELP
+Daemon script for pushing new notices to RSSCloud subscribers.
+
+ -i --id Identity (default none)
+
+END_OF_ENJIT_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/queuehandler.php';
+require_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php';
+require_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudSubscription.php';
+
+class RSSCloudQueueHandler extends QueueHandler
+{
+ var $notifier = null;
+
+ function transport()
+ {
+ return 'rsscloud';
+ }
+
+ function start()
+ {
+ $this->log(LOG_INFO, "INITIALIZE");
+ $this->notifier = new RSSCloudNotifier();
+ return true;
+ }
+
+ function handle_notice($notice)
+ {
+ $profile = $notice->getProfile();
+ return $this->notifier->notify($profile);
+ }
+
+ function finish()
+ {
+ }
+
+}
+
+if (have_option('i')) {
+ $id = get_option_value('i');
+} else if (have_option('--id')) {
+ $id = get_option_value('--id');
+} else if (count($args) > 0) {
+ $id = $args[0];
+} else {
+ $id = null;
+}
+
+$handler = new RSSCloudQueueHandler($id);
+
+$handler->runOnce();
diff --git a/plugins/RSSCloud/RSSCloudRequestNotify.php b/plugins/RSSCloud/RSSCloudRequestNotify.php
new file mode 100644
index 000000000..d76c08d37
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudRequestNotify.php
@@ -0,0 +1,347 @@
+<?php
+/**
+ * Action to let RSSCloud aggregators request update notification when
+ * user profile feeds change.
+ *
+ * PHP version 5
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+/**
+ * Action class to handle RSSCloud notification (subscription) requests
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://status.net/
+ **/
+
+class RSSCloudRequestNotifyAction extends Action
+{
+ /**
+ * Initialization.
+ *
+ * @param array $args Web and URL arguments
+ *
+ * @return boolean false if user doesn't exist
+ */
+
+ function prepare($args)
+ {
+ parent::prepare($args);
+
+ $this->ip = $_SERVER['REMOTE_ADDR'];
+ $this->port = $this->arg('port');
+ $this->path = $this->arg('path');
+
+ if ($this->path[0] != '/') {
+ $this->path = '/' . $this->path;
+ }
+
+ $this->protocol = $this->arg('protocol');
+ $this->procedure = $this->arg('notifyProcedure');
+ $this->domain = $this->arg('domain');
+
+ $this->feeds = $this->getFeeds();
+
+ return true;
+ }
+
+ /**
+ * Handle the request
+ *
+ * Checks for all the required parameters for a subscription,
+ * validates that the feed being subscribed to is real, and then
+ * saves the subsctiption.
+ *
+ * @param array $args $_REQUEST data (unused)
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->showResult(false, 'Request must be POST.');
+ return;
+ }
+
+ $missing = array();
+
+ if (empty($this->port)) {
+ $missing[] = 'port';
+ }
+
+ if (empty($this->path)) {
+ $missing[] = 'path';
+ }
+
+ if (empty($this->protocol)) {
+ $missing[] = 'protocol';
+ } else if (strtolower($this->protocol) != 'http-post') {
+ $msg = 'Only http-post notifications are supported at this time.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ if (!isset($this->procedure)) {
+ $missing[] = 'notifyProcedure';
+ }
+
+ if (!empty($missing)) {
+ $msg = 'The following parameters were missing from the request body: ' .
+ implode(', ', $missing) . '.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ if (empty($this->feeds)) {
+ $msg = 'You must provide at least one valid profile feed url ' .
+ '(url1, url2, url3 ... urlN).';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ // We have to validate everything before saving anything.
+ // We only return one success or failure no matter how
+ // many feeds the subscriber is trying to subscribe to
+
+ foreach ($this->feeds as $feed) {
+
+ if (!$this->validateFeed($feed)) {
+
+ $nh = $this->getNotifyUrl();
+ common_log(LOG_WARNING,
+ "RSSCloud plugin - $nh tried to subscribe to invalid feed: $feed");
+
+ $msg = 'Feed subscription failed - Not a valid feed.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ if (!$this->testNotificationHandler($feed)) {
+ $msg = 'Feed subscription failed - ' .
+ 'notification handler doesn\'t respond correctly.';
+ $this->showResult(false, $msg);
+ return;
+ }
+
+ }
+
+ foreach ($this->feeds as $feed) {
+ $this->saveSubscription($feed);
+ }
+
+ // XXX: What to do about deleting stale subscriptions?
+ // 25 hours seems harsh. WordPress doesn't ever remove
+ // subscriptions.
+
+ $msg = 'Thanks for the subscription. ' .
+ 'When the feed(s) update(s) we\'ll notify you.';
+
+ $this->showResult(true, $msg);
+ }
+
+ /**
+ * Validate that the requested feed is one we serve
+ * up via RSSCloud.
+ *
+ * @param string $feed the feed in question
+ *
+ * @return void
+ */
+
+ function validateFeed($feed)
+ {
+ $user = $this->userFromFeed($feed);
+
+ if (empty($user)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Pull all of the urls (url1, url2, url3...urlN) that
+ * the subscriber wants to subscribe to.
+ *
+ * @return array $feeds the list of feeds
+ */
+
+ function getFeeds()
+ {
+ $feeds = array();
+
+ while (list($key, $feed) = each($this->args)) {
+ if (preg_match('/^url\d*$/', $key)) {
+ $feeds[] = $feed;
+ }
+ }
+
+ return $feeds;
+ }
+
+ /**
+ * Test that a notification handler is there and is reponding
+ * correctly. This is called before adding a subscription.
+ *
+ * @param string $feed the feed to verify
+ *
+ * @return boolean success result
+ */
+
+ function testNotificationHandler($feed)
+ {
+ $notifyUrl = $this->getNotifyUrl();
+
+ $notifier = new RSSCloudNotifier();
+
+ if (isset($this->domain)) {
+
+ // 'domain' param set, so we have to use GET and send a challenge
+
+ common_log(LOG_INFO,
+ 'RSSCloud plugin - Testing notification handler with challenge: ' .
+ $notifyUrl);
+ return $notifier->challenge($notifyUrl, $feed);
+
+ } else {
+ common_log(LOG_INFO, 'RSSCloud plugin - Testing notification handler: ' .
+ $notifyUrl);
+
+ return $notifier->postUpdate($notifyUrl, $feed);
+ }
+ }
+
+ /**
+ * Build the URL for the notification handler based on the
+ * parameters passed in with the subscription request.
+ *
+ * @return string notification handler url
+ */
+
+ function getNotifyUrl()
+ {
+ if (isset($this->domain)) {
+ return 'http://' . $this->domain . ':' . $this->port . $this->path;
+ } else {
+ return 'http://' . $this->ip . ':' . $this->port . $this->path;
+ }
+ }
+
+ /**
+ * Uses the nickname part of the subscribed feed URL to figure out
+ * whethere there's really a user with such a feed. Used to
+ * validate feeds before adding a subscription.
+ *
+ * @param string $feed the feed in question
+ *
+ * @return boolean success
+ */
+
+ function userFromFeed($feed)
+ {
+ // We only do profile feeds
+
+ $path = common_path('api/statuses/user_timeline/');
+ $valid = '%^' . $path . '(?<nickname>.*)\.rss$%';
+
+ if (preg_match($valid, $feed, $matches)) {
+ $user = User::staticGet('nickname', $matches['nickname']);
+ if (!empty($user)) {
+ return $user;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Save an RSSCloud subscription
+ *
+ * @param string $feed a valid profile feed
+ *
+ * @return boolean success result
+ */
+
+ function saveSubscription($feed)
+ {
+ $user = $this->userFromFeed($feed);
+
+ $notifyUrl = $this->getNotifyUrl();
+
+ $sub = RSSCloudSubscription::getSubscription($user->id, $notifyUrl);
+
+ if ($sub) {
+ common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl refreshed subscription" .
+ " to user $user->nickname (id: $user->id).");
+ } else {
+
+ $sub = new RSSCloudSubscription();
+
+ $sub->subscribed = $user->id;
+ $sub->url = $notifyUrl;
+ $sub->created = common_sql_now();
+
+ if (!$sub->insert()) {
+ common_log_db_error($sub, 'INSERT', __FILE__);
+ return false;
+ }
+
+ common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl subscribed" .
+ " to user $user->nickname (id: $user->id)");
+ }
+
+ return true;
+ }
+
+ /**
+ * Show an XML message indicating the subscription
+ * was successful or failed.
+ *
+ * @param boolean $success whether it was good or bad
+ * @param string $msg the message to output
+ *
+ * @return boolean success result
+ */
+
+ function showResult($success, $msg)
+ {
+ $this->startXML();
+ $this->elementStart('notifyResult',
+ array('success' => ($success) ? 'true' : 'false',
+ 'msg' => $msg));
+ $this->endXML();
+ }
+
+}
+
diff --git a/plugins/RSSCloud/RSSCloudSubscription.php b/plugins/RSSCloud/RSSCloudSubscription.php
new file mode 100644
index 000000000..396c604e7
--- /dev/null
+++ b/plugins/RSSCloud/RSSCloudSubscription.php
@@ -0,0 +1,79 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+ exit(1);
+}
+
+/**
+ * Table Definition for rsscloud_subscription
+ */
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+class RSSCloudSubscription extends Memcached_DataObject {
+
+ var $__table='rsscloud_subscription'; // table name
+ var $subscribed; // int primary key user id
+ var $url; // string primary key
+ var $failures; // int
+ var $created; // datestamp()
+ var $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('DataObjects_Grp',$k,$v); }
+
+ function table()
+ {
+
+ $db = $this->getDatabaseConnection();
+ $dbtype = $db->phptype;
+
+ $cols = array('subscribed' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'url' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'failures' => DB_DATAOBJECT_INT,
+ 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+ 'modified' => ($dbtype == 'mysql' || $dbtype == 'mysqli') ?
+ DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL :
+ DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME
+ );
+
+ return $cols;
+ }
+
+ function keys()
+ {
+ return array('subscribed' => 'N', 'url' => 'N');
+ }
+
+ static function getSubscription($subscribed, $url)
+ {
+ $sub = new RSSCloudSubscription();
+ $sub->whereAdd("subscribed = $subscribed");
+ $sub->whereAdd("url = '$url'");
+ $sub->limit(1);
+
+ if ($sub->find()) {
+ $sub->fetch();
+ return $sub;
+ }
+
+ return false;
+ }
+
+}
diff --git a/plugins/Recaptcha/RecaptchaPlugin.php b/plugins/Recaptcha/RecaptchaPlugin.php
index db118dbb8..3665214f8 100644
--- a/plugins/Recaptcha/RecaptchaPlugin.php
+++ b/plugins/Recaptcha/RecaptchaPlugin.php
@@ -62,9 +62,8 @@ class RecaptchaPlugin extends Plugin
function onEndRegistrationFormData($action)
{
- $action->style('#recaptcha_area{float:left;}');
$action->elementStart('li');
- $action->raw('<label for="recaptcha_area">Captcha</label>');
+ $action->raw('<label for="recaptcha">Captcha</label>');
if($this->checkssl() === true) {
$action->raw(recaptcha_get_html($this->public_key), null, true);
} else {
diff --git a/plugins/UserFlag/UserFlagPlugin.php b/plugins/UserFlag/UserFlagPlugin.php
index 602a5bfa8..a33869c19 100644
--- a/plugins/UserFlag/UserFlagPlugin.php
+++ b/plugins/UserFlag/UserFlagPlugin.php
@@ -102,20 +102,20 @@ class UserFlagPlugin extends Plugin
function onAutoload($cls)
{
- switch ($cls)
+ switch (strtolower($cls))
{
- case 'FlagprofileAction':
- case 'AdminprofileflagAction':
- case 'ClearflagAction':
+ case 'flagprofileaction':
+ case 'adminprofileflagaction':
+ case 'clearflagaction':
include_once INSTALLDIR.'/plugins/UserFlag/' .
strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
- case 'FlagProfileForm':
- case 'ClearFlagForm':
+ case 'flagprofileform':
+ case 'clearflagform':
include_once INSTALLDIR.'/plugins/UserFlag/' . strtolower($cls . '.php');
return false;
- case 'User_flag_profile':
- include_once INSTALLDIR.'/plugins/UserFlag/'.$cls.'.php';
+ case 'user_flag_profile':
+ include_once INSTALLDIR.'/plugins/UserFlag/'.ucfirst(strtolower($cls)).'.php';
return false;
default:
return true;
@@ -258,4 +258,39 @@ class UserFlagPlugin extends Plugin
}
return true;
}
+
+ /**
+ * Ensure that flag entries for a profile are deleted
+ * along with the profile when deleting users.
+ * This prevents breakage of the admin profile flag UI.
+ *
+ * @param Profile $profile
+ * @param array &$related list of related tables; entries
+ * with matching profile_id will be deleted.
+ *
+ * @return boolean hook result
+ */
+
+ function onProfileDeleteRelated($profile, &$related)
+ {
+ $related[] = 'user_flag_profile';
+ return true;
+ }
+
+ /**
+ * Ensure that flag entries created by a user are deleted
+ * when that user gets deleted.
+ *
+ * @param User $user
+ * @param array &$related list of related tables; entries
+ * with matching user_id will be deleted.
+ *
+ * @return boolean hook result
+ */
+
+ function onUserDeleteRelated($user, &$related)
+ {
+ $related[] = 'user_flag_profile';
+ return true;
+ }
}
diff --git a/plugins/UserFlag/User_flag_profile.php b/plugins/UserFlag/User_flag_profile.php
index 6bf47071b..bc4251cf7 100644
--- a/plugins/UserFlag/User_flag_profile.php
+++ b/plugins/UserFlag/User_flag_profile.php
@@ -108,7 +108,7 @@ class User_flag_profile extends Memcached_DataObject
* @return User_flag_profile found object or null
*/
- function &pkeyGet($kv)
+ function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('User_flag_profile', $kv);
}
diff --git a/scripts/console.php b/scripts/console.php
index 329caf472..8b62a3a96 100755
--- a/scripts/console.php
+++ b/scripts/console.php
@@ -128,6 +128,8 @@ function console_help()
if (CONSOLE_INTERACTIVE) {
print "StatusNet interactive PHP console... type ctrl+D or enter 'exit' to exit.\n";
$prompt = common_config('site', 'name') . '> ';
+} else {
+ $prompt = '';
}
while (!feof(STDIN)) {
$line = read_input_line($prompt);
diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh
index 90e7331ca..c790f1f34 100755
--- a/scripts/stopdaemons.sh
+++ b/scripts/stopdaemons.sh
@@ -25,7 +25,7 @@ DIR=`php $SDIR/getpiddir.php`
for f in jabberhandler ombhandler publichandler smshandler pinghandler \
xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
- twitterstatusfetcher synctwitterfriends pluginhandler; do
+ twitterstatusfetcher synctwitterfriends pluginhandler rsscloudhandler; do
FILES="$DIR/$f.*.pid"
for ff in "$FILES" ; do
diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css
index 92d544977..a27bd74b8 100644
--- a/theme/cloudy/css/display.css
+++ b/theme/cloudy/css/display.css
@@ -242,6 +242,7 @@ margin-right:-47px;
#header {
width:100%;
+height:10.5em;
position:relative;
float:left;
padding-top:18px;
@@ -1000,7 +1001,7 @@ float:left;
font-size:0.95em;
margin-left:59px;
min-width:60%;
-max-width:66%;
+max-width:62%;
}
#showstream .notice div.entry-content,
#shownotice .notice div.entry-content {
@@ -1517,12 +1518,13 @@ min-width:0;
#subscribers.user_in #content,
#showgroup.user_in #content,
#conversation.user_in #content,
-#siteadminpanel #content,
-#designadminpanel #content,
-#useradminpanel #content,
-#pathsadminpanel #content,
-#adminprofileflag #content {
-padding-top:170px;
+#attachment.user_in #content,
+#siteadminpanel.user_in #content,
+#designadminpanel.user_in #content,
+#useradminpanel.user_in #content,
+#pathsadminpanel.user_in #content,
+#adminprofileflag.user_in #content {
+padding-top:12.5em;
}
#profilesettings #form_notice,