Merge branch 'master' into 0.9.x

Conflicts:
	lib/attachmentlist.php
This commit is contained in:
Evan Prodromou 2010-03-28 15:25:02 -04:00
commit f0d905112e
12 changed files with 124 additions and 140 deletions

87
README
View File

@ -2,8 +2,8 @@
README README
------ ------
StatusNet 0.9.0 ("Stand") StatusNet 0.9.1 ("Everybody Hurts")
4 Mar 2010 28 Mar 2010
This is the README file for StatusNet, the Open Source microblogging This is the README file for StatusNet, the Open Source microblogging
platform. It includes installation instructions, descriptions of platform. It includes installation instructions, descriptions of
@ -77,57 +77,34 @@ for additional terms.
New this version New this version
================ ================
This is a major feature release since version 0.8.3, released Feb 1 This is a minor bug and feature release since version 0.9.0 released 4
2010. It is the final release version of 0.9.0, replacing any beta March 2010.
versions.
Because of fixes to OStatus bugs, it is highly recommended that all
public sites upgrade to the new version immediately.
Notable changes this version: Notable changes this version:
- Support for the new distributed status update standard OStatus - Twitter bridge truncates and links back to original for long
<http://ostatus.org>, based on PubSubHubbub, Salmon, Webfinger, notices.
and Activity Streams. - Changed "Home" link in main menu to "Personal".
- Support for location using the Geolocation API. Notices are (optionally) - A new memcached plugin (using pecl/memcached versus pecl/memcache)
marked with lat-long information with geo microformats, and can be shown - Opt-in subscription to update@status.net
on a map. - Script to run commands on behalf of a user.
- No fixed content size. Notice size is configurable, from 1 to - Better Web UI for long notices.
unlimited number of characters. Default is still 140! - A plugin to open external links in their own window or tab
- An authorization framework, allowing different levels of users. - Fixes to Salmon protocol for compatibility with other systems.
- A Web-based administration panel. - Updates to latest ActivityStreams definition.
- A moderation system that lets site moderators sandbox, silence, - Twitpic-compatible API for image upload.
or delete uncooperative users. - Background deletion of user accounts.
- A flag system that lets users flag profiles for moderator review. - Better support for HTTP basic authentication with CGI/FastCGI
- Support for OAuth <http://oauth.net> authentication in the Twitter - Better discovery on OStatus
API. - Support for PuSH-enabled RSS 2.0 feeds
- User roles system that lets the owner of the site to assign - OpenID-only mode
administrator and moderator roles to other users. - OpenID blacklist/whitelist
- A pluggable authentication system. - OStatus unit tests
- An authentication plugin for LDAP servers.
- Many features that were core in 0.8.x are now plugins, such
as OpenID, Twitter integration, Facebook integration
- A much-improved offline processing system
- In-browser "realtime" updates using a number of realtime
servers (Meteor, Orbited, Cometd)
- A plugin to provide an interface optimized for mobile browsers
- Support for Facebook Connect
- Support for logging in with a Twitter account
- Vastly improved translation with additional languages and
translation in plugins
- Support for all-SSL instances
- Core support for "repeats" (like Twitter's "retweets")
- Pluggable caching system, with plugins for Memcached,
APC, XCache, and a disk-based cache
- Plugin to support RSSCloud
- A framework for adding advertisements to a public site,
and plugins for Google AdSense and OpenX server
- Plugins to throttle excessive subscriptions and registrations.
- A plugin to blacklist particular URLs or nicknames.
There are also literally thousands of bugs fixed and minor features A full changelog is available at http://status.net/wiki/StatusNet_0.9.1.
added. A full changelog is available at http://status.net/wiki/StatusNet_0.9.0.
Under the covers, the software has a vastly improved plugin and
extension mechanism that makes writing powerful and flexible additions
to the core functionality much easier.
Prerequisites Prerequisites
============= =============
@ -239,9 +216,9 @@ especially if you've previously installed PHP/MySQL packages.
1. Unpack the tarball you downloaded on your Web server. Usually a 1. Unpack the tarball you downloaded on your Web server. Usually a
command like this will work: command like this will work:
tar zxf statusnet-0.9.0.tar.gz tar zxf statusnet-0.9.1.tar.gz
...which will make a statusnet-0.9.0 subdirectory in your current ...which will make a statusnet-0.9.1 subdirectory in your current
directory. (If you don't have shell access on your Web server, you 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 may have to unpack the tarball on your local computer and FTP the
files to the server.) files to the server.)
@ -249,7 +226,7 @@ especially if you've previously installed PHP/MySQL packages.
2. Move the tarball to a directory of your choosing in your Web root 2. Move the tarball to a directory of your choosing in your Web root
directory. Usually something like this will work: directory. Usually something like this will work:
mv statusnet-0.9.0 /var/www/statusnet mv statusnet-0.9.1 /var/www/statusnet
This will make your StatusNet instance available in the statusnet path of This will make your StatusNet instance available in the statusnet path of
your server, like "http://example.net/statusnet". "microblog" or your server, like "http://example.net/statusnet". "microblog" or
@ -664,7 +641,7 @@ with this situation.
If you've been using StatusNet 0.7, 0.6, 0.5 or lower, or if you've 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 been tracking the "git" version of the software, you will probably
want to upgrade and keep your existing data. There is no automated want to upgrade and keep your existing data. There is no automated
upgrade procedure in StatusNet 0.9.0. Try these step-by-step upgrade procedure in StatusNet 0.9.1. Try these step-by-step
instructions; read to the end first before trying them. instructions; read to the end first before trying them.
0. Download StatusNet and set up all the prerequisites as if you were 0. Download StatusNet and set up all the prerequisites as if you were
@ -685,7 +662,7 @@ instructions; read to the end first before trying them.
5. Once all writing processes to your site are turned off, make a 5. Once all writing processes to your site are turned off, make a
final backup of the Web directory and database. final backup of the Web directory and database.
6. Move your StatusNet directory to a backup spot, like "statusnet.bak". 6. Move your StatusNet directory to a backup spot, like "statusnet.bak".
7. Unpack your StatusNet 0.9.0 tarball and move it to "statusnet" or 7. Unpack your StatusNet 0.9.1 tarball and move it to "statusnet" or
wherever your code used to be. wherever your code used to be.
8. Copy the config.php file and avatar directory from your old 8. Copy the config.php file and avatar directory from your old
directory to your new directory. directory to your new directory.
@ -1522,7 +1499,7 @@ repository (see below), and you get a compilation error ("unexpected
T_STRING") in the browser, check to see that you don't have any T_STRING") in the browser, check to see that you don't have any
conflicts in your code. conflicts in your code.
If you upgraded to StatusNet 0.9.0 without reading the "Notice If you upgraded to StatusNet 0.9.1 without reading the "Notice
inboxes" section above, and all your users' 'Personal' tabs are empty, inboxes" section above, and all your users' 'Personal' tabs are empty,
read the "Notice inboxes" section above. read the "Notice inboxes" section above.

