Merge branch 'master' into 0.9.x
Conflicts: lib/attachmentlist.php
This commit is contained in:
commit
f0d905112e
87
README
87
README
@ -2,8 +2,8 @@
|
||||
README
|
||||
------
|
||||
|
||||
StatusNet 0.9.0 ("Stand")
|
||||
4 Mar 2010
|
||||
StatusNet 0.9.1 ("Everybody Hurts")
|
||||
28 Mar 2010
|
||||
|
||||
This is the README file for StatusNet, the Open Source microblogging
|
||||
platform. It includes installation instructions, descriptions of
|
||||
@ -77,57 +77,34 @@ for additional terms.
|
||||
New this version
|
||||
================
|
||||
|
||||
This is a major feature release since version 0.8.3, released Feb 1
|
||||
2010. It is the final release version of 0.9.0, replacing any beta
|
||||
versions.
|
||||
This is a minor bug and feature release since version 0.9.0 released 4
|
||||
March 2010.
|
||||
|
||||
Because of fixes to OStatus bugs, it is highly recommended that all
|
||||
public sites upgrade to the new version immediately.
|
||||
|
||||
Notable changes this version:
|
||||
|
||||
- Support for the new distributed status update standard OStatus
|
||||
<http://ostatus.org>, based on PubSubHubbub, Salmon, Webfinger,
|
||||
and Activity Streams.
|
||||
- Support for location using the Geolocation API. Notices are (optionally)
|
||||
marked with lat-long information with geo microformats, and can be shown
|
||||
on a map.
|
||||
- No fixed content size. Notice size is configurable, from 1 to
|
||||
unlimited number of characters. Default is still 140!
|
||||
- An authorization framework, allowing different levels of users.
|
||||
- A Web-based administration panel.
|
||||
- A moderation system that lets site moderators sandbox, silence,
|
||||
or delete uncooperative users.
|
||||
- A flag system that lets users flag profiles for moderator review.
|
||||
- Support for OAuth <http://oauth.net> authentication in the Twitter
|
||||
API.
|
||||
- User roles system that lets the owner of the site to assign
|
||||
administrator and moderator roles to other users.
|
||||
- A pluggable authentication system.
|
||||
- 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.
|
||||
- Twitter bridge truncates and links back to original for long
|
||||
notices.
|
||||
- Changed "Home" link in main menu to "Personal".
|
||||
- A new memcached plugin (using pecl/memcached versus pecl/memcache)
|
||||
- Opt-in subscription to update@status.net
|
||||
- Script to run commands on behalf of a user.
|
||||
- Better Web UI for long notices.
|
||||
- A plugin to open external links in their own window or tab
|
||||
- Fixes to Salmon protocol for compatibility with other systems.
|
||||
- Updates to latest ActivityStreams definition.
|
||||
- Twitpic-compatible API for image upload.
|
||||
- Background deletion of user accounts.
|
||||
- Better support for HTTP basic authentication with CGI/FastCGI
|
||||
- Better discovery on OStatus
|
||||
- Support for PuSH-enabled RSS 2.0 feeds
|
||||
- OpenID-only mode
|
||||
- OpenID blacklist/whitelist
|
||||
- OStatus unit tests
|
||||
|
||||
There are also literally thousands of bugs fixed and minor features
|
||||
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.
|
||||
A full changelog is available at http://status.net/wiki/StatusNet_0.9.1.
|
||||
|
||||
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
|
||||
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
|
||||
may have to unpack the tarball on your local computer and FTP the
|
||||
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
|
||||
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
|
||||
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
|
||||
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.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.
|
||||
|
||||
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
|
||||
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.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.
|
||||
8. Copy the config.php file and avatar directory from your old
|
||||
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
|
||||
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,
|
||||
read the "Notice inboxes" section above.
|
||||
|
||||
|
@ -670,8 +670,12 @@ class User extends Memcached_DataObject
|
||||
|
||||
function delete()
|
||||
{
|
||||
try {
|
||||
$profile = $this->getProfile();
|
||||
$profile->delete();
|
||||
} catch (UserNoProfileException $unp) {
|
||||
common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion.");
|
||||
}
|
||||
|
||||
$related = array('Fave',
|
||||
'Confirm_address',
|
||||
@ -679,6 +683,7 @@ class User extends Memcached_DataObject
|
||||
'Foreign_link',
|
||||
'Invitation',
|
||||
);
|
||||
|
||||
Event::handle('UserDeleteRelated', array($this, &$related));
|
||||
|
||||
foreach ($related as $cls) {
|
||||
|
@ -179,6 +179,17 @@ class Activity
|
||||
|
||||
$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) &&
|
||||
$subjectEl = $this->_child($feed, self::SUBJECT)) {
|
||||
|
||||
|
@ -177,10 +177,7 @@ class ActivityObject
|
||||
$this->type = self::PERSON; // XXX: is this fair?
|
||||
$this->title = $this->_childContent($element, self::NAME);
|
||||
|
||||
$id = $this->_childContent($element, self::URI);
|
||||
if (ActivityUtils::validateUri($id)) {
|
||||
$this->id = $id;
|
||||
}
|
||||
$this->id = $this->_childContent($element, self::URI);
|
||||
|
||||
if (empty($this->id)) {
|
||||
$email = $this->_childContent($element, self::EMAIL);
|
||||
@ -193,15 +190,6 @@ class ActivityObject
|
||||
|
||||
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,
|
||||
Activity::SPEC);
|
||||
|
||||
@ -209,11 +197,6 @@ class ActivityObject
|
||||
$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->content = ActivityUtils::getContent($element);
|
||||
|
||||
@ -226,6 +209,12 @@ class ActivityObject
|
||||
$this->source = $this->_getSource($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()
|
||||
|
@ -337,7 +337,7 @@ class Attachment extends AttachmentListItem
|
||||
$this->showHtmlFile($this->attachment);
|
||||
break;
|
||||
}
|
||||
// Fall through to default
|
||||
// Fall through to default.
|
||||
|
||||
default:
|
||||
$this->showFallback();
|
||||
|
@ -22,10 +22,10 @@ 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_VERSION', '0.9.0');
|
||||
define('STATUSNET_VERSION', '0.9.1');
|
||||
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
|
||||
|
||||
define('STATUSNET_CODENAME', 'Stand');
|
||||
define('STATUSNET_CODENAME', 'Everybody Hurts');
|
||||
|
||||
define('AVATAR_PROFILE_SIZE', 96);
|
||||
define('AVATAR_STREAM_SIZE', 48);
|
||||
|
@ -49,30 +49,24 @@ class BlacklistPlugin extends Plugin
|
||||
public $urls = array();
|
||||
public $canAdmin = true;
|
||||
|
||||
private $_nicknamePatterns = array();
|
||||
private $_urlPatterns = array();
|
||||
|
||||
/**
|
||||
* Initialize the plugin
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function initialize()
|
||||
function _getNicknamePatterns()
|
||||
{
|
||||
$confNicknames = $this->_configArray('blacklist', 'nicknames');
|
||||
|
||||
$dbNicknames = Nickname_blacklist::getPatterns();
|
||||
|
||||
$this->_nicknamePatterns = array_merge($this->nicknames,
|
||||
return array_merge($this->nicknames,
|
||||
$confNicknames,
|
||||
$dbNicknames);
|
||||
}
|
||||
|
||||
function _getUrlPatterns()
|
||||
{
|
||||
$confURLs = $this->_configArray('blacklist', 'urls');
|
||||
|
||||
$dbURLs = Homepage_blacklist::getPatterns();
|
||||
|
||||
$this->_urlPatterns = array_merge($this->urls,
|
||||
return array_merge($this->urls,
|
||||
$confURLs,
|
||||
$dbURLs);
|
||||
}
|
||||
@ -265,8 +259,9 @@ class BlacklistPlugin extends Plugin
|
||||
|
||||
private function _checkUrl($url)
|
||||
{
|
||||
foreach ($this->_urlPatterns as $pattern) {
|
||||
common_debug("Checking $url against $pattern");
|
||||
$patterns = $this->_getUrlPatterns();
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match("/$pattern/", $url)) {
|
||||
return false;
|
||||
}
|
||||
@ -287,8 +282,9 @@ class BlacklistPlugin extends Plugin
|
||||
|
||||
private function _checkNickname($nickname)
|
||||
{
|
||||
foreach ($this->_nicknamePatterns as $pattern) {
|
||||
common_debug("Checking $nickname against $pattern");
|
||||
$patterns = $this->_getNicknamePatterns();
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match("/$pattern/", $nickname)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -102,7 +102,8 @@ class OStatusPlugin extends Plugin
|
||||
*/
|
||||
function onStartEnqueueNotice($notice, &$transports)
|
||||
{
|
||||
$transports[] = 'ostatus';
|
||||
// put our transport first, in case there's any conflict (like OMB)
|
||||
array_unshift($transports, 'ostatus');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -129,11 +129,11 @@ class Magicsig extends Memcached_DataObject
|
||||
|
||||
public function toString($full_pair = true)
|
||||
{
|
||||
$mod = base64_url_encode($this->publicKey->modulus->toBytes());
|
||||
$exp = base64_url_encode($this->publicKey->exponent->toBytes());
|
||||
$mod = Magicsig::base64_url_encode($this->publicKey->modulus->toBytes());
|
||||
$exp = Magicsig::base64_url_encode($this->publicKey->exponent->toBytes());
|
||||
$private_exp = '';
|
||||
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;
|
||||
@ -174,9 +174,9 @@ class Magicsig extends Memcached_DataObject
|
||||
$rsa = new Crypt_RSA();
|
||||
$rsa->signatureMode = CRYPT_RSA_SIGNATURE_PKCS1;
|
||||
$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->exponent = new Math_BigInteger(base64_url_decode($exp), 256);
|
||||
$rsa->exponent = new Math_BigInteger(Magicsig::base64_url_decode($exp), 256);
|
||||
|
||||
if ($type == 'private') {
|
||||
$this->privateKey = $rsa;
|
||||
@ -203,23 +203,25 @@ class Magicsig extends Memcached_DataObject
|
||||
public function sign($bytes)
|
||||
{
|
||||
$sig = $this->privateKey->sign($bytes);
|
||||
return base64_url_encode($sig);
|
||||
return Magicsig::base64_url_encode($sig);
|
||||
}
|
||||
|
||||
public function verify($signed_bytes, $signature)
|
||||
{
|
||||
$signature = base64_url_decode($signature);
|
||||
$signature = Magicsig::base64_url_decode($signature);
|
||||
return $this->publicKey->verify($signed_bytes, $signature);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function base64_url_encode($input)
|
||||
public static function base64_url_encode($input)
|
||||
{
|
||||
return strtr(base64_encode($input), '+/', '-_');
|
||||
}
|
||||
|
||||
function base64_url_decode($input)
|
||||
public static function base64_url_decode($input)
|
||||
{
|
||||
return base64_decode(strtr($input, '-_', '+/'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -83,7 +83,7 @@ class MagicEnvelope
|
||||
public function signMessage($text, $mimetype, $keypair)
|
||||
{
|
||||
$signature_alg = Magicsig::fromString($keypair);
|
||||
$armored_text = base64_url_encode($text);
|
||||
$armored_text = Magicsig::base64_url_encode($text);
|
||||
|
||||
return array(
|
||||
'data' => $armored_text,
|
||||
@ -121,7 +121,7 @@ class MagicEnvelope
|
||||
public function unfold($env)
|
||||
{
|
||||
$dom = new DOMDocument();
|
||||
$dom->loadXML(base64_url_decode($env['data']));
|
||||
$dom->loadXML(Magicsig::base64_url_decode($env['data']));
|
||||
|
||||
if ($dom->documentElement->tagName != 'entry') {
|
||||
return false;
|
||||
@ -178,7 +178,7 @@ class MagicEnvelope
|
||||
return false;
|
||||
}
|
||||
|
||||
$text = base64_url_decode($env['data']);
|
||||
$text = Magicsig::base64_url_decode($env['data']);
|
||||
$signer_uri = $this->getAuthor($text);
|
||||
|
||||
try {
|
||||
|
@ -20,8 +20,8 @@
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||
|
||||
$shortoptions = 'i:n:';
|
||||
$longoptions = array('id=', 'nickname=');
|
||||
$shortoptions = 'i:n:o';
|
||||
$longoptions = array('id=', 'nickname=', 'owner');
|
||||
|
||||
$helptext = <<<END_OF_USERROLE_HELP
|
||||
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
|
||||
-n --nickname nickname of the user
|
||||
-o --owner use the site owner
|
||||
|
||||
END_OF_USERROLE_HELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
|
||||
|
||||
function interpretCommand($user, $body)
|
||||
{
|
||||
$inter = new CommandInterpreter();
|
||||
@ -50,8 +49,6 @@ function interpretCommand($user, $body)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (have_option('i', 'id')) {
|
||||
$id = get_option_value('i', 'id');
|
||||
$user = User::staticGet('id', $id);
|
||||
@ -66,6 +63,12 @@ if (have_option('i', 'id')) {
|
||||
print "Can't find user with nickname '$nickname'\n";
|
||||
exit(1);
|
||||
}
|
||||
} else if (have_option('o', 'owner')) {
|
||||
$user = User::siteOwner();
|
||||
if (empty($user)) {
|
||||
print "Site has no owner.\n";
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
print "You must provide either an ID or a nickname.\n\n";
|
||||
print $helptext;
|
||||
|
@ -22,14 +22,14 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertFalse(empty($act));
|
||||
|
||||
$this->assertEquals($act->time, 1243860840);
|
||||
$this->assertEquals($act->verb, ActivityVerb::POST);
|
||||
$this->assertEquals(1243860840, $act->time);
|
||||
$this->assertEquals(ActivityVerb::POST, $act->verb);
|
||||
|
||||
$this->assertFalse(empty($act->objects[0]));
|
||||
$this->assertEquals($act->objects[0]->title, 'Punctuation Changeset');
|
||||
$this->assertEquals($act->objects[0]->type, 'http://versioncentral.example.org/activity/changeset');
|
||||
$this->assertEquals($act->objects[0]->summary, 'Fixing punctuation because it makes it more readable.');
|
||||
$this->assertEquals($act->objects[0]->id, 'tag:versioncentral.example.org,2009:/change/1643245');
|
||||
$this->assertEquals('Punctuation Changeset', $act->objects[0]->title);
|
||||
$this->assertEquals('http://versioncentral.example.org/activity/changeset', $act->objects[0]->type);
|
||||
$this->assertEquals('Fixing punctuation because it makes it more readable.', $act->objects[0]->summary);
|
||||
$this->assertEquals('tag:versioncentral.example.org,2009:/change/1643245', $act->objects[0]->id);
|
||||
}
|
||||
|
||||
public function testExample3()
|
||||
@ -46,22 +46,22 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
|
||||
$act = new Activity($entry, $feed);
|
||||
|
||||
$this->assertFalse(empty($act));
|
||||
$this->assertEquals($act->time, 1071340202);
|
||||
$this->assertEquals($act->link, 'http://example.org/2003/12/13/atom03.html');
|
||||
$this->assertEquals(1071340202, $act->time);
|
||||
$this->assertEquals('http://example.org/2003/12/13/atom03.html', $act->link);
|
||||
|
||||
$this->assertEquals($act->verb, ActivityVerb::POST);
|
||||
|
||||
$this->assertFalse(empty($act->actor));
|
||||
$this->assertEquals($act->actor->type, ActivityObject::PERSON);
|
||||
$this->assertEquals($act->actor->title, 'John Doe');
|
||||
$this->assertEquals($act->actor->id, 'mailto:johndoe@example.com');
|
||||
$this->assertEquals(ActivityObject::PERSON, $act->actor->type);
|
||||
$this->assertEquals('John Doe', $act->actor->title);
|
||||
$this->assertEquals('mailto:johndoe@example.com', $act->actor->id);
|
||||
|
||||
$this->assertFalse(empty($act->objects[0]));
|
||||
$this->assertEquals($act->objects[0]->type, ActivityObject::NOTE);
|
||||
$this->assertEquals($act->objects[0]->id, 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a');
|
||||
$this->assertEquals($act->objects[0]->title, 'Atom-Powered Robots Run Amok');
|
||||
$this->assertEquals($act->objects[0]->summary, 'Some text.');
|
||||
$this->assertEquals($act->objects[0]->link, 'http://example.org/2003/12/13/atom03.html');
|
||||
$this->assertEquals(ActivityObject::NOTE, $act->objects[0]->type);
|
||||
$this->assertEquals('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', $act->objects[0]->id);
|
||||
$this->assertEquals('Atom-Powered Robots Run Amok', $act->objects[0]->title);
|
||||
$this->assertEquals('Some text.', $act->objects[0]->summary);
|
||||
$this->assertEquals('http://example.org/2003/12/13/atom03.html', $act->objects[0]->link);
|
||||
|
||||
$this->assertFalse(empty($act->context));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user