We are no longer offering accounts on this server. Consider https://gitlab.freedesktop.org/ as a place to host projects.

Commit daea9887 authored by Evan Prodromou's avatar Evan Prodromou

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

parents f5ec7c27 07214f13
......@@ -501,7 +501,11 @@ class Memcached_DataObject extends Safe_DataObject
function raiseError($message, $type = null, $behaviour = null)
{
throw new ServerException("DB_DataObject error [$type]: $message");
$id = get_class($this);
if ($this->id) {
$id .= ':' . $this->id;
}
throw new ServerException("[$id] DB_DataObject error [$type]: $message");
}
static function cacheGet($keyPart)
......
......@@ -54,7 +54,8 @@ var SN = { // StatusNet
NoticeGeoName: 'notice_data-geo_name',
NoticeDataGeo: 'notice_data-geo',
NoticeDataGeoCookie: 'notice_data-geo_cookie',
NoticeDataGeoSelected: 'notice_data-geo_selected'
NoticeDataGeoSelected: 'notice_data-geo_selected',
StatusNetInstance:'StatusNetInstance'
}
},
......@@ -670,6 +671,35 @@ var SN = { // StatusNet
date.setFullYear(year, month, day);
return date;
},
StatusNetInstance: {
Set: function(value) {
var SNI = SN.U.StatusNetInstance.Get();
if (SNI !== null) {
value = $.extend(SNI, value);
}
$.cookie(
SN.C.S.StatusNetInstance,
JSON.stringify(value),
{
path: '/',
expires: SN.U.GetFullYear(2029, 0, 1)
});
},
Get: function() {
var cookieValue = $.cookie(SN.C.S.StatusNetInstance);
if (cookieValue !== null) {
return JSON.parse(cookieValue);
}
return null;
},
Delete: function() {
$.cookie(SN.C.S.StatusNetInstance, null);
}
}
},
......@@ -707,6 +737,20 @@ var SN = { // StatusNet
SN.U.NewDirectMessage();
}
},
Login: function() {
if (SN.U.StatusNetInstance.Get() !== null) {
var nickname = SN.U.StatusNetInstance.Get().Nickname;
if (nickname !== null) {
$('#form_login #nickname').val(nickname);
}
}
$('#form_login').bind('submit', function() {
SN.U.StatusNetInstance.Set({Nickname: $('#form_login #nickname').val()});
return true;
});
}
}
};
......@@ -721,5 +765,8 @@ $(document).ready(function(){
if ($('#content .entity_actions').length > 0) {
SN.Init.EntityActions();
}
if ($('#form_login').length > 0) {
SN.Init.Login();
}
});
......@@ -34,6 +34,7 @@ if (!defined('STATUSNET')) {
class PoCoURL
{
const URLS = 'urls';
const TYPE = 'type';
const VALUE = 'value';
const PRIMARY = 'primary';
......@@ -55,7 +56,7 @@ class PoCoURL
$xs->elementStart('poco:urls');
$xs->element('poco:type', null, $this->type);
$xs->element('poco:value', null, $this->value);
if ($this->primary) {
if (!empty($this->primary)) {
$xs->element('poco:primary', null, 'true');
}
$xs->elementEnd('poco:urls');
......@@ -70,21 +71,19 @@ class PoCoAddress
public $formatted;
function __construct($formatted)
{
if (empty($formatted)) {
return null;
}
$this->formatted = $formatted;
}
// @todo Other address fields
function asString()
{
$xs = new XMLStringer(true);
$xs->elementStart('poco:address');
$xs->element('poco:formatted', null, $this->formatted);
$xs->elementEnd('poco:address');
return $xs->getString();
if (!empty($this->formatted)) {
$xs = new XMLStringer(true);
$xs->elementStart('poco:address');
$xs->element('poco:formatted', null, $this->formatted);
$xs->elementEnd('poco:address');
return $xs->getString();
}
return null;
}
}
......@@ -92,25 +91,117 @@ class PoCo
{
const NS = 'http://portablecontacts.net/spec/1.0';
const USERNAME = 'preferredUsername';
const NOTE = 'note';
const URLS = 'urls';
const USERNAME = 'preferredUsername';
const DISPLAYNAME = 'displayName';
const NOTE = 'note';
public $preferredUsername;
public $displayName;
public $note;
public $address;
public $urls = array();
function __construct($profile)
function __construct($element = null)
{
if (empty($element)) {
return;
}
$this->preferredUsername = ActivityUtils::childContent(
$element,
self::USERNAME,
self::NS
);
$this->displayName = ActivityUtils::childContent(
$element,
self::DISPLAYNAME,
self::NS
);
$this->note = ActivityUtils::childContent(
$element,
self::NOTE,
self::NS
);
$this->address = $this->_getAddress($element);
$this->urls = $this->_getURLs($element);
}
private function _getURLs($element)
{
$urlEls = $element->getElementsByTagnameNS(self::NS, PoCoURL::URLS);
$urls = array();
foreach ($urlEls as $urlEl) {
$type = ActivityUtils::childContent(
$urlEl,
PoCoURL::TYPE,
PoCo::NS
);
$value = ActivityUtils::childContent(
$urlEl,
PoCoURL::VALUE,
PoCo::NS
);
$primary = ActivityUtils::childContent(
$urlEl,
PoCoURL::PRIMARY,
PoCo::NS
);
array_push($urls, new PoCoURL($type, $value, $primary));
}
return $urls;
}
private function _getAddress($element)
{
$this->preferredUsername = $profile->nickname;
$addressEl = ActivityUtils::child(
$element,
PoCoAddress::ADDRESS,
PoCo::NS
);
$formatted = ActivityUtils::childContent(
$addressEl,
PoCoAddress::FORMATTED,
self::NS
);
if (!empty($formatted)) {
$address = new PoCoAddress();
$address->formatted = $formatted;
return $address;
}
return null;
}
function fromProfile($profile)
{
if (empty($profile)) {
return null;
}
$poco = new PoCo();
$poco->preferredUsername = $profile->nickname;
$poco->displayName = $profile->getBestName();
$this->note = $profile->bio;
$this->address = new PoCoAddress($profile->location);
$poco->note = $profile->bio;
$paddy = new PoCoAddress();
$paddy->formatted = $profile->location;
$poco->address = $paddy;
if (!empty($profile->homepage)) {
array_push(
$this->urls,
$poco->urls,
new PoCoURL(
'homepage',
$profile->homepage,
......@@ -118,6 +209,8 @@ class PoCo
)
);
}
return $poco;
}
function asString()
......@@ -129,6 +222,12 @@ class PoCo
$this->preferredUsername
);
$xs->element(
'poco:displayName',
null,
$this->displayName
);
if (!empty($this->note)) {
$xs->element('poco:note', null, $this->note);
}
......@@ -374,6 +473,8 @@ class ActivityObject
public $source;
public $avatar;
public $geopoint;
public $poco;
public $displayName;
/**
* Constructor
......@@ -426,7 +527,6 @@ class ActivityObject
$this->link = ActivityUtils::getPermalink($element);
// XXX: grab PoCo stuff
}
// Some per-type attributes...
......@@ -434,8 +534,9 @@ class ActivityObject
$this->displayName = $this->title;
// @fixme we may have multiple avatars with different resolutions specified
$this->avatar = ActivityUtils::getLink($element, 'avatar');
$this->nickname = ActivityUtils::childContent($element, PoCo::USERNAME, PoCo::NS);
$this->avatar = ActivityUtils::getLink($element, 'avatar');
$this->poco = new PoCo($element);
}
}
......@@ -490,7 +591,7 @@ class ActivityObject
$object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon;
}
$object->poco = new PoCo($profile);
$object->poco = PoCo::fromProfile($profile);
return $object;
}
......@@ -519,11 +620,19 @@ class ActivityObject
}
if (!empty($this->link)) {
$xs->element('link', array('rel' => 'alternate', 'type' => 'text/html'),
$this->link);
$xs->element(
'link',
array(
'rel' => 'alternate',
'type' => 'text/html',
'href' => $this->link
),
null
);
}
if ($this->type == ActivityObject::PERSON) {
if ($this->type == ActivityObject::PERSON
|| $this->type == ActivityObject::GROUP) {
$xs->element(
'link', array(
'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype,
......@@ -532,7 +641,7 @@ class ActivityObject
? Avatar::defaultImage(AVATAR_PROFILE_SIZE)
: $this->avatar->displayUrl()
),
''
null
);
}
......@@ -848,11 +957,24 @@ class Activity
}
// XXX: add context
// XXX: add target
$xs->elementStart('author');
$xs->element('uri', array(), $this->actor->id);
if ($this->actor->title) {
$xs->element('name', array(), $this->actor->title);
}
$xs->elementEnd('author');
$xs->raw($this->actor->asString('activity:actor'));
$xs->element('activity:verb', null, $this->verb);
$xs->raw($this->object->asString());
if ($this->object) {
$xs->raw($this->object->asString());
}
if ($this->target) {
$xs->raw($this->target->asString('activity:target'));
}
$xs->elementEnd('entry');
......
......@@ -438,14 +438,15 @@ class NoticeListItem extends Widget
$this->out->text(_('at'));
$this->out->text(' ');
if (empty($url)) {
$this->out->element('span', array('class' => 'geo',
$this->out->element('abbr', array('class' => 'geo',
'title' => $latlon),
$name);
} else {
$this->out->element('a', array('class' => 'geo',
'title' => $latlon,
'href' => $url),
$this->out->elementStart('a', array('href' => $url));
$this->out->element('abbr', array('class' => 'geo',
'title' => $latlon),
$name);
$this->out->elementEnd('a');
}
$this->out->elementEnd('span');
}
......
......@@ -78,11 +78,16 @@ class OStatusPlugin extends Plugin
*/
function onEndInitializeQueueManager(QueueManager $qm)
{
// Prepare outgoing distributions after notice save.
$qm->connect('ostatus', 'OStatusQueueHandler');
// Outgoing from our internal PuSH hub
$qm->connect('hubverify', 'HubVerifyQueueHandler');
$qm->connect('hubdistrib', 'HubDistribQueueHandler');
$qm->connect('hubout', 'HubOutQueueHandler');
// Outgoing Salmon replies (when we don't need a return value)
$qm->connect('salmonout', 'SalmonOutQueueHandler');
// Incoming from a foreign PuSH hub
$qm->connect('pushinput', 'PushInputQueueHandler');
return true;
......@@ -93,7 +98,7 @@ class OStatusPlugin extends Plugin
*/
function onStartEnqueueNotice($notice, &$transports)
{
$transports[] = 'hubdistrib';
$transports[] = 'ostatus';
return true;
}
......@@ -199,25 +204,6 @@ class OStatusPlugin extends Plugin
function onEndNoticeSave($notice)
{
$mentioned = $notice->getReplies();
foreach ($mentioned as $profile_id) {
$oprofile = Ostatus_profile::staticGet('profile_id', $profile_id);
if (!empty($oprofile) && !empty($oprofile->salmonuri)) {
common_log(LOG_INFO, "Sending notice '{$notice->uri}' to remote profile '{$oprofile->uri}'.");
// FIXME: this needs to go out in a queue handler
$xml = '<?xml version="1.0" encoding="UTF-8" ?' . '>';
$xml .= $notice->asAtomEntry(true, true);
$salmon = new Salmon();
$salmon->post($oprofile->salmonuri, $xml);
}
}
}
/**
......@@ -295,13 +281,19 @@ class OStatusPlugin extends Plugin
function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
{
if ($notice->source == 'ostatus') {
$bits = parse_url($notice->uri);
$domain = $bits['host'];
$name = $domain;
$url = $notice->uri;
$title = sprintf(_m("Sent from %s via OStatus"), $domain);
return false;
if ($notice->url) {
$bits = parse_url($notice->url);
$domain = $bits['host'];
if (substr($domain, 0, 4) == 'www.') {
$name = substr($domain, 4);
} else {
$name = $domain;
}
$url = $notice->url;
$title = sprintf(_m("Sent from %s via OStatus"), $domain);
return false;
}
}
}
......@@ -316,7 +308,7 @@ class OStatusPlugin extends Plugin
{
$oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
if ($oprofile) {
$oprofile->processFeed($feed);
$oprofile->processFeed($feed, 'push');
} else {
common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri");
}
......@@ -517,12 +509,8 @@ class OStatusPlugin extends Plugin
$oprofile = Ostatus_profile::staticGet('group_id', $group->id);
if ($oprofile) {
// Drop the PuSH subscription if there are no other subscribers.
$oprofile->garbageCollect();
$members = $group->getMembers(0, 1);
if ($members->N == 0) {
common_log(LOG_INFO, "Unsubscribing from now-unused group feed $oprofile->feeduri");
$oprofile->unsubscribe();
}
$member = Profile::staticGet($user->id);
......
......@@ -46,6 +46,11 @@ class GroupsalmonAction extends SalmonAction
$this->clientError(_('No such group.'));
}
$oprofile = Ostatus_profile::staticGet('group_id', $id);
if ($oprofile) {
$this->clientError(_m("Can't accept remote posts for a remote group."));
}
return true;
}
......@@ -74,13 +79,13 @@ class GroupsalmonAction extends SalmonAction
throw new ClientException("Not to the attention of anyone.");
} else {
$uri = common_local_url('groupbyid', array('id' => $this->group->id));
if (!in_array($context->attention, $uri)) {
if (!in_array($uri, $context->attention)) {
throw new ClientException("Not to the attention of this group.");
}
}
$profile = $this->ensureProfile();
// @fixme save the post
$this->saveNotice();
}
/**
......
......@@ -288,10 +288,15 @@ class OStatusSubAction extends Action
}
$this->profile_uri = $profile_uri;
// @fixme validate, normalize bla bla
try {
$oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
$this->oprofile = $oprofile;
if (Validate::email($this->profile_uri)) {
$this->oprofile = Ostatus_profile::ensureWebfinger($this->profile_uri);
} else if (Validate::uri($this->profile_uri)) {
$this->oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
} else {
$this->error = _m("Invalid address format.");
return false;
}
return true;
} catch (FeedSubBadURLException $e) {
$this->error = _m('Invalid URL or could not reach server.');
......
......@@ -65,6 +65,21 @@ class WebfingerAction extends Action
'format' => 'atom')),
'type' => 'application/atom+xml');
// hCard
$xrd->links[] = array('rel' => 'http://microformats.org/profile/hcard',
'type' => 'text/html',
'href' => common_profile_url($nick));
// XFN
$xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11',
'type' => 'text/html',
'href' => common_profile_url($nick));
// FOAF
$xrd->links[] = array('rel' => 'describedby',
'type' => 'application/rdf+xml',
'href' => common_local_url('foaf',
array('nickname' => $nick)));
$salmon_url = common_local_url('salmon',
array('id' => $this->user->id));
......
......@@ -33,6 +33,7 @@ class Ostatus_profile extends Memcached_DataObject
public $feeduri;
public $salmonuri;
public $avatar; // remote URL of the last avatar we saved
public $created;
public $modified;
......@@ -58,6 +59,7 @@ class Ostatus_profile extends Memcached_DataObject
'group_id' => DB_DATAOBJECT_INT,
'feeduri' => DB_DATAOBJECT_STR,
'salmonuri' => DB_DATAOBJECT_STR,
'avatar' => DB_DATAOBJECT_STR,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
}
......@@ -74,6 +76,8 @@ class Ostatus_profile extends Memcached_DataObject
255, true, 'UNI'),
new ColumnDef('salmonuri', 'text',
null, true),
new ColumnDef('avatar', 'text',
null, true),
new ColumnDef('created', 'datetime',
null, false),
new ColumnDef('modified', 'datetime',
......@@ -488,7 +492,7 @@ class Ostatus_profile extends Memcached_DataObject
*
* @param DOMDocument $feed
*/
public function processFeed($feed)
public function processFeed($feed, $source)
{
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
if ($entries->length == 0) {
......@@ -498,7 +502,7 @@ class Ostatus_profile extends Memcached_DataObject
for ($i = 0; $i < $entries->length; $i++) {
$entry = $entries->item($i);
$this->processEntry($entry, $feed);
$this->processEntry($entry, $feed, $source);
}
}
......@@ -508,15 +512,12 @@ class Ostatus_profile extends Memcached_DataObject
* @param DOMElement $entry
* @param DOMElement $feed for context
*/
protected function processEntry($entry, $feed)
public function processEntry($entry, $feed, $source)
{
$activity = new Activity($entry, $feed);
$debug = var_export($activity, true);
common_log(LOG_DEBUG, $debug);
if ($activity->verb == ActivityVerb::POST) {
$this->processPost($activity);
$this->processPost($activity, $source);
} else {
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
}
......@@ -525,130 +526,190 @@ class Ostatus_profile extends Memcached_DataObject
/**
* Process an incoming post activity from this remote feed.
* @param Activity $activity
* @param string $method 'push' or 'salmon'
* @return mixed saved Notice or false
* @fixme break up this function, it's getting nasty long
*/
protected function processPost($activity)
public function processPost($activity, $method)
{
if ($this->isGroup()) {
// A group feed will contain posts from multiple authors.
// @fixme validate these profiles in some way!
$oprofile = self::ensureActorProfile($activity);
if ($oprofile->isGroup()) {
// Groups can't post notices in StatusNet.
common_log(LOG_WARNING, "OStatus: skipping post with group listed as author: $oprofile->uri in feed from $this->uri");
return false;