View File

@ -670,8 +670,12 @@ class User extends Memcached_DataObject
function delete() function delete()
{ {
$profile = $this->getProfile(); try {
$profile->delete(); $profile = $this->getProfile();
$profile->delete();
} catch (UserNoProfileException $unp) {
common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion.");
}
$related = array('Fave', $related = array('Fave',
'Confirm_address', 'Confirm_address',
@ -679,6 +683,7 @@ class User extends Memcached_DataObject
'Foreign_link', 'Foreign_link',
'Invitation', 'Invitation',
); );
Event::handle('UserDeleteRelated', array($this, &$related)); Event::handle('UserDeleteRelated', array($this, &$related));
foreach ($related as $cls) { foreach ($related as $cls) {

View File

@ -179,6 +179,17 @@ class Activity
$this->actor = new ActivityObject($actorEl); $this->actor = new ActivityObject($actorEl);
// Cliqset has bad actor IDs (just nickname of user). We
// work around it by getting the author data and using its
// id instead
if (!preg_match('/^\w+:/', $this->actor->id)) {
$authorEl = ActivityUtils::child($entry, 'author');
if (!empty($authorEl)) {
$authorObj = new ActivityObject($authorEl);
$this->actor->id = $authorObj->id;
}
}
} else if (!empty($feed) && } else if (!empty($feed) &&
$subjectEl = $this->_child($feed, self::SUBJECT)) { $subjectEl = $this->_child($feed, self::SUBJECT)) {

View File

@ -177,10 +177,7 @@ class ActivityObject
$this->type = self::PERSON; // XXX: is this fair? $this->type = self::PERSON; // XXX: is this fair?
$this->title = $this->_childContent($element, self::NAME); $this->title = $this->_childContent($element, self::NAME);
$id = $this->_childContent($element, self::URI); $this->id = $this->_childContent($element, self::URI);
if (ActivityUtils::validateUri($id)) {
$this->id = $id;
}
if (empty($this->id)) { if (empty($this->id)) {
$email = $this->_childContent($element, self::EMAIL); $email = $this->_childContent($element, self::EMAIL);
@ -193,15 +190,6 @@ class ActivityObject
private function _fromAtomEntry($element) private function _fromAtomEntry($element)
{ {
if ($element->localName == 'actor') {
// Old-fashioned <activity:actor>...
// First pull anything from <author>, then we'll add on top.
$author = ActivityUtils::child($element->parentNode, 'author');
if ($author) {
$this->_fromAuthor($author);
}
}
$this->type = $this->_childContent($element, Activity::OBJECTTYPE, $this->type = $this->_childContent($element, Activity::OBJECTTYPE,
Activity::SPEC); Activity::SPEC);
@ -209,11 +197,6 @@ class ActivityObject
$this->type = ActivityObject::NOTE; $this->type = ActivityObject::NOTE;
} }
$id = $this->_childContent($element, self::ID);
if (ActivityUtils::validateUri($id)) {
$this->id = $id;
}
$this->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY); $this->summary = ActivityUtils::childHtmlContent($element, self::SUMMARY);
$this->content = ActivityUtils::getContent($element); $this->content = ActivityUtils::getContent($element);
@ -226,6 +209,12 @@ class ActivityObject
$this->source = $this->_getSource($element); $this->source = $this->_getSource($element);
$this->link = ActivityUtils::getPermalink($element); $this->link = ActivityUtils::getPermalink($element);
$this->id = $this->_childContent($element, self::ID);
if (empty($this->id) && !empty($this->link)) { // fallback if there's no ID
$this->id = $this->link;
}
} }
// @fixme rationalize with Activity::_fromRssItem() // @fixme rationalize with Activity::_fromRssItem()

View File

@ -337,7 +337,7 @@ class Attachment extends AttachmentListItem
$this->showHtmlFile($this->attachment); $this->showHtmlFile($this->attachment);
break; break;
} }
// Fall through to default // Fall through to default.
default: default:
$this->showFallback(); $this->showFallback();

View File

@ -22,10 +22,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
//exit with 200 response, if this is checking fancy from the installer //exit with 200 response, if this is checking fancy from the installer
if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; } if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') { exit; }
define('STATUSNET_VERSION', '0.9.0'); define('STATUSNET_VERSION', '0.9.1');
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
define('STATUSNET_CODENAME', 'Stand'); define('STATUSNET_CODENAME', 'Everybody Hurts');
define('AVATAR_PROFILE_SIZE', 96); define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48); define('AVATAR_STREAM_SIZE', 48);

