diff --git a/actions/apigroupleave.php b/actions/apigroupleave.php index 514a3a557d..5627bfc146 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 08fce15098..90c85e1a4e 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 1cb8d7efe6..baed2a0c7c 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 8d6424e8b2..91bde0f040 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 6d914ca1f6..43b99587fa 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 11e876ff19..8113c8e166 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 e3db91b205..72a42b0880 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 de2cf5f6eb..9f4d592956 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 1af7439f7f..2a0787e387 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 3c23a991f0..069b2c7a1c 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 15ca348218..4e3cc56788 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 d3ddad656a..e350e6e2f8 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 02740280f5..79231f0b0c 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 03196447b8..25d908dbf9 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 afa7fb74e4..74aca37305 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 295c321b57..9c673540d7 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 fedfd5f19e..faf1331cda 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 1a3064318f..2c20921c35 100644 --- a/doc-src/sms +++ b/doc-src/sms @@ -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. diff --git a/lib/command.php b/lib/command.php index 67140c3485..ad2e0bb975 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 - 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 - unsubscribe from user\n". - "d - direct message to user\n". - "get - get last notice from user\n". - "whois - get profile info on user\n". - "fav - add user's last notice as a 'fave'\n". - "fav # - add notice with the given id as a 'fave'\n". - "repeat # - repeat a notice with a given id\n". - "repeat - repeat the last notice from user\n". - "reply # - reply to notice with a given id\n". - "reply - reply to the last notice from user\n". - "join - join group\n". - "login - Get a link to login to the web interface\n". - "drop - leave group\n". - "stats - get your stats\n". - "stop - same as 'off'\n". - "quit - same as 'off'\n". - "sub - same as 'follow'\n". - "unsub - same as 'leave'\n". - "last - same as 'get'\n". - "on - not yet implemented.\n". - "off - not yet implemented.\n". - "nudge - remind a user to update.\n". - "invite - not yet implemented.\n". - "track - not yet implemented.\n". - "untrack - 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 - 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 - unsubscribe from user")."\n". + _("d - direct message to user")."\n". + _("get - get last notice from user")."\n". + _("whois - get profile info on user")."\n". + _("fav - add user's last notice as a 'fave'")."\n". + _("fav # - add notice with the given id as a 'fave'")."\n". + _("repeat # - repeat a notice with a given id")."\n". + _("repeat - repeat the last notice from user")."\n". + _("reply # - reply to notice with a given id")."\n". + _("reply - reply to the last notice from user")."\n". + _("join - join group")."\n". + #_("login - Get a link to login to the web interface")."\n". + _("drop - leave group")."\n". + _("stats - get your stats")."\n". + _("stop - same as 'off'")."\n". + _("quit - same as 'off'")."\n". + _("sub - same as 'follow'")."\n". + _("unsub - same as 'leave'")."\n". + _("last - same as 'get'")."\n". + #_("on - not yet implemented.")."\n". + #_("off - not yet implemented.")."\n". + _("nudge - remind a user to update.")."\n"); + #_("invite - not yet implemented.")."\n". + #_("track - not yet implemented.")."\n". + #_("untrack - 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 2091c6e2ca..31660ce954 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 569bfa8734..0d72ddf7ab 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 6fe442d56b..a7f64ebed1 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 7673e61efb..e5e22c0dde 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 71fade19a5..718bfd1635 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 44288945be..0b411b8f7f 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 0000000000..e37eed16a3 --- /dev/null +++ b/plugins/RSSCloud/LoggingAggregator.php @@ -0,0 +1,140 @@ + + * @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 . + */ + +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 + * @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 "\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 "\n"; + echo "\n"; + } + +} \ No newline at end of file diff --git a/plugins/RSSCloud/README b/plugins/RSSCloud/README new file mode 100644 index 0000000000..1237e3e0e2 --- /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 element to your RSS 2.0 profile feeds +that looks like this: + + + +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 0000000000..d454691c80 --- /dev/null +++ b/plugins/RSSCloud/RSSCloudNotifier.php @@ -0,0 +1,240 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @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 + * @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 0000000000..4b9812a479 --- /dev/null +++ b/plugins/RSSCloud/RSSCloudPlugin.php @@ -0,0 +1,279 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @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 + * @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 element to the RSS feed (after the rss + * 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 (). + + $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 0000000000..693dd27c1f --- /dev/null +++ b/plugins/RSSCloud/RSSCloudQueueHandler.php @@ -0,0 +1,78 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +$shortoptions = 'i::'; +$longoptions = array('id::'); + +$helptext = <<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 0000000000..d76c08d379 --- /dev/null +++ b/plugins/RSSCloud/RSSCloudRequestNotify.php @@ -0,0 +1,347 @@ + + * @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 . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Action class to handle RSSCloud notification (subscription) requests + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + **/ + +class 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 . '(?.*)\.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 0000000000..396c604e71 --- /dev/null +++ b/plugins/RSSCloud/RSSCloudSubscription.php @@ -0,0 +1,79 @@ +. + */ + +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 db118dbb81..3665214f85 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(''); + $action->raw(''); 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 602a5bfa88..a33869c19e 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 6bf47071b2..bc4251cf7f 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 329caf4724..8b62a3a967 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 90e7331ca4..c790f1f349 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 92d5449773..a27bd74b80 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,