Merge branch 'master' into 0.9.x

This commit is contained in:
Evan Prodromou 2011-05-04 14:46:36 -07:00
commit 753bc7302a
19 changed files with 367 additions and 91 deletions

93
README
View File

@ -2,8 +2,8 @@
README
------
StatusNet 0.9.6 "Man on the Moon"
29 October 2010
StatusNet 0.9.7 "World Leader Pretend"
17 March 2011
This is the README file for StatusNet, the Open Source microblogging
platform. It includes installation instructions, descriptions of
@ -96,43 +96,47 @@ for additional terms.
New this version
================
This is a security, bug and feature release since version 0.9.5 released on
10 September 2010.
This is a security, bug and feature release since version 0.9.6 released on
23 October 2010.
For best compatibility with client software and site federation, and a lot of
bug fixes, it is highly recommended that all public sites upgrade to the new
version.
For best compatibility with client software and site federation, and a
lot of bug fixes, it is highly recommended that all public sites
upgrade to the new version. Upgrades require new database indexes for
best performance; see Upgrade below.
Notable changes this version:
- Site moderators can now delete groups.
- New themes: clean, shiny, mnml, victorian
- New YammerImport plugin allows site admins to import non-private profiles and
message from an authenticated Yammer site.
- New experimental plugins: AnonFavorites, SlicedFavorites, GroupFavorited,
ForceGroup, ShareNotice
- OAuth upgraded to 1.0a
- Localization updates now include plugins, thanks to translatewiki.net!
- SSL link generation should be more consistent; alternate SSL URLs can be
set in the admin UI for more parts of the system.
- Experimental backupuser.php, restoreuser.php command-line scripts to
dump/restore a user's complete activity stream. Can be used to transfer
accounts manually between sites, or to save a backup before deleting.
- Unicode fixes for OStatus notices
- Header metadata on notice pages to aid in manual reposting on Facebook
- Lots of little fixes...
- GroupPrivateMessage plugin lets users send private messages
to a group. (Similar to "private groups" on Yammer.)
- Support for Twitter streaming API in Twitter bridge plugin
- Support for a new Activity Streams-based API using AtomPub, allowing
richer API data. See http://status.net/wiki/AtomPub for details.
- Unified Facebook plugin, replacing previous Facebook application
and Facebook Connect plugin.
- A plugin to send out a daily summary email to network users.
- In-line thumbnails of some attachments (video, images) and oEmbed objects.
- Local copies of remote profiles to let moderators manage OStatus users.
- Upgrade upstream JS, minify everything.
- Allow pushing plugin JS, CSS, and static files to a CDN.
- Configurable nickname rules.
- Better support for bit.ly URL shortener.
- InProcessCache plugin for additional caching on top of memcached.
- Support for Activity Streams JSON feeds on many streams.
- User-initiated backup and restore of account data in Activity Streams
format.
- Bookmark plugin for making del.icio.us-like social bookmarking sites,
including del.icio.us backup file import. Supports OStatus.
- SQLProfile plugin to tune SQL queries.
- Better sorting on timelines to support restored or imported data.
- Hundreds of translations from http://translatewiki.net/
- Hundreds of performance tunings, bug fixes, and UI improvements.
- Remove deprecated data from Activity Streams Atom output, to the
extent possible.
- NewMenu plugin for new layout of menu items.
- Experimental support for moving an account from one server to
another, using new AtomPub API.
Changes from 0.9.6 release candidate 1:
- fix for broken group pages when logged out
- fix for stuck ping queue entries when bad profile
- fix for bogus single-user nickname config entry error
- i18n updates
- nofollow updates
- SSL-only mode secure cookie fix
- experimental ApiLogger plugin for usage data gathering
- experimental follow-everyone plugin
A full changelog is available at http://status.net/wiki/StatusNet_0.9.6.
A full changelog is available at http://status.net/wiki/StatusNet_0.9.7.
Prerequisites
=============
@ -243,9 +247,9 @@ especially if you've previously installed PHP/MySQL packages.
1. Unpack the tarball you downloaded on your Web server. Usually a
command like this will work:
tar zxf statusnet-0.9.6.tar.gz
tar zxf statusnet-0.9.7.tar.gz
...which will make a statusnet-0.9.6 subdirectory in your current
...which will make a statusnet-0.9.7 subdirectory in your current
directory. (If you don't have shell access on your Web server, you
may have to unpack the tarball on your local computer and FTP the
files to the server.)
@ -253,7 +257,7 @@ especially if you've previously installed PHP/MySQL packages.
2. Move the tarball to a directory of your choosing in your Web root
directory. Usually something like this will work:
mv statusnet-0.9.6 /var/www/statusnet
mv statusnet-0.9.7 /var/www/statusnet
This will make your StatusNet instance available in the statusnet path of
your server, like "http://example.net/statusnet". "microblog" or
@ -668,7 +672,7 @@ with this situation.
If you've been using StatusNet 0.7, 0.6, 0.5 or lower, or if you've
been tracking the "git" version of the software, you will probably
want to upgrade and keep your existing data. There is no automated
upgrade procedure in StatusNet 0.9.6. Try these step-by-step
upgrade procedure in StatusNet 0.9.7. Try these step-by-step
instructions; read to the end first before trying them.
0. Download StatusNet and set up all the prerequisites as if you were
@ -689,25 +693,30 @@ instructions; read to the end first before trying them.
5. Once all writing processes to your site are turned off, make a
final backup of the Web directory and database.
6. Move your StatusNet directory to a backup spot, like "statusnet.bak".
7. Unpack your StatusNet 0.9.6 tarball and move it to "statusnet" or
7. Unpack your StatusNet 0.9.7 tarball and move it to "statusnet" or
wherever your code used to be.
8. Copy the config.php file and the contents of the avatar/, background/,
file/, and local/ subdirectories from your old directory to your new
directory.
9. Copy htaccess.sample to .htaccess in the new directory. Change the
RewriteBase to use the correct path.
10. Rebuild the database. (You can safely skip this step and go to #12
if you're upgrading from another 0.9.x version).
10. Rebuild the database.
NOTE: this step is destructive and cannot be
reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't
do it without a known-good backup!
If your database is at version 0.8.0 or above, you can run a
If your database is at version 0.8.0 or higher in the 0.8.x line, you can run a
special upgrade script:
mysql -u<rootuser> -p<rootpassword> <database> db/08to09.sql
If you are upgrading from any 0.9.x version like 0.9.6, run this script:
mysql -u<rootuser> -p<rootpassword> <database> db/096to097.sql
Despite the name, it should work for any 0.9.x branch.
Otherwise, go to your StatusNet directory and AFTER YOU MAKE A
BACKUP run the rebuilddb.sh script like this:

View File

@ -322,8 +322,11 @@ class ApiTimelineUserAction extends ApiBareAuthAction
$this->clientError(_('Atom post must not be empty.'));
}
$dom = DOMDocument::loadXML($xml);
if (!$dom) {
$old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE));
$dom = new DOMDocument();
$ok = $dom->loadXML($xml);
error_reporting($old);
if (!$ok) {
// TRANS: Client error displayed attempting to post an API that is not well-formed XML.
$this->clientError(_('Atom post must be well-formed XML.'));
}