View File

@ -49,32 +49,26 @@ class BlacklistPlugin extends Plugin
public $urls = array(); public $urls = array();
public $canAdmin = true; public $canAdmin = true;
private $_nicknamePatterns = array(); function _getNicknamePatterns()
private $_urlPatterns = array();
/**
* Initialize the plugin
*
* @return void
*/
function initialize()
{ {
$confNicknames = $this->_configArray('blacklist', 'nicknames'); $confNicknames = $this->_configArray('blacklist', 'nicknames');
$dbNicknames = Nickname_blacklist::getPatterns(); $dbNicknames = Nickname_blacklist::getPatterns();
$this->_nicknamePatterns = array_merge($this->nicknames, return array_merge($this->nicknames,
$confNicknames, $confNicknames,
$dbNicknames); $dbNicknames);
}
function _getUrlPatterns()
{
$confURLs = $this->_configArray('blacklist', 'urls'); $confURLs = $this->_configArray('blacklist', 'urls');
$dbURLs = Homepage_blacklist::getPatterns(); $dbURLs = Homepage_blacklist::getPatterns();
$this->_urlPatterns = array_merge($this->urls, return array_merge($this->urls,
$confURLs, $confURLs,
$dbURLs); $dbURLs);
} }
/** /**
@ -265,8 +259,9 @@ class BlacklistPlugin extends Plugin
private function _checkUrl($url) private function _checkUrl($url)
{ {
foreach ($this->_urlPatterns as $pattern) { $patterns = $this->_getUrlPatterns();
common_debug("Checking $url against $pattern");
foreach ($patterns as $pattern) {
if (preg_match("/$pattern/", $url)) { if (preg_match("/$pattern/", $url)) {
return false; return false;
} }
@ -287,8 +282,9 @@ class BlacklistPlugin extends Plugin
private function _checkNickname($nickname) private function _checkNickname($nickname)
{ {
foreach ($this->_nicknamePatterns as $pattern) { $patterns = $this->_getNicknamePatterns();
common_debug("Checking $nickname against $pattern");
foreach ($patterns as $pattern) {
if (preg_match("/$pattern/", $nickname)) { if (preg_match("/$pattern/", $nickname)) {
return false; return false;
} }

View File

@ -102,7 +102,8 @@ class OStatusPlugin extends Plugin
*/ */
function onStartEnqueueNotice($notice, &$transports) function onStartEnqueueNotice($notice, &$transports)
{ {
$transports[] = 'ostatus'; // put our transport first, in case there's any conflict (like OMB)
array_unshift($transports, 'ostatus');
return true; return true;
} }

