Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing
This commit is contained in:
commit
daea988786
@ -501,7 +501,11 @@ class Memcached_DataObject extends Safe_DataObject
|
|||||||
|
|
||||||
function raiseError($message, $type = null, $behaviour = null)
|
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)
|
static function cacheGet($keyPart)
|
||||||
|
49
js/util.js
49
js/util.js
@ -54,7 +54,8 @@ var SN = { // StatusNet
|
|||||||
NoticeGeoName: 'notice_data-geo_name',
|
NoticeGeoName: 'notice_data-geo_name',
|
||||||
NoticeDataGeo: 'notice_data-geo',
|
NoticeDataGeo: 'notice_data-geo',
|
||||||
NoticeDataGeoCookie: 'notice_data-geo_cookie',
|
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);
|
date.setFullYear(year, month, day);
|
||||||
|
|
||||||
return date;
|
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();
|
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) {
|
if ($('#content .entity_actions').length > 0) {
|
||||||
SN.Init.EntityActions();
|
SN.Init.EntityActions();
|
||||||
}
|
}
|
||||||
|
if ($('#form_login').length > 0) {
|
||||||
|
SN.Init.Login();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
166
lib/activity.php
166
lib/activity.php
@ -34,6 +34,7 @@ if (!defined('STATUSNET')) {
|
|||||||
|
|
||||||
class PoCoURL
|
class PoCoURL
|
||||||
{
|
{
|
||||||
|
const URLS = 'urls';
|
||||||
const TYPE = 'type';
|
const TYPE = 'type';
|
||||||
const VALUE = 'value';
|
const VALUE = 'value';
|
||||||
const PRIMARY = 'primary';
|
const PRIMARY = 'primary';
|
||||||
@ -55,7 +56,7 @@ class PoCoURL
|
|||||||
$xs->elementStart('poco:urls');
|
$xs->elementStart('poco:urls');
|
||||||
$xs->element('poco:type', null, $this->type);
|
$xs->element('poco:type', null, $this->type);
|
||||||
$xs->element('poco:value', null, $this->value);
|
$xs->element('poco:value', null, $this->value);
|
||||||
if ($this->primary) {
|
if (!empty($this->primary)) {
|
||||||
$xs->element('poco:primary', null, 'true');
|
$xs->element('poco:primary', null, 'true');
|
||||||
}
|
}
|
||||||
$xs->elementEnd('poco:urls');
|
$xs->elementEnd('poco:urls');
|
||||||
@ -70,22 +71,20 @@ class PoCoAddress
|
|||||||
|
|
||||||
public $formatted;
|
public $formatted;
|
||||||
|
|
||||||
function __construct($formatted)
|
// @todo Other address fields
|
||||||
{
|
|
||||||
if (empty($formatted)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$this->formatted = $formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
function asString()
|
function asString()
|
||||||
{
|
{
|
||||||
|
if (!empty($this->formatted)) {
|
||||||
$xs = new XMLStringer(true);
|
$xs = new XMLStringer(true);
|
||||||
$xs->elementStart('poco:address');
|
$xs->elementStart('poco:address');
|
||||||
$xs->element('poco:formatted', null, $this->formatted);
|
$xs->element('poco:formatted', null, $this->formatted);
|
||||||
$xs->elementEnd('poco:address');
|
$xs->elementEnd('poco:address');
|
||||||
return $xs->getString();
|
return $xs->getString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PoCo
|
class PoCo
|
||||||
@ -93,24 +92,116 @@ class PoCo
|
|||||||
const NS = 'http://portablecontacts.net/spec/1.0';
|
const NS = 'http://portablecontacts.net/spec/1.0';
|
||||||
|
|
||||||
const USERNAME = 'preferredUsername';
|
const USERNAME = 'preferredUsername';
|
||||||
|
const DISPLAYNAME = 'displayName';
|
||||||
const NOTE = 'note';
|
const NOTE = 'note';
|
||||||
const URLS = 'urls';
|
|
||||||
|
|
||||||
public $preferredUsername;
|
public $preferredUsername;
|
||||||
|
public $displayName;
|
||||||
public $note;
|
public $note;
|
||||||
public $address;
|
public $address;
|
||||||
public $urls = array();
|
public $urls = array();
|
||||||
|
|
||||||
function __construct($profile)
|
function __construct($element = null)
|
||||||
{
|
{
|
||||||
$this->preferredUsername = $profile->nickname;
|
if (empty($element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->note = $profile->bio;
|
$this->preferredUsername = ActivityUtils::childContent(
|
||||||
$this->address = new PoCoAddress($profile->location);
|
$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)
|
||||||
|
{
|
||||||
|
$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();
|
||||||
|
|
||||||
|
$poco->note = $profile->bio;
|
||||||
|
|
||||||
|
$paddy = new PoCoAddress();
|
||||||
|
$paddy->formatted = $profile->location;
|
||||||
|
$poco->address = $paddy;
|
||||||
|
|
||||||
if (!empty($profile->homepage)) {
|
if (!empty($profile->homepage)) {
|
||||||
array_push(
|
array_push(
|
||||||
$this->urls,
|
$poco->urls,
|
||||||
new PoCoURL(
|
new PoCoURL(
|
||||||
'homepage',
|
'homepage',
|
||||||
$profile->homepage,
|
$profile->homepage,
|
||||||
@ -118,6 +209,8 @@ class PoCo
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $poco;
|
||||||
}
|
}
|
||||||
|
|
||||||
function asString()
|
function asString()
|
||||||
@ -129,6 +222,12 @@ class PoCo
|
|||||||
$this->preferredUsername
|
$this->preferredUsername
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$xs->element(
|
||||||
|
'poco:displayName',
|
||||||
|
null,
|
||||||
|
$this->displayName
|
||||||
|
);
|
||||||
|
|
||||||
if (!empty($this->note)) {
|
if (!empty($this->note)) {
|
||||||
$xs->element('poco:note', null, $this->note);
|
$xs->element('poco:note', null, $this->note);
|
||||||
}
|
}
|
||||||
@ -374,6 +473,8 @@ class ActivityObject
|
|||||||
public $source;
|
public $source;
|
||||||
public $avatar;
|
public $avatar;
|
||||||
public $geopoint;
|
public $geopoint;
|
||||||
|
public $poco;
|
||||||
|
public $displayName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -426,7 +527,6 @@ class ActivityObject
|
|||||||
|
|
||||||
$this->link = ActivityUtils::getPermalink($element);
|
$this->link = ActivityUtils::getPermalink($element);
|
||||||
|
|
||||||
// XXX: grab PoCo stuff
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some per-type attributes...
|
// Some per-type attributes...
|
||||||
@ -435,7 +535,8 @@ class ActivityObject
|
|||||||
|
|
||||||
// @fixme we may have multiple avatars with different resolutions specified
|
// @fixme we may have multiple avatars with different resolutions specified
|
||||||
$this->avatar = ActivityUtils::getLink($element, 'avatar');
|
$this->avatar = ActivityUtils::getLink($element, 'avatar');
|
||||||
$this->nickname = ActivityUtils::childContent($element, PoCo::USERNAME, PoCo::NS);
|
|
||||||
|
$this->poco = new PoCo($element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +591,7 @@ class ActivityObject
|
|||||||
$object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon;
|
$object->geopoint = (float)$profile->lat . ' ' . (float)$profile->lon;
|
||||||
}
|
}
|
||||||
|
|
||||||
$object->poco = new PoCo($profile);
|
$object->poco = PoCo::fromProfile($profile);
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
@ -519,11 +620,19 @@ class ActivityObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($this->link)) {
|
if (!empty($this->link)) {
|
||||||
$xs->element('link', array('rel' => 'alternate', 'type' => 'text/html'),
|
$xs->element(
|
||||||
$this->link);
|
'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(
|
$xs->element(
|
||||||
'link', array(
|
'link', array(
|
||||||
'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype,
|
'type' => empty($this->avatar) ? 'image/png' : $this->avatar->mediatype,
|
||||||
@ -532,7 +641,7 @@ class ActivityObject
|
|||||||
? Avatar::defaultImage(AVATAR_PROFILE_SIZE)
|
? Avatar::defaultImage(AVATAR_PROFILE_SIZE)
|
||||||
: $this->avatar->displayUrl()
|
: $this->avatar->displayUrl()
|
||||||
),
|
),
|
||||||
''
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -848,11 +957,24 @@ class Activity
|
|||||||
}
|
}
|
||||||
|
|
||||||
// XXX: add context
|
// 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->raw($this->actor->asString('activity:actor'));
|
||||||
|
|
||||||
$xs->element('activity:verb', null, $this->verb);
|
$xs->element('activity:verb', null, $this->verb);
|
||||||
|
|
||||||
|
if ($this->object) {
|
||||||
$xs->raw($this->object->asString());
|
$xs->raw($this->object->asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->target) {
|
||||||
|
$xs->raw($this->target->asString('activity:target'));
|
||||||
|
}
|
||||||
|
|
||||||
$xs->elementEnd('entry');
|
$xs->elementEnd('entry');
|
||||||
|
|
||||||
|
@ -438,14 +438,15 @@ class NoticeListItem extends Widget
|
|||||||
$this->out->text(_('at'));
|
$this->out->text(_('at'));
|
||||||
$this->out->text(' ');
|
$this->out->text(' ');
|
||||||
if (empty($url)) {
|
if (empty($url)) {
|
||||||
$this->out->element('span', array('class' => 'geo',
|
$this->out->element('abbr', array('class' => 'geo',
|
||||||
'title' => $latlon),
|
'title' => $latlon),
|
||||||
$name);
|
$name);
|
||||||
} else {
|
} else {
|
||||||
$this->out->element('a', array('class' => 'geo',
|
$this->out->elementStart('a', array('href' => $url));
|
||||||
'title' => $latlon,
|
$this->out->element('abbr', array('class' => 'geo',
|
||||||
'href' => $url),
|
'title' => $latlon),
|
||||||
$name);
|
$name);
|
||||||
|
$this->out->elementEnd('a');
|
||||||
}
|
}
|
||||||
$this->out->elementEnd('span');
|
$this->out->elementEnd('span');
|
||||||
}
|
}
|
||||||
|
@ -78,11 +78,16 @@ class OStatusPlugin extends Plugin
|
|||||||
*/
|
*/
|
||||||
function onEndInitializeQueueManager(QueueManager $qm)
|
function onEndInitializeQueueManager(QueueManager $qm)
|
||||||
{
|
{
|
||||||
|
// Prepare outgoing distributions after notice save.
|
||||||
|
$qm->connect('ostatus', 'OStatusQueueHandler');
|
||||||
|
|
||||||
// Outgoing from our internal PuSH hub
|
// Outgoing from our internal PuSH hub
|
||||||
$qm->connect('hubverify', 'HubVerifyQueueHandler');
|
$qm->connect('hubverify', 'HubVerifyQueueHandler');
|
||||||
$qm->connect('hubdistrib', 'HubDistribQueueHandler');
|
|
||||||
$qm->connect('hubout', 'HubOutQueueHandler');
|
$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
|
// Incoming from a foreign PuSH hub
|
||||||
$qm->connect('pushinput', 'PushInputQueueHandler');
|
$qm->connect('pushinput', 'PushInputQueueHandler');
|
||||||
return true;
|
return true;
|
||||||
@ -93,7 +98,7 @@ class OStatusPlugin extends Plugin
|
|||||||
*/
|
*/
|
||||||
function onStartEnqueueNotice($notice, &$transports)
|
function onStartEnqueueNotice($notice, &$transports)
|
||||||
{
|
{
|
||||||
$transports[] = 'hubdistrib';
|
$transports[] = 'ostatus';
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,25 +204,6 @@ class OStatusPlugin extends Plugin
|
|||||||
|
|
||||||
function onEndNoticeSave($notice)
|
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,15 +281,21 @@ class OStatusPlugin extends Plugin
|
|||||||
function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
|
function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
|
||||||
{
|
{
|
||||||
if ($notice->source == 'ostatus') {
|
if ($notice->source == 'ostatus') {
|
||||||
$bits = parse_url($notice->uri);
|
if ($notice->url) {
|
||||||
|
$bits = parse_url($notice->url);
|
||||||
$domain = $bits['host'];
|
$domain = $bits['host'];
|
||||||
|
if (substr($domain, 0, 4) == 'www.') {
|
||||||
|
$name = substr($domain, 4);
|
||||||
|
} else {
|
||||||
$name = $domain;
|
$name = $domain;
|
||||||
$url = $notice->uri;
|
}
|
||||||
|
|
||||||
|
$url = $notice->url;
|
||||||
$title = sprintf(_m("Sent from %s via OStatus"), $domain);
|
$title = sprintf(_m("Sent from %s via OStatus"), $domain);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send incoming PuSH feeds for OStatus endpoints in for processing.
|
* Send incoming PuSH feeds for OStatus endpoints in for processing.
|
||||||
@ -316,7 +308,7 @@ class OStatusPlugin extends Plugin
|
|||||||
{
|
{
|
||||||
$oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
|
$oprofile = Ostatus_profile::staticGet('feeduri', $feedsub->uri);
|
||||||
if ($oprofile) {
|
if ($oprofile) {
|
||||||
$oprofile->processFeed($feed);
|
$oprofile->processFeed($feed, 'push');
|
||||||
} else {
|
} else {
|
||||||
common_log(LOG_DEBUG, "No ostatus profile for incoming feed $feedsub->uri");
|
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);
|
$oprofile = Ostatus_profile::staticGet('group_id', $group->id);
|
||||||
if ($oprofile) {
|
if ($oprofile) {
|
||||||
// Drop the PuSH subscription if there are no other subscribers.
|
// 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);
|
$member = Profile::staticGet($user->id);
|
||||||
|
|
||||||
|
@ -46,6 +46,11 @@ class GroupsalmonAction extends SalmonAction
|
|||||||
$this->clientError(_('No such group.'));
|
$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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,13 +79,13 @@ class GroupsalmonAction extends SalmonAction
|
|||||||
throw new ClientException("Not to the attention of anyone.");
|
throw new ClientException("Not to the attention of anyone.");
|
||||||
} else {
|
} else {
|
||||||
$uri = common_local_url('groupbyid', array('id' => $this->group->id));
|
$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.");
|
throw new ClientException("Not to the attention of this group.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$profile = $this->ensureProfile();
|
$profile = $this->ensureProfile();
|
||||||
// @fixme save the post
|
$this->saveNotice();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,10 +288,15 @@ class OStatusSubAction extends Action
|
|||||||
}
|
}
|
||||||
$this->profile_uri = $profile_uri;
|
$this->profile_uri = $profile_uri;
|
||||||
|
|
||||||
// @fixme validate, normalize bla bla
|
|
||||||
try {
|
try {
|
||||||
$oprofile = Ostatus_profile::ensureProfile($this->profile_uri);
|
if (Validate::email($this->profile_uri)) {
|
||||||
$this->oprofile = $oprofile;
|
$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;
|
return true;
|
||||||
} catch (FeedSubBadURLException $e) {
|
} catch (FeedSubBadURLException $e) {
|
||||||
$this->error = _m('Invalid URL or could not reach server.');
|
$this->error = _m('Invalid URL or could not reach server.');
|
||||||
|
@ -65,6 +65,21 @@ class WebfingerAction extends Action
|
|||||||
'format' => 'atom')),
|
'format' => 'atom')),
|
||||||
'type' => 'application/atom+xml');
|
'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',
|
$salmon_url = common_local_url('salmon',
|
||||||
array('id' => $this->user->id));
|
array('id' => $this->user->id));
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
|
|
||||||
public $feeduri;
|
public $feeduri;
|
||||||
public $salmonuri;
|
public $salmonuri;
|
||||||
|
public $avatar; // remote URL of the last avatar we saved
|
||||||
|
|
||||||
public $created;
|
public $created;
|
||||||
public $modified;
|
public $modified;
|
||||||
@ -58,6 +59,7 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
'group_id' => DB_DATAOBJECT_INT,
|
'group_id' => DB_DATAOBJECT_INT,
|
||||||
'feeduri' => DB_DATAOBJECT_STR,
|
'feeduri' => DB_DATAOBJECT_STR,
|
||||||
'salmonuri' => DB_DATAOBJECT_STR,
|
'salmonuri' => DB_DATAOBJECT_STR,
|
||||||
|
'avatar' => DB_DATAOBJECT_STR,
|
||||||
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
|
'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);
|
'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'),
|
255, true, 'UNI'),
|
||||||
new ColumnDef('salmonuri', 'text',
|
new ColumnDef('salmonuri', 'text',
|
||||||
null, true),
|
null, true),
|
||||||
|
new ColumnDef('avatar', 'text',
|
||||||
|
null, true),
|
||||||
new ColumnDef('created', 'datetime',
|
new ColumnDef('created', 'datetime',
|
||||||
null, false),
|
null, false),
|
||||||
new ColumnDef('modified', 'datetime',
|
new ColumnDef('modified', 'datetime',
|
||||||
@ -488,7 +492,7 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
*
|
*
|
||||||
* @param DOMDocument $feed
|
* @param DOMDocument $feed
|
||||||
*/
|
*/
|
||||||
public function processFeed($feed)
|
public function processFeed($feed, $source)
|
||||||
{
|
{
|
||||||
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
|
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
|
||||||
if ($entries->length == 0) {
|
if ($entries->length == 0) {
|
||||||
@ -498,7 +502,7 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
|
|
||||||
for ($i = 0; $i < $entries->length; $i++) {
|
for ($i = 0; $i < $entries->length; $i++) {
|
||||||
$entry = $entries->item($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 $entry
|
||||||
* @param DOMElement $feed for context
|
* @param DOMElement $feed for context
|
||||||
*/
|
*/
|
||||||
protected function processEntry($entry, $feed)
|
public function processEntry($entry, $feed, $source)
|
||||||
{
|
{
|
||||||
$activity = new Activity($entry, $feed);
|
$activity = new Activity($entry, $feed);
|
||||||
|
|
||||||
$debug = var_export($activity, true);
|
|
||||||
common_log(LOG_DEBUG, $debug);
|
|
||||||
|
|
||||||
if ($activity->verb == ActivityVerb::POST) {
|
if ($activity->verb == ActivityVerb::POST) {
|
||||||
$this->processPost($activity);
|
$this->processPost($activity, $source);
|
||||||
} else {
|
} else {
|
||||||
common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb");
|
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.
|
* Process an incoming post activity from this remote feed.
|
||||||
* @param Activity $activity
|
* @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
|
* @fixme break up this function, it's getting nasty long
|
||||||
*/
|
*/
|
||||||
protected function processPost($activity)
|
public function processPost($activity, $method)
|
||||||
{
|
{
|
||||||
if ($this->isGroup()) {
|
if ($this->isGroup()) {
|
||||||
|
// A group feed will contain posts from multiple authors.
|
||||||
// @fixme validate these profiles in some way!
|
// @fixme validate these profiles in some way!
|
||||||
$oprofile = self::ensureActorProfile($activity);
|
$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;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Individual user feeds may contain only posts from themselves.
|
||||||
|
// Authorship is validated against the profile URI on upper layers,
|
||||||
|
// through PuSH setup or Salmon signature checks.
|
||||||
$actorUri = self::getActorProfileURI($activity);
|
$actorUri = self::getActorProfileURI($activity);
|
||||||
if ($actorUri == $this->uri) {
|
if ($actorUri == $this->uri) {
|
||||||
// @fixme check if profile info has changed and update it
|
// Check if profile info has changed and update it
|
||||||
|
$this->updateFromActivityObject($activity->actor);
|
||||||
} else {
|
} else {
|
||||||
// @fixme drop or reject the messages once we've got the canonical profile URI recorded sanely
|
common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri");
|
||||||
common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->uri");
|
return false;
|
||||||
//return;
|
|
||||||
}
|
}
|
||||||
$oprofile = $this;
|
$oprofile = $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The id URI will be used as a unique identifier for for the notice,
|
||||||
|
// protecting against duplicate saves. It isn't required to be a URL;
|
||||||
|
// tag: URIs for instance are found in Google Buzz feeds.
|
||||||
$sourceUri = $activity->object->id;
|
$sourceUri = $activity->object->id;
|
||||||
|
|
||||||
$dupe = Notice::staticGet('uri', $sourceUri);
|
$dupe = Notice::staticGet('uri', $sourceUri);
|
||||||
|
|
||||||
if ($dupe) {
|
if ($dupe) {
|
||||||
common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri");
|
common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll also want to save a web link to the original notice, if provided.
|
||||||
$sourceUrl = null;
|
$sourceUrl = null;
|
||||||
|
|
||||||
if ($activity->object->link) {
|
if ($activity->object->link) {
|
||||||
$sourceUrl = $activity->object->link;
|
$sourceUrl = $activity->object->link;
|
||||||
|
} else if ($activity->link) {
|
||||||
|
$sourceUrl = $activity->link;
|
||||||
} else if (preg_match('!^https?://!', $activity->object->id)) {
|
} else if (preg_match('!^https?://!', $activity->object->id)) {
|
||||||
$sourceUrl = $activity->object->id;
|
$sourceUrl = $activity->object->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @fixme sanitize and save HTML content if available
|
// Get (safe!) HTML and text versions of the content
|
||||||
|
$rendered = $this->purify($activity->object->content);
|
||||||
|
$content = html_entity_decode(strip_tags($rendered));
|
||||||
|
|
||||||
$content = $activity->object->title;
|
$options = array('is_local' => Notice::REMOTE_OMB,
|
||||||
|
|
||||||
$params = array('is_local' => Notice::REMOTE_OMB,
|
|
||||||
'url' => $sourceUrl,
|
'url' => $sourceUrl,
|
||||||
'uri' => $sourceUri);
|
'uri' => $sourceUri,
|
||||||
|
'rendered' => $rendered,
|
||||||
|
'replies' => array(),
|
||||||
|
'groups' => array());
|
||||||
|
|
||||||
|
// Check for optional attributes...
|
||||||
|
|
||||||
|
if (!empty($activity->time)) {
|
||||||
|
$options['created'] = common_sql_date($activity->time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($activity->context) {
|
||||||
|
// Any individual or group attn: targets?
|
||||||
|
$replies = $activity->context->attention;
|
||||||
|
$options['groups'] = $this->filterReplies($oprofile, $replies);
|
||||||
|
$options['replies'] = $replies;
|
||||||
|
|
||||||
|
// Maintain direct reply associations
|
||||||
|
// @fixme what about conversation ID?
|
||||||
|
if (!empty($activity->context->replyToID)) {
|
||||||
|
$orig = Notice::staticGet('uri',
|
||||||
|
$activity->context->replyToID);
|
||||||
|
if (!empty($orig)) {
|
||||||
|
$options['reply_to'] = $orig->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$location = $activity->context->location;
|
$location = $activity->context->location;
|
||||||
|
|
||||||
if ($location) {
|
if ($location) {
|
||||||
$params['lat'] = $location->lat;
|
$options['lat'] = $location->lat;
|
||||||
$params['lon'] = $location->lon;
|
$options['lon'] = $location->lon;
|
||||||
if ($location->location_id) {
|
if ($location->location_id) {
|
||||||
$params['location_ns'] = $location->location_ns;
|
$options['location_ns'] = $location->location_ns;
|
||||||
$params['location_id'] = $location->location_id;
|
$options['location_id'] = $location->location_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$profile = $oprofile->localProfile();
|
try {
|
||||||
$params['groups'] = array();
|
$saved = Notice::saveNew($oprofile->profile_id,
|
||||||
$params['replies'] = array();
|
$content,
|
||||||
if ($activity->context) {
|
'ostatus',
|
||||||
foreach ($activity->context->attention as $recipient) {
|
$options);
|
||||||
$roprofile = Ostatus_profile::staticGet('uri', $recipient);
|
if ($saved) {
|
||||||
if ($roprofile) {
|
Ostatus_source::saveNew($saved, $this, $method);
|
||||||
if ($roprofile->isGroup()) {
|
|
||||||
// Deliver to local recipients of this remote group.
|
|
||||||
// @fixme sender verification?
|
|
||||||
$params['groups'][] = $roprofile->group_id;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// Delivery to remote users is the source service's job.
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
common_log(LOG_ERR, "OStatus save of remote message $sourceUri failed: " . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
common_log(LOG_INFO, "OStatus saved remote message $sourceUri as notice id $saved->id");
|
||||||
|
return $saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up HTML
|
||||||
|
*/
|
||||||
|
protected function purify($html)
|
||||||
|
{
|
||||||
|
// @fixme disable caching or set a sane temp dir
|
||||||
|
require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
|
||||||
|
$purifier = new HTMLPurifier();
|
||||||
|
return $purifier->purify($html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a list of recipient ID URIs to just those for local delivery.
|
||||||
|
* @param Ostatus_profile local profile of sender
|
||||||
|
* @param array in/out &$attention_uris set of URIs, will be pruned on output
|
||||||
|
* @return array of group IDs
|
||||||
|
*/
|
||||||
|
protected function filterReplies($sender, &$attention_uris)
|
||||||
|
{
|
||||||
|
common_log(LOG_DEBUG, "Original reply recipients: " . implode(', ', $attention_uris));
|
||||||
|
$groups = array();
|
||||||
|
$replies = array();
|
||||||
|
foreach ($attention_uris as $recipient) {
|
||||||
|
// Is the recipient a local user?
|
||||||
$user = User::staticGet('uri', $recipient);
|
$user = User::staticGet('uri', $recipient);
|
||||||
if ($user) {
|
if ($user) {
|
||||||
// An @-reply directed to a local user.
|
|
||||||
// @fixme sender verification, spam etc?
|
// @fixme sender verification, spam etc?
|
||||||
$params['replies'][] = $recipient;
|
$replies[] = $recipient;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is the recipient a remote group?
|
||||||
|
$oprofile = Ostatus_profile::staticGet('uri', $recipient);
|
||||||
|
if ($oprofile) {
|
||||||
|
if ($oprofile->isGroup()) {
|
||||||
|
// Deliver to local members of this remote group.
|
||||||
|
// @fixme sender verification?
|
||||||
|
$groups[] = $oprofile->group_id;
|
||||||
|
} else {
|
||||||
|
common_log(LOG_DEBUG, "Skipping reply to remote profile $recipient");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the recipient a local group?
|
||||||
// @fixme we need a uri on user_group
|
// @fixme we need a uri on user_group
|
||||||
// $group = User_group::staticGet('uri', $recipient);
|
// $group = User_group::staticGet('uri', $recipient);
|
||||||
$template = common_local_url('groupbyid', array('id' => '31337'));
|
$template = common_local_url('groupbyid', array('id' => '31337'));
|
||||||
$template = preg_quote($template, '/');
|
$template = preg_quote($template, '/');
|
||||||
$template = str_replace('31337', '(\d+)', $template);
|
$template = str_replace('31337', '(\d+)', $template);
|
||||||
common_log(LOG_DEBUG, $template);
|
|
||||||
if (preg_match("/$template/", $recipient, $matches)) {
|
if (preg_match("/$template/", $recipient, $matches)) {
|
||||||
$id = $matches[1];
|
$id = $matches[1];
|
||||||
$group = User_group::staticGet('id', $id);
|
$group = User_group::staticGet('id', $id);
|
||||||
if ($group) {
|
if ($group) {
|
||||||
// Deliver to all members of this local group.
|
// Deliver to all members of this local group if allowed.
|
||||||
// @fixme sender verification?
|
$profile = $sender->localProfile();
|
||||||
if ($profile->isMember($group)) {
|
if ($profile->isMember($group)) {
|
||||||
common_log(LOG_DEBUG, "delivering to group $id $group->nickname");
|
$groups[] = $group->id;
|
||||||
$params['groups'][] = $group->id;
|
|
||||||
} else {
|
} else {
|
||||||
common_log(LOG_DEBUG, "not delivering to group $id $group->nickname because sender $profile->nickname is not a member");
|
common_log(LOG_DEBUG, "Skipping reply to local group $group->nickname as sender $profile->id is not a member");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
common_log(LOG_DEBUG, "not delivering to missing group $id");
|
common_log(LOG_DEBUG, "Skipping reply to bogus group $recipient");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
common_log(LOG_DEBUG, "not delivering to groups for $recipient");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
common_log(LOG_DEBUG, "Skipping reply to unrecognized profile $recipient");
|
||||||
$saved = Notice::saveNew($profile->id,
|
|
||||||
$content,
|
|
||||||
'ostatus',
|
|
||||||
$params);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
common_log(LOG_ERR, "Failed saving notice entry for $sourceUri: " . $e->getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record which feed this came through...
|
|
||||||
try {
|
|
||||||
Ostatus_source::saveNew($saved, $this, 'push');
|
|
||||||
} catch (Exception $e) {
|
|
||||||
common_log(LOG_ERR, "Failed saving ostatus_source entry for $saved->notice_id: " . $e->getMessage());
|
|
||||||
}
|
}
|
||||||
|
$attention_uris = $replies;
|
||||||
|
common_log(LOG_DEBUG, "Local reply recipients: " . implode(', ', $replies));
|
||||||
|
common_log(LOG_DEBUG, "Local group recipients: " . implode(', ', $groups));
|
||||||
|
return $groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -729,6 +790,11 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
*/
|
*/
|
||||||
protected function updateAvatar($url)
|
protected function updateAvatar($url)
|
||||||
{
|
{
|
||||||
|
if ($url == $this->avatar) {
|
||||||
|
// We've already got this one.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->isGroup()) {
|
if ($this->isGroup()) {
|
||||||
$self = $this->localGroup();
|
$self = $this->localGroup();
|
||||||
} else {
|
} else {
|
||||||
@ -760,12 +826,28 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
common_timestamp());
|
common_timestamp());
|
||||||
rename($temp_filename, Avatar::path($filename));
|
rename($temp_filename, Avatar::path($filename));
|
||||||
$self->setOriginal($filename);
|
$self->setOriginal($filename);
|
||||||
|
|
||||||
|
$orig = clone($this);
|
||||||
|
$this->avatar = $url;
|
||||||
|
$this->update($orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function getActivityObjectAvatar($object)
|
/**
|
||||||
|
* Pull avatar URL from ActivityObject or profile hints
|
||||||
|
*
|
||||||
|
* @param ActivityObject $object
|
||||||
|
* @param array $hints
|
||||||
|
* @return mixed URL string or false
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected static function getActivityObjectAvatar($object, $hints=array())
|
||||||
{
|
{
|
||||||
// XXX: go poke around in the feed
|
if ($object->avatar) {
|
||||||
return $object->avatar;
|
return $object->avatar;
|
||||||
|
} else if (array_key_exists('avatar', $hints)) {
|
||||||
|
return $hints['avatar'];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -832,7 +914,9 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
|
public static function ensureActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
|
||||||
{
|
{
|
||||||
$profile = self::getActivityObjectProfile($object);
|
$profile = self::getActivityObjectProfile($object);
|
||||||
if (!$profile) {
|
if ($profile) {
|
||||||
|
$profile->updateFromActivityObject($object, $hints);
|
||||||
|
} else {
|
||||||
$profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints);
|
$profile = self::createActivityObjectProfile($object, $feeduri, $salmonuri, $hints);
|
||||||
}
|
}
|
||||||
return $profile;
|
return $profile;
|
||||||
@ -901,8 +985,6 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
|
protected static function createActivityObjectProfile($object, $feeduri=null, $salmonuri=null, $hints=array())
|
||||||
{
|
{
|
||||||
$homeuri = $object->id;
|
$homeuri = $object->id;
|
||||||
$nickname = self::getActivityObjectNickname($object, $hints);
|
|
||||||
$avatar = self::getActivityObjectAvatar($object);
|
|
||||||
|
|
||||||
if (!$homeuri) {
|
if (!$homeuri) {
|
||||||
common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
|
common_log(LOG_DEBUG, __METHOD__ . " empty actor profile URI: " . var_export($activity, true));
|
||||||
@ -946,43 +1028,19 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
|
|
||||||
if ($object->type == ActivityObject::PERSON) {
|
if ($object->type == ActivityObject::PERSON) {
|
||||||
$profile = new Profile();
|
$profile = new Profile();
|
||||||
$profile->nickname = $nickname;
|
self::updateProfile($profile, $object, $hints);
|
||||||
$profile->fullname = $object->title;
|
|
||||||
if (!empty($object->link)) {
|
|
||||||
$profile->profileurl = $object->link;
|
|
||||||
} else if (array_key_exists('profileurl', $hints)) {
|
|
||||||
$profile->profileurl = $hints['profileurl'];
|
|
||||||
}
|
|
||||||
$profile->created = common_sql_now();
|
$profile->created = common_sql_now();
|
||||||
|
|
||||||
// @fixme bio
|
|
||||||
// @fixme tags/categories
|
|
||||||
// @fixme location?
|
|
||||||
// @todo tags from categories
|
|
||||||
// @todo lat/lon/location?
|
|
||||||
|
|
||||||
$oprofile->profile_id = $profile->insert();
|
$oprofile->profile_id = $profile->insert();
|
||||||
|
|
||||||
if (!$oprofile->profile_id) {
|
if (!$oprofile->profile_id) {
|
||||||
throw new ServerException("Can't save local profile");
|
throw new ServerException("Can't save local profile");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$group = new User_group();
|
$group = new User_group();
|
||||||
$group->nickname = $nickname;
|
|
||||||
$group->fullname = $object->title;
|
|
||||||
// @fixme no canonical profileurl; using homepage instead for now
|
|
||||||
$group->homepage = $homeuri;
|
|
||||||
$group->created = common_sql_now();
|
$group->created = common_sql_now();
|
||||||
|
self::updateGroup($group, $object, $hints);
|
||||||
// @fixme homepage
|
|
||||||
// @fixme bio
|
|
||||||
// @fixme tags/categories
|
|
||||||
// @fixme location?
|
|
||||||
// @todo tags from categories
|
|
||||||
// @todo lat/lon/location?
|
|
||||||
|
|
||||||
$oprofile->group_id = $group->insert();
|
$oprofile->group_id = $group->insert();
|
||||||
|
|
||||||
if (!$oprofile->group_id) {
|
if (!$oprofile->group_id) {
|
||||||
throw new ServerException("Can't save local profile");
|
throw new ServerException("Can't save local profile");
|
||||||
}
|
}
|
||||||
@ -991,6 +1049,7 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
$ok = $oprofile->insert();
|
$ok = $oprofile->insert();
|
||||||
|
|
||||||
if ($ok) {
|
if ($ok) {
|
||||||
|
$avatar = self::getActivityObjectAvatar($object, $hints);
|
||||||
if ($avatar) {
|
if ($avatar) {
|
||||||
$oprofile->updateAvatar($avatar);
|
$oprofile->updateAvatar($avatar);
|
||||||
}
|
}
|
||||||
@ -1000,8 +1059,82 @@ class Ostatus_profile extends Memcached_DataObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save any updated profile information to our local copy.
|
||||||
|
* @param ActivityObject $object
|
||||||
|
* @param array $hints
|
||||||
|
*/
|
||||||
|
protected function updateFromActivityObject($object, $hints=array())
|
||||||
|
{
|
||||||
|
if ($this->isGroup()) {
|
||||||
|
$group = $this->localGroup();
|
||||||
|
self::updateGroup($group, $object, $hints);
|
||||||
|
} else {
|
||||||
|
$profile = $this->localProfile();
|
||||||
|
self::updateProfile($profile, $object, $hints);
|
||||||
|
}
|
||||||
|
$avatar = self::getActivityObjectAvatar($object, $hints);
|
||||||
|
if ($avatar) {
|
||||||
|
$this->updateAvatar($avatar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function updateProfile($profile, $object, $hints=array())
|
||||||
|
{
|
||||||
|
$orig = clone($profile);
|
||||||
|
|
||||||
|
$profile->nickname = self::getActivityObjectNickname($object, $hints);
|
||||||
|
$profile->fullname = $object->title;
|
||||||
|
if (!empty($object->link)) {
|
||||||
|
$profile->profileurl = $object->link;
|
||||||
|
} else if (array_key_exists('profileurl', $hints)) {
|
||||||
|
$profile->profileurl = $hints['profileurl'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// @fixme bio
|
||||||
|
// @fixme tags/categories
|
||||||
|
// @fixme location?
|
||||||
|
// @todo tags from categories
|
||||||
|
// @todo lat/lon/location?
|
||||||
|
|
||||||
|
if ($profile->id) {
|
||||||
|
common_log(LOG_DEBUG, "Updating OStatus profile $profile->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
|
||||||
|
$profile->update($orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function updateGroup($group, $object, $hints=array())
|
||||||
|
{
|
||||||
|
$orig = clone($group);
|
||||||
|
|
||||||
|
// @fixme need to make nick unique etc *hack hack*
|
||||||
|
$group->nickname = self::getActivityObjectNickname($object, $hints);
|
||||||
|
$group->fullname = $object->title;
|
||||||
|
|
||||||
|
// @fixme no canonical profileurl; using homepage instead for now
|
||||||
|
$group->homepage = $object->id;
|
||||||
|
|
||||||
|
// @fixme homepage
|
||||||
|
// @fixme bio
|
||||||
|
// @fixme tags/categories
|
||||||
|
// @fixme location?
|
||||||
|
// @todo tags from categories
|
||||||
|
// @todo lat/lon/location?
|
||||||
|
|
||||||
|
if ($group->id) {
|
||||||
|
common_log(LOG_DEBUG, "Updating OStatus group $group->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
|
||||||
|
$group->update($orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static function getActivityObjectNickname($object, $hints=array())
|
protected static function getActivityObjectNickname($object, $hints=array())
|
||||||
{
|
{
|
||||||
|
if ($object->poco) {
|
||||||
|
if (!empty($object->poco->preferredUsername)) {
|
||||||
|
return common_nicknamize($object->poco->preferredUsername);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!empty($object->nickname)) {
|
if (!empty($object->nickname)) {
|
||||||
return common_nicknamize($object->nickname);
|
return common_nicknamize($object->nickname);
|
||||||
}
|
}
|
||||||
|
@ -24,35 +24,9 @@
|
|||||||
* @note Everything in here should eventually migrate over to /js/util.js's SN.
|
* @note Everything in here should eventually migrate over to /js/util.js's SN.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SN.C.S.StatusNetInstance = 'StatusNetInstance';
|
|
||||||
|
|
||||||
SN.U.StatusNetInstance = {
|
|
||||||
Set: function(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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SN.Init.OStatusCookie = function() {
|
SN.Init.OStatusCookie = function() {
|
||||||
if (SN.U.StatusNetInstance.Get() === null) {
|
if (SN.U.StatusNetInstance.Get() === null) {
|
||||||
SN.U.StatusNetInstance.Set({profile: null});
|
SN.U.StatusNetInstance.Set({RemoteProfile: null});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,10 +75,10 @@ SN.U.DialogBox = {
|
|||||||
|
|
||||||
if (form.attr('id') == 'form_ostatus_connect') {
|
if (form.attr('id') == 'form_ostatus_connect') {
|
||||||
SN.Init.OStatusCookie();
|
SN.Init.OStatusCookie();
|
||||||
form.find('#profile').val(SN.U.StatusNetInstance.Get().profile);
|
form.find('#profile').val(SN.U.StatusNetInstance.Get().RemoteProfile);
|
||||||
|
|
||||||
form.find("[type=submit]").bind('click', function() {
|
form.find("[type=submit]").bind('click', function() {
|
||||||
SN.U.StatusNetInstance.Set({profile: form.find('#profile').val()});
|
SN.U.StatusNetInstance.Set({RemoteProfile: form.find('#profile').val()});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,4 +97,6 @@ SN.Init.Subscribe = function() {
|
|||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
SN.Init.Subscribe();
|
SN.Init.Subscribe();
|
||||||
|
|
||||||
|
$('.form_remote_authorize').bind('submit', function() { $(this).addClass(SN.C.S.Processing); return true; });
|
||||||
});
|
});
|
||||||
|
@ -18,46 +18,89 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a PuSH subscription verification from our internal hub.
|
* Prepare PuSH and Salmon distributions for an outgoing message.
|
||||||
* Queue up final distribution for
|
*
|
||||||
* @package Hub
|
* @package OStatusPlugin
|
||||||
* @author Brion Vibber <brion@status.net>
|
* @author Brion Vibber <brion@status.net>
|
||||||
*/
|
*/
|
||||||
class HubDistribQueueHandler extends QueueHandler
|
class OStatusQueueHandler extends QueueHandler
|
||||||
{
|
{
|
||||||
function transport()
|
function transport()
|
||||||
{
|
{
|
||||||
return 'hubdistrib';
|
return 'ostatus';
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle($notice)
|
function handle($notice)
|
||||||
{
|
{
|
||||||
assert($notice instanceof Notice);
|
assert($notice instanceof Notice);
|
||||||
|
|
||||||
$this->pushUser($notice);
|
$this->notice = $notice;
|
||||||
|
$this->user = User::staticGet($notice->profile_id);
|
||||||
|
|
||||||
|
$this->pushUser();
|
||||||
|
|
||||||
foreach ($notice->getGroups() as $group) {
|
foreach ($notice->getGroups() as $group) {
|
||||||
$this->pushGroup($notice, $group->id);
|
$oprofile = Ostatus_profile::staticGet('group_id', $group->id);
|
||||||
|
if ($oprofile) {
|
||||||
|
$this->pingReply($oprofile);
|
||||||
|
} else {
|
||||||
|
$this->pushGroup($group->id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($notice->getReplies() as $profile_id) {
|
||||||
|
$oprofile = Ostatus_profile::staticGet('profile_id', $profile_id);
|
||||||
|
if ($oprofile) {
|
||||||
|
$this->pingReply($oprofile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushUser($notice)
|
function pushUser()
|
||||||
{
|
{
|
||||||
// See if there's any PuSH subscriptions, including OStatus clients.
|
if ($this->user) {
|
||||||
// @fixme handle group subscriptions as well
|
// For local posts, ping the PuSH hub to update their feed.
|
||||||
// http://identi.ca/api/statuses/user_timeline/1.atom
|
// http://identi.ca/api/statuses/user_timeline/1.atom
|
||||||
$feed = common_local_url('ApiTimelineUser',
|
$feed = common_local_url('ApiTimelineUser',
|
||||||
array('id' => $notice->profile_id,
|
array('id' => $this->user->id,
|
||||||
'format' => 'atom'));
|
'format' => 'atom'));
|
||||||
$this->pushFeed($feed, array($this, 'userFeedForNotice'), $notice);
|
$this->pushFeed($feed, array($this, 'userFeedForNotice'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushGroup($notice, $group_id)
|
function pushGroup($group_id)
|
||||||
{
|
{
|
||||||
|
// For a local group, ping the PuSH hub to update its feed.
|
||||||
|
// Updates may come from either a local or a remote user.
|
||||||
$feed = common_local_url('ApiTimelineGroup',
|
$feed = common_local_url('ApiTimelineGroup',
|
||||||
array('id' => $group_id,
|
array('id' => $group_id,
|
||||||
'format' => 'atom'));
|
'format' => 'atom'));
|
||||||
$this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id, $notice);
|
$this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pingReply($oprofile)
|
||||||
|
{
|
||||||
|
if ($this->user) {
|
||||||
|
if (!empty($oprofile->salmonuri)) {
|
||||||
|
// For local posts, send a Salmon ping to the mentioned
|
||||||
|
// remote user or group.
|
||||||
|
// @fixme as an optimization we can skip this if the
|
||||||
|
// remote profile is subscribed to the author.
|
||||||
|
|
||||||
|
common_log(LOG_INFO, "Prepping to send notice '{$this->notice->uri}' to remote profile '{$oprofile->uri}'.");
|
||||||
|
|
||||||
|
$xml = '<?xml version="1.0" encoding="UTF-8" ?' . '>';
|
||||||
|
$xml .= $this->notice->asAtomEntry(true, true);
|
||||||
|
|
||||||
|
$data = array('salmonuri' => $oprofile->salmonuri,
|
||||||
|
'entry' => $xml);
|
||||||
|
|
||||||
|
$qm = QueueManager::get();
|
||||||
|
$qm->enqueue($data, 'salmonout');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,7 +165,6 @@ class HubDistribQueueHandler extends QueueHandler
|
|||||||
function pushFeedInternal($atom, $sub)
|
function pushFeedInternal($atom, $sub)
|
||||||
{
|
{
|
||||||
common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic");
|
common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic");
|
||||||
$qm = QueueManager::get();
|
|
||||||
while ($sub->fetch()) {
|
while ($sub->fetch()) {
|
||||||
$sub->distribute($atom);
|
$sub->distribute($atom);
|
||||||
}
|
}
|
||||||
@ -130,20 +172,19 @@ class HubDistribQueueHandler extends QueueHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a single-item version of the sending user's Atom feed.
|
* Build a single-item version of the sending user's Atom feed.
|
||||||
* @param Notice $notice
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function userFeedForNotice($notice)
|
function userFeedForNotice()
|
||||||
{
|
{
|
||||||
// @fixme this feels VERY hacky...
|
// @fixme this feels VERY hacky...
|
||||||
// should probably be a cleaner way to do it
|
// should probably be a cleaner way to do it
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
$api = new ApiTimelineUserAction();
|
$api = new ApiTimelineUserAction();
|
||||||
$api->prepare(array('id' => $notice->profile_id,
|
$api->prepare(array('id' => $this->notice->profile_id,
|
||||||
'format' => 'atom',
|
'format' => 'atom',
|
||||||
'max_id' => $notice->id,
|
'max_id' => $this->notice->id,
|
||||||
'since_id' => $notice->id - 1));
|
'since_id' => $this->notice->id - 1));
|
||||||
$api->showTimeline();
|
$api->showTimeline();
|
||||||
$feed = ob_get_clean();
|
$feed = ob_get_clean();
|
||||||
|
|
||||||
@ -155,7 +196,7 @@ class HubDistribQueueHandler extends QueueHandler
|
|||||||
return $feed;
|
return $feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupFeedForNotice($group_id, $notice)
|
function groupFeedForNotice($group_id)
|
||||||
{
|
{
|
||||||
// @fixme this feels VERY hacky...
|
// @fixme this feels VERY hacky...
|
||||||
// should probably be a cleaner way to do it
|
// should probably be a cleaner way to do it
|
||||||
@ -164,8 +205,8 @@ class HubDistribQueueHandler extends QueueHandler
|
|||||||
$api = new ApiTimelineGroupAction();
|
$api = new ApiTimelineGroupAction();
|
||||||
$args = array('id' => $group_id,
|
$args = array('id' => $group_id,
|
||||||
'format' => 'atom',
|
'format' => 'atom',
|
||||||
'max_id' => $notice->id,
|
'max_id' => $this->notice->id,
|
||||||
'since_id' => $notice->id - 1);
|
'since_id' => $this->notice->id - 1);
|
||||||
$api->prepare($args);
|
$api->prepare($args);
|
||||||
$api->handle($args);
|
$api->handle($args);
|
||||||
$feed = ob_get_clean();
|
$feed = ob_get_clean();
|
@ -185,54 +185,6 @@ class SalmonAction extends Action
|
|||||||
function saveNotice()
|
function saveNotice()
|
||||||
{
|
{
|
||||||
$oprofile = $this->ensureProfile();
|
$oprofile = $this->ensureProfile();
|
||||||
|
return $oprofile->processPost($this->act, 'salmon');
|
||||||
// Get (safe!) HTML and text versions of the content
|
|
||||||
|
|
||||||
require_once(INSTALLDIR.'/extlib/HTMLPurifier/HTMLPurifier.auto.php');
|
|
||||||
|
|
||||||
$html = $this->act->object->content;
|
|
||||||
|
|
||||||
$purifier = new HTMLPurifier();
|
|
||||||
|
|
||||||
$rendered = $purifier->purify($html);
|
|
||||||
|
|
||||||
$content = html_entity_decode(strip_tags($rendered));
|
|
||||||
|
|
||||||
$options = array('is_local' => Notice::REMOTE_OMB,
|
|
||||||
'uri' => $this->act->object->id,
|
|
||||||
'url' => $this->act->object->link,
|
|
||||||
'rendered' => $rendered,
|
|
||||||
'replies' => $this->act->context->attention);
|
|
||||||
|
|
||||||
if (!empty($this->act->context->location)) {
|
|
||||||
$options['lat'] = $location->lat;
|
|
||||||
$options['lon'] = $location->lon;
|
|
||||||
if ($location->location_id) {
|
|
||||||
$options['location_ns'] = $location->location_ns;
|
|
||||||
$options['location_id'] = $location->location_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($this->act->context->replyToID)) {
|
|
||||||
$orig = Notice::staticGet('uri',
|
|
||||||
$this->act->context->replyToID);
|
|
||||||
if (!empty($orig)) {
|
|
||||||
$options['reply_to'] = $orig->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($this->act->time)) {
|
|
||||||
$options['created'] = common_sql_date($this->act->time);
|
|
||||||
}
|
|
||||||
|
|
||||||
$saved = Notice::saveNew($oprofile->profile_id,
|
|
||||||
$content,
|
|
||||||
'ostatus+salmon',
|
|
||||||
$options);
|
|
||||||
|
|
||||||
// Record that this was saved through a validated Salmon source
|
|
||||||
// @fixme actually do the signature validation!
|
|
||||||
Ostatus_source::saveNew($saved, $oprofile, 'salmon');
|
|
||||||
return $saved;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
plugins/OStatus/lib/salmonoutqueuehandler.php
Normal file
44
plugins/OStatus/lib/salmonoutqueuehandler.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* StatusNet - the distributed open-source microblogging tool
|
||||||
|
* Copyright (C) 2010, 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a Salmon notification in the background.
|
||||||
|
* @package OStatusPlugin
|
||||||
|
* @author Brion Vibber <brion@status.net>
|
||||||
|
*/
|
||||||
|
class SalmonOutQueueHandler extends QueueHandler
|
||||||
|
{
|
||||||
|
function transport()
|
||||||
|
{
|
||||||
|
return 'salmonout';
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle($data)
|
||||||
|
{
|
||||||
|
assert(is_array($data));
|
||||||
|
assert(is_string($data['salmonuri']));
|
||||||
|
assert(is_string($data['entry']));
|
||||||
|
|
||||||
|
$salmon = new Salmon();
|
||||||
|
$salmon->post($data['salmonuri'], $data['entry']);
|
||||||
|
|
||||||
|
// @fixme detect failure and attempt to resend
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,10 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
|||||||
|
|
||||||
// XXX: we should probably have some common source for this stuff
|
// XXX: we should probably have some common source for this stuff
|
||||||
|
|
||||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
|
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||||
define('STATUSNET', true);
|
define('STATUSNET', true);
|
||||||
|
|
||||||
require_once INSTALLDIR . '/lib/common.php';
|
require_once INSTALLDIR . '/lib/common.php';
|
||||||
require_once INSTALLDIR . '/plugins/OStatus/lib/activity.php';
|
|
||||||
|
|
||||||
class ActivityParseTests extends PHPUnit_Framework_TestCase
|
class ActivityParseTests extends PHPUnit_Framework_TestCase
|
||||||
{
|
{
|
||||||
@ -97,6 +96,45 @@ class ActivityParseTests extends PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$this->assertFalse(empty($act->actor));
|
$this->assertFalse(empty($act->actor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testExample5()
|
||||||
|
{
|
||||||
|
global $_example5;
|
||||||
|
$dom = DOMDocument::loadXML($_example5);
|
||||||
|
|
||||||
|
$feed = $dom->documentElement;
|
||||||
|
|
||||||
|
// @todo Test feed elements
|
||||||
|
|
||||||
|
$entries = $feed->getElementsByTagName('entry');
|
||||||
|
$entry = $entries->item(0);
|
||||||
|
|
||||||
|
$act = new Activity($entry, $feed);
|
||||||
|
|
||||||
|
// Post
|
||||||
|
$this->assertEquals($act->verb, ActivityVerb::POST);
|
||||||
|
$this->assertFalse(empty($act->context));
|
||||||
|
|
||||||
|
// Actor w/Portable Contacts stuff
|
||||||
|
$this->assertFalse(empty($act->actor));
|
||||||
|
$this->assertEquals($act->actor->type, ActivityObject::PERSON);
|
||||||
|
$this->assertEquals($act->actor->title, 'Test User');
|
||||||
|
$this->assertEquals($act->actor->id, 'http://example.net/mysite/user/3');
|
||||||
|
$this->assertEquals($act->actor->link, 'http://example.net/mysite/testuser');
|
||||||
|
$this->assertEquals(
|
||||||
|
$act->actor->avatar,
|
||||||
|
'http://example.net/mysite/avatar/3-96-20100224004207.jpeg'
|
||||||
|
);
|
||||||
|
$this->assertEquals($act->actor->displayName, 'Test User');
|
||||||
|
|
||||||
|
$poco = $act->actor->poco;
|
||||||
|
$this->assertEquals($poco->preferredUsername, 'testuser');
|
||||||
|
$this->assertEquals($poco->address->formatted, 'San Francisco, CA');
|
||||||
|
$this->assertEquals($poco->urls[0]->type, 'homepage');
|
||||||
|
$this->assertEquals($poco->urls[0]->value, 'http://example.com/blog.html');
|
||||||
|
$this->assertEquals($poco->urls[0]->primary, 'true');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$_example1 = <<<EXAMPLE1
|
$_example1 = <<<EXAMPLE1
|
||||||
@ -207,3 +245,82 @@ $_example4 = <<<EXAMPLE4
|
|||||||
<category term="thetime"></category>
|
<category term="thetime"></category>
|
||||||
</entry>
|
</entry>
|
||||||
EXAMPLE4;
|
EXAMPLE4;
|
||||||
|
|
||||||
|
$_example5 = <<<EXAMPLE5
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:georss="http://www.georss.org/georss" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0">
|
||||||
|
<id>3</id>
|
||||||
|
<title>testuser timeline</title>
|
||||||
|
<subtitle>Updates from testuser on Zach Dev!</subtitle>
|
||||||
|
<logo>http://example.net/mysite/avatar/3-96-20100224004207.jpeg</logo>
|
||||||
|
<updated>2010-02-24T06:38:49+00:00</updated>
|
||||||
|
<author>
|
||||||
|
<name>testuser</name>
|
||||||
|
<uri>http://example.net/mysite/user/3</uri>
|
||||||
|
|
||||||
|
</author>
|
||||||
|
<link href="http://example.net/mysite/testuser" rel="alternate" type="text/html"/>
|
||||||
|
<link href="http://example.net/mysite/api/statuses/user_timeline/3.atom" rel="self" type="application/atom+xml"/>
|
||||||
|
<link href="http://example.net/mysite/main/sup#3" rel="http://api.friendfeed.com/2008/03#sup" type="application/json"/>
|
||||||
|
<link href="http://example.net/mysite/main/push/hub" rel="hub"/>
|
||||||
|
<link href="http://example.net/mysite/main/salmon/user/3" rel="salmon"/>
|
||||||
|
<activity:subject>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||||
|
<id>http://example.net/mysite/user/3</id>
|
||||||
|
<title>Test User</title>
|
||||||
|
<link rel="alternate" type="text/html" href="http://example.net/mysite/testuser"/>
|
||||||
|
<link type="image/jpeg" rel="avatar" href="http://example.net/mysite/avatar/3-96-20100224004207.jpeg"/>
|
||||||
|
<georss:point>37.7749295 -122.4194155</georss:point>
|
||||||
|
|
||||||
|
<poco:preferredUsername>testuser</poco:preferredUsername>
|
||||||
|
<poco:displayName>Test User</poco:displayName>
|
||||||
|
<poco:note>Just another test user.</poco:note>
|
||||||
|
<poco:address>
|
||||||
|
<poco:formatted>San Francisco, CA</poco:formatted>
|
||||||
|
</poco:address>
|
||||||
|
<poco:urls>
|
||||||
|
<poco:type>homepage</poco:type>
|
||||||
|
<poco:value>http://example.com/blog.html</poco:value>
|
||||||
|
<poco:primary>true</poco:primary>
|
||||||
|
|
||||||
|
</poco:urls>
|
||||||
|
</activity:subject>
|
||||||
|
<entry>
|
||||||
|
<title>Hey man, is that Freedom Code?! #freedom #hippy</title>
|
||||||
|
<summary>Hey man, is that Freedom Code?! #freedom #hippy</summary>
|
||||||
|
<author>
|
||||||
|
<name>testuser</name>
|
||||||
|
<uri>http://example.net/mysite/user/3</uri>
|
||||||
|
</author>
|
||||||
|
<activity:actor>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||||
|
<id>http://example.net/mysite/user/3</id>
|
||||||
|
<title>Test User</title>
|
||||||
|
<link rel="alternate" type="text/html" href="http://example.net/mysite/testuser"/>
|
||||||
|
<link type="image/jpeg" rel="avatar" href="http://example.net/mysite/avatar/3-96-20100224004207.jpeg"/>
|
||||||
|
<georss:point>37.7749295 -122.4194155</georss:point>
|
||||||
|
|
||||||
|
<poco:preferredUsername>testuser</poco:preferredUsername>
|
||||||
|
<poco:displayName>Test User</poco:displayName>
|
||||||
|
<poco:note>Just another test user.</poco:note>
|
||||||
|
<poco:address>
|
||||||
|
<poco:formatted>San Francisco, CA</poco:formatted>
|
||||||
|
</poco:address>
|
||||||
|
<poco:urls>
|
||||||
|
<poco:type>homepage</poco:type>
|
||||||
|
<poco:value>http://example.com/blog.html</poco:value>
|
||||||
|
<poco:primary>true</poco:primary>
|
||||||
|
|
||||||
|
</poco:urls>
|
||||||
|
</activity:actor>
|
||||||
|
<link rel="alternate" type="text/html" href="http://example.net/mysite/notice/7"/>
|
||||||
|
<id>http://example.net/mysite/notice/7</id>
|
||||||
|
<published>2010-02-24T00:53:06+00:00</published>
|
||||||
|
<updated>2010-02-24T00:53:06+00:00</updated>
|
||||||
|
<link rel="ostatus:conversation" href="http://example.net/mysite/conversation/7"/>
|
||||||
|
<content type="html">Hey man, is that Freedom Code?! #<span class="tag"><a href="http://example.net/mysite/tag/freedom" rel="tag">freedom</a></span> #<span class="tag"><a href="http://example.net/mysite/tag/hippy" rel="tag">hippy</a></span></content>
|
||||||
|
<georss:point>37.8313160 -122.2852473</georss:point>
|
||||||
|
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
|
EXAMPLE5;
|
@ -1490,6 +1490,7 @@ text-align:center;
|
|||||||
}
|
}
|
||||||
.aside .tag-cloud {
|
.aside .tag-cloud {
|
||||||
font-size:0.8em;
|
font-size:0.8em;
|
||||||
|
word-wrap:break-word;
|
||||||
}
|
}
|
||||||
.tag-cloud li {
|
.tag-cloud li {
|
||||||
display:inline;
|
display:inline;
|
||||||
|
Loading…
Reference in New Issue
Block a user