Merge branch '0.9.x' of git@gitorious.org:statusnet/mainline into 0.9.x

This commit is contained in:
Evan Prodromou 2010-01-06 23:23:30 -08:00
commit a55939f3b1
38 changed files with 1364 additions and 98 deletions

View File

@ -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;

View File

@ -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));
}

View File

@ -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();

View File

@ -37,7 +37,7 @@ class Avatar extends Memcached_DataObject
}
}
function &pkeyGet($kv)
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Avatar', $kv);
}

View File

@ -120,7 +120,7 @@ class Config extends Memcached_DataObject
return $result;
}
function &pkeyGet($kv)
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Config', $kv);
}

View File

@ -32,7 +32,7 @@ class Fave extends Memcached_DataObject
return $fave;
}
function &pkeyGet($kv)
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Fave', $kv);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -96,7 +96,7 @@ class Notice_tag extends Memcached_DataObject
}
}
function &pkeyGet($kv)
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('Notice_tag', $kv);
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -56,13 +56,4 @@ You can use the following commands with %%site.name%%.
* sub <nickname> - same as 'follow'
* unsub <nickname> - same as 'leave'
* last <nickname> - same as 'get'
* on <nickname> - not yet implemented.
* off <nickname> - not yet implemented.
* nudge <nickname> - not yet implemented.
* invite <phone number> - not yet implemented.
* track <word> - not yet implemented.
* untrack <word> - not yet implemented.
* track off - not yet implemented.
* untrack all - not yet implemented.
* tracks - not yet implemented.
* tracking - not yet implemented.
* nudge <nickname> - remind a user to update.

View File

@ -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"
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -528,6 +528,10 @@ class Schema
$sql .= " auto_increment ";
}
if (!empty($cd->extra)) {
$sql .= "{$cd->extra} ";
}
return $sql;
}
}

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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";
}
}

54
plugins/RSSCloud/README Normal file
View File

@ -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

View File

@ -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');
}
}
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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,