View File

@ -282,7 +282,11 @@ class RecoverpasswordAction extends Action
$user = User::staticGet('email', common_canonical_email($nore));
if (!$user) {
$user = User::staticGet('nickname', common_canonical_nickname($nore));
try {
$user = User::staticGet('nickname', common_canonical_nickname($nore));
} catch (NicknameException $e) {
// invalid
}
}
# See if it's an unconfirmed email address

View File

@ -445,19 +445,19 @@ class Notice extends Memcached_DataObject
function blowOnInsert($conversation = false)
{
self::blow('profile:notice_ids:%d', $this->profile_id);
$this->blowStream('profile:notice_ids:%d', $this->profile_id);
if ($this->isPublic()) {
self::blow('public');
$this->blowStream('public');
}
// XXX: Before we were blowing the casche only if the notice id
// was not the root of the conversation. What to do now?
self::blow('notice:conversation_ids:%d', $this->conversation);
$this->blowStream('notice:conversation_ids:%d', $this->conversation);
if (!empty($this->repeat_of)) {
self::blow('notice:repeats:%d', $this->repeat_of);
$this->blowStream('notice:repeats:%d', $this->repeat_of);
}
$original = Notice::staticGet('id', $this->repeat_of);
@ -465,11 +465,12 @@ class Notice extends Memcached_DataObject
if (!empty($original)) {
$originalUser = User::staticGet('id', $original->profile_id);
if (!empty($originalUser)) {
self::blow('user:repeats_of_me:%d', $originalUser->id);
$this->blowStream('user:repeats_of_me:%d', $originalUser->id);
}
}
$profile = Profile::staticGet($this->profile_id);
if (!empty($profile)) {
$profile->blowNoticeCount();
}
@ -490,6 +491,42 @@ class Notice extends Memcached_DataObject
}
}
function blowStream()
{
$c = self::memcache();
if (empty($c)) {
return false;
}
$args = func_get_args();
$format = array_shift($args);
$keyPart = vsprintf($format, $args);
$cacheKey = Cache::key($keyPart);
$c->delete($cacheKey);
// delete the "last" stream, too, if this notice is
// older than the top of that stream
$lastKey = $cacheKey.';last';
$lastStr = $c->get($lastKey);
if ($lastStr !== false) {
$window = explode(',', $lastStr);
$lastID = $window[0];
$lastNotice = Notice::staticGet('id', $lastID);
if (empty($lastNotice) // just weird
|| strtotime($lastNotice->created) >= strtotime($this->created)) {
$c->delete($lastKey);
}
}
}
/** save all urls in the notice to the db
*
* follow redirects and save all available file information
@ -1513,22 +1550,22 @@ class Notice extends Memcached_DataObject
}
$laststr = $cache->get($idkey.';last');
if ($laststr !== false) {
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
$last_id, 0, null)));
$new_window = array_merge($new_ids, $window);
$new_windowstr = implode(',', $new_window);
$result = $cache->set($idkey, $new_windowstr);
$result = $cache->set($idkey . ';last', $new_windowstr);
$ids = array_slice($new_window, $offset, $limit);
return $ids;
}

View File

@ -83,15 +83,9 @@ class ActivityImporter extends QueueHandler
Event::handle('EndImportActivity',
array($user, $author, $activity, $trusted));
$done = true;
} catch (ClientException $ce) {
common_log(LOG_WARNING, $ce->getMessage());
$done = true;
} catch (ServerException $se) {
common_log(LOG_ERR, $se->getMessage());
$done = false;
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
$done = false;
$done = true;
}
}
return $done;

View File

@ -22,13 +22,13 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
//exit with 200 response, if this is checking fancy from the installer
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
define('STATUSNET_BASE_VERSION', '0.9.8');
define('STATUSNET_LIFECYCLE', 'dev'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
define('STATUSNET_BASE_VERSION', '0.9.7');
define('STATUSNET_LIFECYCLE', 'fix1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', '' for release
define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . STATUSNET_LIFECYCLE);
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
define('STATUSNET_CODENAME', 'Letter Never Sent');
define('STATUSNET_CODENAME', 'World Leader Pretend');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);

View File

@ -102,7 +102,7 @@ class NoticeSection extends Section
$this->out->elementEnd('p');
$this->out->elementStart('div', 'entry_content');
get_class('NoticeList');
class_exists('NoticeList');
$nli = new NoticeListItem($notice, $this->out);
$nli->showNoticeLink();
$this->out->elementEnd('div');

View File

@ -126,6 +126,15 @@ class Router
return Router::$inst;
}
/**
* Clear the global singleton instance for this class.
* Needed to ensure reset when switching site configurations.
*/
static function clear()
{
Router::$inst = null;
}
function __construct()
{
if (empty($this->m)) {

View File

@ -107,6 +107,8 @@ class StatusNet
*/
public static function init($server=null, $path=null, $conffile=null)
{
Router::clear();
StatusNet::initDefaults($server, $path);
StatusNet::loadConfigFile($conffile);

View File

@ -238,8 +238,23 @@ class Bookmark extends Memcached_DataObject
if (array_key_exists('uri', $options)) {
$nb->uri = $options['uri'];
} else {
$nb->uri = common_local_url('showbookmark',
array('id' => $nb->id));
// FIXME: hacks to work around router bugs in
// queue daemons
$r = Router::get();
$path = $r->build('showbookmark',
array('id' => $nb->id));
if (empty($path)) {
$nb->uri = common_path('bookmark/'.$nb->id, false, false);
} else {
$nb->uri = common_local_url('showbookmark',
array('id' => $nb->id),
null,
null,
false);
}
}
$nb->insert();
@ -314,11 +329,20 @@ class Bookmark extends Memcached_DataObject
$options['uri'] = $nb->uri;
}
$saved = Notice::saveNew($profile->id,
$content,
array_key_exists('source', $options) ?
$options['source'] : 'web',
$options);
try {
$saved = Notice::saveNew($profile->id,
$content,
array_key_exists('source', $options) ?
$options['source'] : 'web',
$options);
} catch (Exception $e) {
$nb->delete();
throw $e;
}
if (empty($saved)) {
$nb->delete();
}
return $saved;
}

View File

@ -450,7 +450,7 @@ class BookmarkPlugin extends Plugin
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'Sample',
$versions[] = array('name' => 'Bookmark',
'version' => self::VERSION,
'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:Bookmark',

View File

@ -79,6 +79,12 @@ class DeliciousBackupImporter extends QueueHandler
$doc = $this->importHTML($body);
// If we can't parse it, it's no good
if (empty($doc)) {
return true;
}
$dls = $doc->getElementsByTagName('dl');
if ($dls->length != 1) {
@ -112,9 +118,11 @@ class DeliciousBackupImporter extends QueueHandler
case 'dd':
$dd = $child;
// This <dd> contains a description for the bookmark in
// the preceding <dt> node.
$saved = $this->importBookmark($user, $dt, $dd);
if (!empty($dt)) {
// This <dd> contains a description for the bookmark in
// the preceding <dt> node.
$saved = $this->importBookmark($user, $dt, $dd);
}
$dt = null;
$dd = null;

View File

@ -526,7 +526,7 @@ ENDOFSCRIPT;
*/
function onEndFavorNotice(Profile $profile, Notice $notice)
{
$client = new Facebookclient($notice);
$client = new Facebookclient($notice, $profile);
$client->like();
return true;
@ -542,7 +542,7 @@ ENDOFSCRIPT;
*/
function onEndDisfavorNotice(Profile $profile, Notice $notice)
{
$client = new Facebookclient($notice);
$client = new Facebookclient($notice, $profile);
$client->unLike();
return true;

View File

@ -48,7 +48,12 @@ class Facebookclient
protected $notice = null; // The user's notice
protected $user = null; // Sender of the notice
function __construct($notice)
/**
*
* @param Notice $notice the notice to manipulate
* @param Profile $profile local user to act as; if left empty, the notice's poster will be used.
*/
function __construct($notice, $profile=null)
{
$this->facebook = self::getFacebook();
@ -60,8 +65,9 @@ class Facebookclient
$this->notice = $notice;
$profile_id = $profile ? $profile->id : $notice->profile_id;
$this->flink = Foreign_link::getByUserID(
$notice->profile_id,
$profile_id,
FACEBOOK_SERVICE
);

View File

@ -127,7 +127,10 @@ class Group_message_profile extends Memcached_DataObject
$gmp->insert();
$gmp->notify();
// If it's not for the author, send email notification
if ($gm->from_profile != $profile->id) {
$gmp->notify();
}
return $gmp;
}
@ -156,9 +159,7 @@ class Group_message_profile extends Memcached_DataObject
// TRANS: Subject for direct-message notification email.
// TRANS: %s is the sending user's nickname.
$subject = sprintf(_('New private message from %s to group %s'), $from->nickname, $group->nickname);
$from_profile = $from->getProfile();
$subject = sprintf(_('New private message from %s to group %s'), $from_profile->nickname, $group->nickname);
// TRANS: Body for direct-message notification email.
// TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname,
@ -174,13 +175,13 @@ class Group_message_profile extends Memcached_DataObject
"With kind regards,\n".
"%6\$s\n"),
$from_profile->getBestName(),
$from->nickname,
$from_profile->nickname,
$group->nickname,
$this->content,
common_local_url('newmessage', array('to' => $from->id)),
$gm->content,
common_local_url('newmessage', array('to' => $from_profile->id)),
common_config('site', 'name'));
$headers = _mail_prepare_headers('message', $to->nickname, $from->nickname);
$headers = _mail_prepare_headers('message', $to->nickname, $from_profile->nickname);
common_switch_locale();

View File

@ -129,9 +129,9 @@ class ModPlusPlugin extends Plugin
* Currently only adds output for remote profiles, nothing for local users.
*
* @param HTMLOutputter $out
* @param Profile $profile
* @param Profile $profile (may also be an ArrayWrapper... sigh)
*/
protected function showProfileOptions(HTMLOutputter $out, Profile $profile)
protected function showProfileOptions(HTMLOutputter $out, $profile)
{
$isRemote = !(User::staticGet('id', $profile->id));
if ($isRemote) {

View File

@ -0,0 +1,92 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Plugin to put the site notice in the sidebar
*
* PHP version 5
*
* 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 Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Put the site notice in the sidebar
*
* @category General
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class SiteNoticeInSidebarPlugin extends Plugin
{
/**
* Function comment
*
* @param
*
* @return
*/
function onStartShowSiteNotice($action)
{
return false;
}
function onStartShowSections($action)
{
$text = common_config('site', 'notice');
if (!empty($text)) {
$sns = new SiteNoticeSection($action, $text);
$sns->show();
}
return true;
}
function onEndShowStyles($action)
{
$action->element('style', null, '#site_notice { width: 100% }');
return true;
}
function onAutoload($cls)
{
$dir = dirname(__FILE__);
switch ($cls)
{
case 'SiteNoticeSection':
include_once $dir . '/'.strtolower($cls).'.php';
return false;
default:
return true;
}
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Site notice section
*
* PHP version 5
*
* 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 Site
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Site notice section
*
* @category Site
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class SiteNoticeSection extends Section
{
var $text;
function __construct($action, $text)
{
parent::__construct($action);
$this->text = $text;
}
function title()
{
return _('Site notice');
}
function showContent()
{
$this->out->raw($this->text);
}
}

View File

@ -320,7 +320,20 @@ function process_error($e, $flink, $notice)
common_log(LOG_WARNING, $logmsg);
// http://dev.twitter.com/pages/responses_errors
switch($code) {
case 400:
// Probably invalid data (bad Unicode chars or coords) that
// cannot be resolved by just sending again.
//
// It could also be rate limiting, but retrying immediately
// won't help much with that, so we'll discard for now.
// If a facility for retrying things later comes up in future,
// we can detect the rate-limiting headers and use that.
//
// Discard the message permanently.
return true;
break;
case 401:
// Probably a revoked or otherwise bad access token - nuke!
remove_twitter_link($flink);
@ -330,6 +343,13 @@ function process_error($e, $flink, $notice)
// User has exceeder her rate limit -- toss the notice
return true;
break;
case 404:
// Resource not found. Shouldn't happen much on posting,
// but just in case!
//
// Consider it a matter for tossing the notice.
return true;
break;
default:
// For every other case, it's probably some flakiness so try