View File

@ -129,11 +129,11 @@ class Magicsig extends Memcached_DataObject
public function toString($full_pair = true) public function toString($full_pair = true)
{ {
$mod = base64_url_encode($this->publicKey->modulus->toBytes()); $mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes());
$exp = base64_url_encode($this->publicKey->exponent->toBytes()); $exp = Magicsig::base64_url_encode($this->publicKey->exponent->toBytes());
$private_exp = ''; $private_exp = '';
if ($full_pair && $this->privateKey->exponent->toBytes()) { if ($full_pair && $this->privateKey->exponent->toBytes()) {
$private_exp = '.' . base64_url_encode($this->privateKey->exponent->toBytes()); $private_exp = '.' . Magicsig::base64_url_encode($this->privateKey->exponent->toBytes());
} }
return 'RSA.' . $mod . '.' . $exp . $private_exp; return 'RSA.' . $mod . '.' . $exp . $private_exp;
@ -174,9 +174,9 @@ class Magicsig extends Memcached_DataObject
$rsa = new Crypt_RSA(); $rsa = new Crypt_RSA();
$rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1; $rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
$rsa->setHash('sha256'); $rsa->setHash('sha256');
$rsa->modulus = new Math_BigInteger(base64_url_decode($mod), 256); $rsa->modulus = new Math_BigInteger(Magicsig::base64_url_decode($mod), 256);
$rsa->k = strlen($rsa->modulus->toBytes()); $rsa->k = strlen($rsa->modulus->toBytes());
$rsa->exponent = new Math_BigInteger(base64_url_decode($exp), 256); $rsa->exponent = new Math_BigInteger(Magicsig::base64_url_decode($exp), 256);
if ($type == 'private') { if ($type == 'private') {
$this->privateKey = $rsa; $this->privateKey = $rsa;
@ -203,23 +203,25 @@ class Magicsig extends Memcached_DataObject
public function sign($bytes) public function sign($bytes)
{ {
$sig = $this->privateKey->sign($bytes); $sig = $this->privateKey->sign($bytes);
return base64_url_encode($sig); return Magicsig::base64_url_encode($sig);
} }
public function verify($signed_bytes, $signature) public function verify($signed_bytes, $signature)
{ {
$signature = base64_url_decode($signature); $signature = Magicsig::base64_url_decode($signature);
return $this->publicKey->verify($signed_bytes, $signature); return $this->publicKey->verify($signed_bytes, $signature);
} }
public static function base64_url_encode($input)
{
return strtr(base64_encode($input), '+/', '-_');
}
public static function base64_url_decode($input)
{
return base64_decode(strtr($input, '-_', '+/'));
}
} }
function base64_url_encode($input)
{
return strtr(base64_encode($input), '+/', '-_');
}
function base64_url_decode($input)
{
return base64_decode(strtr($input, '-_', '+/'));
}

View File

@ -83,7 +83,7 @@ class MagicEnvelope
public function signMessage($text, $mimetype, $keypair) public function signMessage($text, $mimetype, $keypair)
{ {
$signature_alg = Magicsig::fromString($keypair); $signature_alg = Magicsig::fromString($keypair);
$armored_text = base64_url_encode($text); $armored_text = Magicsig::base64_url_encode($text);
return array( return array(
'data' => $armored_text, 'data' => $armored_text,
@ -121,7 +121,7 @@ class MagicEnvelope
public function unfold($env) public function unfold($env)
{ {
$dom = new DOMDocument(); $dom = new DOMDocument();
$dom->loadXML(base64_url_decode($env['data'])); $dom->loadXML(Magicsig::base64_url_decode($env['data']));
if ($dom->documentElement->tagName != 'entry') { if ($dom->documentElement->tagName != 'entry') {
return false; return false;
@ -178,7 +178,7 @@ class MagicEnvelope
return false; return false;
} }
$text = base64_url_decode($env['data']); $text = Magicsig::base64_url_decode($env['data']);
$signer_uri = $this->getAuthor($text); $signer_uri = $this->getAuthor($text);
try { try {

View File

@ -20,8 +20,8 @@
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'i:n:'; $shortoptions = 'i:n:o';
$longoptions = array('id=', 'nickname='); $longoptions = array('id=', 'nickname=', 'owner');
$helptext = <<<END_OF_USERROLE_HELP $helptext = <<<END_OF_USERROLE_HELP
command.php [options] [command line] command.php [options] [command line]
@ -29,13 +29,12 @@ Perform commands on behalf of a user, such as sub, unsub, join, drop
-i --id ID of the user -i --id ID of the user
-n --nickname nickname of the user -n --nickname nickname of the user
-o --owner use the site owner
END_OF_USERROLE_HELP; END_OF_USERROLE_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc'; require_once INSTALLDIR.'/scripts/commandline.inc';
function interpretCommand($user, $body) function interpretCommand($user, $body)
{ {
$inter = new CommandInterpreter(); $inter = new CommandInterpreter();
@ -50,8 +49,6 @@ function interpretCommand($user, $body)
} }
} }
if (have_option('i', 'id')) { if (have_option('i', 'id')) {
$id = get_option_value('i', 'id'); $id = get_option_value('i', 'id');
$user = User::staticGet('id', $id); $user = User::staticGet('id', $id);
@ -66,6 +63,12 @@ if (have_option('i', 'id')) {
print "Can't find user with nickname '$nickname'\n"; print "Can't find user with nickname '$nickname'\n";
exit(1); exit(1);
} }
} else if (have_option('o', 'owner')) {
$user = User::siteOwner();
if (empty($user)) {
print "Site has no owner.\n";
exit(1);
}
} else { } else {
print "You must provide either an ID or a nickname.\n\n"; print "You must provide either an ID or a nickname.\n\n";
print $helptext; print $helptext;

View File

@ -22,14 +22,14 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
$this->assertFalse(empty($act)); $this->assertFalse(empty($act));
$this->assertEquals($act->time, 1243860840); $this->assertEquals(1243860840, $act->time);
$this->assertEquals($act->verb, ActivityVerb::POST); $this->assertEquals(ActivityVerb::POST, $act->verb);
$this->assertFalse(empty($act->objects[0])); $this->assertFalse(empty($act->objects[0]));
$this->assertEquals($act->objects[0]->title, 'Punctuation Changeset'); $this->assertEquals('Punctuation Changeset', $act->objects[0]->title);
$this->assertEquals($act->objects[0]->type, 'http://versioncentral.example.org/activity/changeset'); $this->assertEquals('http://versioncentral.example.org/activity/changeset', $act->objects[0]->type);
$this->assertEquals($act->objects[0]->summary, 'Fixing punctuation because it makes it more readable.'); $this->assertEquals('Fixing punctuation because it makes it more readable.', $act->objects[0]->summary);
$this->assertEquals($act->objects[0]->id, 'tag:versioncentral.example.org,2009:/change/1643245'); $this->assertEquals('tag:versioncentral.example.org,2009:/change/1643245', $act->objects[0]->id);
} }
public function testExample3() public function testExample3()
@ -46,22 +46,22 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
$act = new Activity($entry, $feed); $act = new Activity($entry, $feed);
$this->assertFalse(empty($act)); $this->assertFalse(empty($act));
$this->assertEquals($act->time, 1071340202); $this->assertEquals(1071340202, $act->time);
$this->assertEquals($act->link, 'http://example.org/2003/12/13/atom03.html'); $this->assertEquals('http://example.org/2003/12/13/atom03.html', $act->link);
$this->assertEquals($act->verb, ActivityVerb::POST); $this->assertEquals($act->verb, ActivityVerb::POST);
$this->assertFalse(empty($act->actor)); $this->assertFalse(empty($act->actor));
$this->assertEquals($act->actor->type, ActivityObject::PERSON); $this->assertEquals(ActivityObject::PERSON, $act->actor->type);
$this->assertEquals($act->actor->title, 'John Doe'); $this->assertEquals('John Doe', $act->actor->title);
$this->assertEquals($act->actor->id, 'mailto:johndoe@example.com'); $this->assertEquals('mailto:johndoe@example.com', $act->actor->id);
$this->assertFalse(empty($act->objects[0])); $this->assertFalse(empty($act->objects[0]));
$this->assertEquals($act->objects[0]->type, ActivityObject::NOTE); $this->assertEquals(ActivityObject::NOTE, $act->objects[0]->type);
$this->assertEquals($act->objects[0]->id, 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'); $this->assertEquals('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', $act->objects[0]->id);
$this->assertEquals($act->objects[0]->title, 'Atom-Powered Robots Run Amok'); $this->assertEquals('Atom-Powered Robots Run Amok', $act->objects[0]->title);
$this->assertEquals($act->objects[0]->summary, 'Some text.'); $this->assertEquals('Some text.', $act->objects[0]->summary);
$this->assertEquals($act->objects[0]->link, 'http://example.org/2003/12/13/atom03.html'); $this->assertEquals('http://example.org/2003/12/13/atom03.html', $act->objects[0]->link);
$this->assertFalse(empty($act->context)); $this->assertFalse(empty($act->context));