Merge branch 'testing' of git@gitorious.org:statusnet/mainline into testing
This commit is contained in:
commit
8ad7629422
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
/*
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, StatusNet, Inc.
|
||||
* Copyright (C) 2008-2010, StatusNet, Inc.
|
||||
*
|
||||
* Subscription action.
|
||||
*
|
||||
* 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
|
||||
@ -15,68 +17,142 @@
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscription action
|
||||
*
|
||||
* Subscribing to a profile. Does not work for OMB 0.1 remote subscriptions,
|
||||
* but may work for other remote subscription protocols, like OStatus.
|
||||
*
|
||||
* Takes parameters:
|
||||
*
|
||||
* - subscribeto: a profile ID
|
||||
* - token: session token to prevent CSRF attacks
|
||||
* - ajax: boolean; whether to return Ajax or full-browser results
|
||||
*
|
||||
* Only works if the current user is logged in.
|
||||
*
|
||||
* @category Action
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @copyright 2008-2010 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
class SubscribeAction extends Action
|
||||
{
|
||||
var $user;
|
||||
var $other;
|
||||
|
||||
function handle($args)
|
||||
/**
|
||||
* Check pre-requisites and instantiate attributes
|
||||
*
|
||||
* @param Array $args array of arguments (URL, GET, POST)
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
parent::prepare($args);
|
||||
|
||||
if (!common_logged_in()) {
|
||||
$this->clientError(_('Not logged in.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
// Only allow POST requests
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
|
||||
return;
|
||||
$this->clientError(_('This action only accepts POST requests.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
# CSRF protection
|
||||
// CSRF protection
|
||||
|
||||
$token = $this->trimmed('token');
|
||||
|
||||
if (!$token || $token != common_session_token()) {
|
||||
$this->clientError(_('There was a problem with your session token. Try again, please.'));
|
||||
return;
|
||||
$this->clientError(_('There was a problem with your session token.'.
|
||||
' Try again, please.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only for logged-in users
|
||||
|
||||
$this->user = common_current_user();
|
||||
|
||||
if (empty($this->user)) {
|
||||
$this->clientError(_('Not logged in.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Profile to subscribe to
|
||||
|
||||
$other_id = $this->arg('subscribeto');
|
||||
|
||||
$other = User::staticGet('id', $other_id);
|
||||
$this->other = Profile::staticGet('id', $other_id);
|
||||
|
||||
if (!$other) {
|
||||
$this->clientError(_('Not a local user.'));
|
||||
return;
|
||||
if (empty($this->other)) {
|
||||
$this->clientError(_('No such profile.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = subs_subscribe_to($user, $other);
|
||||
// OMB 0.1 doesn't have a mechanism for local-server-
|
||||
// originated subscription.
|
||||
|
||||
if (is_string($result)) {
|
||||
$this->clientError($result);
|
||||
return;
|
||||
$omb01 = Remote_profile::staticGet('id', $other_id);
|
||||
|
||||
if (!empty($omb01)) {
|
||||
$this->clientError(_('You cannot subscribe to an OMB 0.1'.
|
||||
' remote profile with this action.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle request
|
||||
*
|
||||
* Does the subscription and returns results.
|
||||
*
|
||||
* @param Array $args unused.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
// Throws exception on error
|
||||
|
||||
Subscription::start($this->user->getProfile(),
|
||||
$this->other);
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->startHTML('text/xml;charset=utf-8');
|
||||
$this->elementStart('head');
|
||||
$this->element('title', null, _('Subscribed'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$unsubscribe = new UnsubscribeForm($this, $other->getProfile());
|
||||
$unsubscribe = new UnsubscribeForm($this, $this->other->getProfile());
|
||||
$unsubscribe->show();
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
common_redirect(common_local_url('subscriptions', array('nickname' =>
|
||||
$user->nickname)),
|
||||
303);
|
||||
$url = common_local_url('subscriptions',
|
||||
array('nickname' => $this->user->nickname));
|
||||
common_redirect($url, 303);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -104,6 +104,7 @@ class PoCo
|
||||
function __construct($profile)
|
||||
{
|
||||
$this->preferredUsername = $profile->nickname;
|
||||
$this->displayName = $profile->getBestName();
|
||||
|
||||
$this->note = $profile->bio;
|
||||
$this->address = new PoCoAddress($profile->location);
|
||||
@ -129,6 +130,12 @@ class PoCo
|
||||
$this->preferredUsername
|
||||
);
|
||||
|
||||
$xs->element(
|
||||
'poco:displayName',
|
||||
null,
|
||||
$this->displayName
|
||||
);
|
||||
|
||||
if (!empty($this->note)) {
|
||||
$xs->element('poco:note', null, $this->note);
|
||||
}
|
||||
@ -823,7 +830,9 @@ class Activity
|
||||
if ($namespace) {
|
||||
$attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
|
||||
'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
|
||||
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
|
||||
'xmlns:georss' => 'http://www.georss.org/georss',
|
||||
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
|
||||
'xmlns:poco' => 'http://portablecontacts.net/spec/1.0');
|
||||
} else {
|
||||
$attrs = array();
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -224,7 +224,7 @@ class OStatusPlugin extends Plugin
|
||||
*
|
||||
*/
|
||||
|
||||
function onEndFindMentions($sender, $text, &$mentions)
|
||||
function onStartFindMentions($sender, $text, &$mentions)
|
||||
{
|
||||
preg_match_all('/(?:^|\s+)@((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)/',
|
||||
$text,
|
||||
@ -251,58 +251,6 @@ class OStatusPlugin extends Plugin
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify remote server and garbage collect unused feeds on unsubscribe.
|
||||
* @fixme send these operations to background queues
|
||||
*
|
||||
* @param User $user
|
||||
* @param Profile $other
|
||||
* @return hook return value
|
||||
*/
|
||||
function onEndUnsubscribe($profile, $other)
|
||||
{
|
||||
$user = User::staticGet('id', $profile->id);
|
||||
|
||||
if (empty($user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
|
||||
|
||||
if (empty($oprofile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Drop the PuSH subscription if there are no other subscribers.
|
||||
|
||||
if ($other->subscriberCount() == 0) {
|
||||
common_log(LOG_INFO, "Unsubscribing from now-unused feed $oprofile->feeduri");
|
||||
$oprofile->unsubscribe();
|
||||
}
|
||||
|
||||
$act = new Activity();
|
||||
|
||||
$act->verb = ActivityVerb::UNFOLLOW;
|
||||
|
||||
$act->id = TagURI::mint('unfollow:%d:%d:%s',
|
||||
$profile->id,
|
||||
$other->id,
|
||||
common_date_iso8601(time()));
|
||||
|
||||
$act->time = time();
|
||||
$act->title = _("Unfollow");
|
||||
$act->content = sprintf(_("%s stopped following %s."),
|
||||
$profile->getBestName(),
|
||||
$other->getBestName());
|
||||
|
||||
$act->actor = ActivityObject::fromProfile($profile);
|
||||
$act->object = ActivityObject::fromProfile($other);
|
||||
|
||||
$oprofile->notifyActivity($act);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure necessary tables are filled out.
|
||||
*/
|
||||
@ -312,6 +260,7 @@ class OStatusPlugin extends Plugin
|
||||
$schema->ensureTable('ostatus_source', Ostatus_source::schemaDef());
|
||||
$schema->ensureTable('feedsub', FeedSub::schemaDef());
|
||||
$schema->ensureTable('hubsub', HubSub::schemaDef());
|
||||
$schema->ensureTable('magicsig', Magicsig::schemaDef());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -338,13 +287,19 @@ class OStatusPlugin extends Plugin
|
||||
function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
|
||||
{
|
||||
if ($notice->source == 'ostatus') {
|
||||
$bits = parse_url($notice->uri);
|
||||
$domain = $bits['host'];
|
||||
if ($notice->url) {
|
||||
$bits = parse_url($notice->url);
|
||||
$domain = $bits['host'];
|
||||
if (substr($domain, 0, 4) == 'www.') {
|
||||
$name = substr($domain, 4);
|
||||
} else {
|
||||
$name = $domain;
|
||||
}
|
||||
|
||||
$name = $domain;
|
||||
$url = $notice->uri;
|
||||
$title = sprintf(_m("Sent from %s via OStatus"), $domain);
|
||||
return false;
|
||||
$url = $notice->url;
|
||||
$title = sprintf(_m("Sent from %s via OStatus"), $domain);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,12 +314,56 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When about to subscribe to a remote user, start a server-to-server
|
||||
* PuSH subscription if needed. If we can't establish that, abort.
|
||||
*
|
||||
* @fixme If something else aborts later, we could end up with a stray
|
||||
* PuSH subscription. This is relatively harmless, though.
|
||||
*
|
||||
* @param Profile $subscriber
|
||||
* @param Profile $other
|
||||
*
|
||||
* @return hook return code
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function onStartSubscribe($subscriber, $other)
|
||||
{
|
||||
$user = User::staticGet('id', $subscriber->id);
|
||||
|
||||
if (empty($user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
|
||||
|
||||
if (empty($oprofile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$oprofile->subscribe()) {
|
||||
throw new Exception(_m('Could not set up remote subscription.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Having established a remote subscription, send a notification to the
|
||||
* remote OStatus profile's endpoint.
|
||||
*
|
||||
* @param Profile $subscriber
|
||||
* @param Profile $other
|
||||
*
|
||||
* @return hook return code
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function onEndSubscribe($subscriber, $other)
|
||||
{
|
||||
$user = User::staticGet('id', $subscriber->id);
|
||||
@ -402,6 +401,54 @@ class OStatusPlugin extends Plugin
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify remote server and garbage collect unused feeds on unsubscribe.
|
||||
* @fixme send these operations to background queues
|
||||
*
|
||||
* @param User $user
|
||||
* @param Profile $other
|
||||
* @return hook return value
|
||||
*/
|
||||
function onEndUnsubscribe($profile, $other)
|
||||
{
|
||||
$user = User::staticGet('id', $profile->id);
|
||||
|
||||
if (empty($user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$oprofile = Ostatus_profile::staticGet('profile_id', $other->id);
|
||||
|
||||
if (empty($oprofile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Drop the PuSH subscription if there are no other subscribers.
|
||||
$oprofile->garbageCollect();
|
||||
|
||||
$act = new Activity();
|
||||
|
||||
$act->verb = ActivityVerb::UNFOLLOW;
|
||||
|
||||
$act->id = TagURI::mint('unfollow:%d:%d:%s',
|
||||
$profile->id,
|
||||
$other->id,
|
||||
common_date_iso8601(time()));
|
||||
|
||||
$act->time = time();
|
||||
$act->title = _("Unfollow");
|
||||
$act->content = sprintf(_("%s stopped following %s."),
|
||||
$profile->getBestName(),
|
||||
$other->getBestName());
|
||||
|
||||
$act->actor = ActivityObject::fromProfile($profile);
|
||||
$act->object = ActivityObject::fromProfile($other);
|
||||
|
||||
$oprofile->notifyActivity($act);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When one of our local users tries to join a remote group,
|
||||
* notify the remote server. If the notification is rejected,
|
||||
@ -417,6 +464,10 @@ class OStatusPlugin extends Plugin
|
||||
{
|
||||
$oprofile = Ostatus_profile::staticGet('group_id', $group->id);
|
||||
if ($oprofile) {
|
||||
if (!$oprofile->subscribe()) {
|
||||
throw new Exception(_m('Could not set up remote group membership.'));
|
||||
}
|
||||
|
||||
$member = Profile::staticGet($user->id);
|
||||
|
||||
$act = new Activity();
|
||||
@ -438,7 +489,8 @@ class OStatusPlugin extends Plugin
|
||||
if ($oprofile->notifyActivity($act)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new ServerException(_m("Failed joining remote group."));
|
||||
$oprofile->garbageCollect();
|
||||
throw new Exception(_m("Failed joining remote group."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -463,12 +515,7 @@ class OStatusPlugin extends Plugin
|
||||
$oprofile = Ostatus_profile::staticGet('group_id', $group->id);
|
||||
if ($oprofile) {
|
||||
// Drop the PuSH subscription if there are no other subscribers.
|
||||
|
||||
$members = $group->getMembers(0, 1);
|
||||
if ($members->N == 0) {
|
||||
common_log(LOG_INFO, "Unsubscribing from now-unused group feed $oprofile->feeduri");
|
||||
$oprofile->unsubscribe();
|
||||
}
|
||||
$oprofile->garbageCollect();
|
||||
|
||||
|
||||
$member = Profile::staticGet($user->id);
|
||||
|
@ -87,53 +87,168 @@ class OStatusSubAction extends Action
|
||||
*/
|
||||
function showPreviewForm()
|
||||
{
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
'id' => 'form_ostatus_sub',
|
||||
'class' => 'form_settings',
|
||||
'action' =>
|
||||
common_local_url('ostatussub')));
|
||||
|
||||
$this->hidden('token', common_session_token());
|
||||
$this->hidden('profile', $this->profile_uri);
|
||||
|
||||
$this->elementStart('fieldset', array('id' => 'settings_feeds'));
|
||||
|
||||
if ($this->oprofile->isGroup()) {
|
||||
$this->previewGroup();
|
||||
$this->submit('subscribe', _m('Join'));
|
||||
$ok = $this->previewGroup();
|
||||
} else {
|
||||
$this->previewUser();
|
||||
$this->submit('subscribe', _m('Subscribe'));
|
||||
$ok = $this->previewUser();
|
||||
}
|
||||
if (!$ok) {
|
||||
// @fixme maybe provide a cancel button or link back?
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$this->elementStart('div', 'entity_actions');
|
||||
$this->elementStart('ul');
|
||||
$this->elementStart('li', 'entity_subscribe');
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
'id' => 'form_ostatus_sub',
|
||||
'class' => 'form_remote_authorize',
|
||||
'action' =>
|
||||
common_local_url('ostatussub')));
|
||||
$this->elementStart('fieldset');
|
||||
$this->hidden('token', common_session_token());
|
||||
$this->hidden('profile', $this->profile_uri);
|
||||
if ($this->oprofile->isGroup()) {
|
||||
$this->submit('submit', _m('Join'), 'submit', null,
|
||||
_m('Join this group'));
|
||||
} else {
|
||||
$this->submit('submit', _m('Subscribe'), 'submit', null,
|
||||
_m('Subscribe to this user'));
|
||||
}
|
||||
$this->elementEnd('fieldset');
|
||||
|
||||
$this->elementEnd('form');
|
||||
$this->elementEnd('li');
|
||||
$this->elementEnd('ul');
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a preview for a remote user's profile
|
||||
* @return boolean true if we're ok to try subscribing
|
||||
*/
|
||||
function previewUser()
|
||||
{
|
||||
$oprofile = $this->oprofile;
|
||||
$profile = $oprofile->localProfile();
|
||||
|
||||
$this->text(sprintf(_m("Remote user %s"), $profile->nickname));
|
||||
// ...
|
||||
$cur = common_current_user();
|
||||
if ($cur->isSubscribed($profile)) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_m("You are already subscribed to this user."));
|
||||
$ok = false;
|
||||
} else {
|
||||
$ok = true;
|
||||
}
|
||||
|
||||
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
|
||||
$avatarUrl = $avatar ? $avatar->displayUrl() : false;
|
||||
|
||||
$this->showEntity($profile,
|
||||
$profile->profileurl,
|
||||
$avatarUrl,
|
||||
$profile->bio);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a preview for a remote group's profile
|
||||
* @return boolean true if we're ok to try joining
|
||||
*/
|
||||
function previewGroup()
|
||||
{
|
||||
$oprofile = $this->oprofile;
|
||||
$group = $oprofile->localGroup();
|
||||
|
||||
$this->text(sprintf(_m("Remote group %s"), $group->nickname));
|
||||
// ..
|
||||
$cur = common_current_user();
|
||||
if ($cur->isMember($group)) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_m("You are already a member of this group."));
|
||||
$ok = false;
|
||||
} else {
|
||||
$ok = true;
|
||||
}
|
||||
|
||||
$this->showEntity($group,
|
||||
$group->getProfileUrl(),
|
||||
$group->homepage_logo,
|
||||
$group->description);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
|
||||
function showEntity($entity, $profile, $avatar, $note)
|
||||
{
|
||||
$nickname = $entity->nickname;
|
||||
$fullname = $entity->fullname;
|
||||
$homepage = $entity->homepage;
|
||||
$location = $entity->location;
|
||||
|
||||
if (!$avatar) {
|
||||
$avatar = Avatar::defaultImage(AVATAR_PROFILE_SIZE);
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'entity_profile vcard');
|
||||
$this->elementStart('dl', 'entity_depiction');
|
||||
$this->element('dt', null, _('Photo'));
|
||||
$this->elementStart('dd');
|
||||
$this->element('img', array('src' => $avatar,
|
||||
'class' => 'photo avatar',
|
||||
'width' => AVATAR_PROFILE_SIZE,
|
||||
'height' => AVATAR_PROFILE_SIZE,
|
||||
'alt' => $nickname));
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementStart('dl', 'entity_nickname');
|
||||
$this->element('dt', null, _('Nickname'));
|
||||
$this->elementStart('dd');
|
||||
$hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname';
|
||||
$this->elementStart('a', array('href' => $profile,
|
||||
'class' => 'url '.$hasFN));
|
||||
$this->raw($nickname);
|
||||
$this->elementEnd('a');
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
|
||||
if (!is_null($fullname)) {
|
||||
$this->elementStart('dl', 'entity_fn');
|
||||
$this->elementStart('dd');
|
||||
$this->elementStart('span', 'fn');
|
||||
$this->raw($fullname);
|
||||
$this->elementEnd('span');
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
}
|
||||
if (!is_null($location)) {
|
||||
$this->elementStart('dl', 'entity_location');
|
||||
$this->element('dt', null, _('Location'));
|
||||
$this->elementStart('dd', 'label');
|
||||
$this->raw($location);
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
}
|
||||
|
||||
if (!is_null($homepage)) {
|
||||
$this->elementStart('dl', 'entity_url');
|
||||
$this->element('dt', null, _('URL'));
|
||||
$this->elementStart('dd');
|
||||
$this->elementStart('a', array('href' => $homepage,
|
||||
'class' => 'url'));
|
||||
$this->raw($homepage);
|
||||
$this->elementEnd('a');
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
}
|
||||
|
||||
if (!is_null($note)) {
|
||||
$this->elementStart('dl', 'entity_note');
|
||||
$this->element('dt', null, _('Note'));
|
||||
$this->elementStart('dd', 'note');
|
||||
$this->raw($note);
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
}
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -173,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.');
|
||||
@ -209,11 +329,6 @@ class OStatusSubAction extends Action
|
||||
// And subscribe the current user to the local profile
|
||||
$user = common_current_user();
|
||||
|
||||
if (!$this->oprofile->subscribe()) {
|
||||
$this->showForm(_m("Failed to set up server-to-server subscription."));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->oprofile->isGroup()) {
|
||||
$group = $this->oprofile->localGroup();
|
||||
if ($user->isMember($group)) {
|
||||
@ -287,7 +402,7 @@ class OStatusSubAction extends Action
|
||||
}
|
||||
|
||||
if ($this->validateFeed()) {
|
||||
if ($this->arg('subscribe')) {
|
||||
if ($this->arg('submit')) {
|
||||
$this->saveFeed();
|
||||
return;
|
||||
}
|
||||
@ -343,7 +458,7 @@ class OStatusSubAction extends Action
|
||||
|
||||
function showPageNotice()
|
||||
{
|
||||
if ($this->error) {
|
||||
if (!empty($this->error)) {
|
||||
$this->element('p', 'error', $this->error);
|
||||
}
|
||||
}
|
||||
|
@ -65,12 +65,38 @@ 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));
|
||||
|
||||
$xrd->links[] = array('rel' => 'salmon',
|
||||
'href' => $salmon_url);
|
||||
|
||||
// Get this user's keypair
|
||||
$magickey = Magicsig::staticGet('user_id', $this->user->id);
|
||||
if (!$magickey) {
|
||||
// No keypair yet, let's generate one.
|
||||
$magickey = new Magicsig();
|
||||
$magickey->generate();
|
||||
}
|
||||
|
||||
$xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL,
|
||||
'href' => 'data:application/magic-public-key;'. $magickey->keypair);
|
||||
|
||||
// TODO - finalize where the redirect should go on the publisher
|
||||
$url = common_local_url('ostatussub') . '?profile={uri}';
|
||||
$xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/subscribe',
|
||||
|
@ -29,45 +29,86 @@
|
||||
|
||||
require_once 'Crypt/RSA.php';
|
||||
|
||||
interface Magicsig
|
||||
class Magicsig extends Memcached_DataObject
|
||||
{
|
||||
|
||||
public function sign($bytes);
|
||||
|
||||
public function verify($signed, $signature_b64);
|
||||
}
|
||||
|
||||
class MagicsigRsaSha256
|
||||
{
|
||||
|
||||
public $keypair;
|
||||
const PUBLICKEYREL = 'magic-public-key';
|
||||
|
||||
public function __construct($init = null)
|
||||
public $__table = 'magicsig';
|
||||
|
||||
public $user_id;
|
||||
public $keypair;
|
||||
public $alg;
|
||||
|
||||
private $_rsa;
|
||||
|
||||
public function __construct($alg = 'RSA-SHA256')
|
||||
{
|
||||
if (is_null($init)) {
|
||||
$this->generate();
|
||||
} else {
|
||||
$this->fromString($init);
|
||||
}
|
||||
$this->alg = $alg;
|
||||
}
|
||||
|
||||
public /*static*/ function staticGet($k, $v=null)
|
||||
{
|
||||
return parent::staticGet(__CLASS__, $k, $v);
|
||||
}
|
||||
|
||||
|
||||
function table()
|
||||
{
|
||||
return array(
|
||||
'user_id' => DB_DATAOBJECT_INT,
|
||||
'keypair' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
|
||||
'alg' => DB_DATAOBJECT_STR
|
||||
);
|
||||
}
|
||||
|
||||
static function schemaDef()
|
||||
{
|
||||
return array(new ColumnDef('user_id', 'integer',
|
||||
null, true, 'PRI'),
|
||||
new ColumnDef('keypair', 'varchar',
|
||||
255, false),
|
||||
new ColumnDef('alg', 'varchar',
|
||||
64, false));
|
||||
}
|
||||
|
||||
|
||||
function keys()
|
||||
{
|
||||
return array_keys($this->keyTypes());
|
||||
}
|
||||
|
||||
function keyTypes()
|
||||
{
|
||||
return array('user_id' => 'K');
|
||||
}
|
||||
|
||||
function insert()
|
||||
{
|
||||
$this->keypair = $this->toString();
|
||||
|
||||
return parent::insert();
|
||||
}
|
||||
|
||||
public function generate($key_length = 512)
|
||||
{
|
||||
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
|
||||
$keypair = new Crypt_RSA_KeyPair($key_length);
|
||||
$params['public_key'] = $keypair->getPublicKey();
|
||||
$params['private_key'] = $keypair->getPrivateKey();
|
||||
|
||||
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
$this->keypair = new Crypt_RSA($params);
|
||||
$this->_rsa = new Crypt_RSA($params);
|
||||
PEAR::popErrorHandling();
|
||||
|
||||
$this->insert();
|
||||
}
|
||||
|
||||
|
||||
public function toString($full_pair = true)
|
||||
{
|
||||
$public_key = $this->keypair->_public_key;
|
||||
$private_key = $this->keypair->_private_key;
|
||||
$public_key = $this->_rsa->_public_key;
|
||||
$private_key = $this->_rsa->_private_key;
|
||||
|
||||
$mod = base64_url_encode($public_key->getModulus());
|
||||
$exp = base64_url_encode($public_key->getExponent());
|
||||
@ -79,10 +120,12 @@ class MagicsigRsaSha256
|
||||
return 'RSA.' . $mod . '.' . $exp . $private_exp;
|
||||
}
|
||||
|
||||
public function fromString($text)
|
||||
public static function fromString($text)
|
||||
{
|
||||
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
|
||||
|
||||
$magic_sig = new Magicsig();
|
||||
|
||||
// remove whitespace
|
||||
$text = preg_replace('/\s+/', '', $text);
|
||||
|
||||
@ -100,33 +143,46 @@ class MagicsigRsaSha256
|
||||
$params['public_key'] = new Crypt_RSA_KEY($mod, $exp, 'public');
|
||||
if ($params['public_key']->isError()) {
|
||||
$error = $params['public_key']->getLastError();
|
||||
print $error->getMessage();
|
||||
exit;
|
||||
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||
return false;
|
||||
}
|
||||
if ($private_exp) {
|
||||
$params['private_key'] = new Crypt_RSA_KEY($mod, $private_exp, 'private');
|
||||
if ($params['private_key']->isError()) {
|
||||
$error = $params['private_key']->getLastError();
|
||||
print $error->getMessage();
|
||||
exit;
|
||||
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->keypair = new Crypt_RSA($params);
|
||||
$magic_sig->_rsa = new Crypt_RSA($params);
|
||||
PEAR::popErrorHandling();
|
||||
|
||||
return $magic_sig;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'RSA-SHA256';
|
||||
return $this->alg;
|
||||
}
|
||||
|
||||
public function getHash()
|
||||
{
|
||||
switch ($this->alg) {
|
||||
|
||||
case 'RSA-SHA256':
|
||||
return 'sha256';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function sign($bytes)
|
||||
{
|
||||
$sig = $this->keypair->createSign($bytes, null, 'sha256');
|
||||
if ($this->keypair->isError()) {
|
||||
$error = $this->keypair->getLastError();
|
||||
$sig = $this->_rsa->createSign($bytes, null, 'sha256');
|
||||
if ($this->_rsa->isError()) {
|
||||
$error = $this->_rsa->getLastError();
|
||||
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return $sig;
|
||||
@ -134,11 +190,11 @@ class MagicsigRsaSha256
|
||||
|
||||
public function verify($signed_bytes, $signature)
|
||||
{
|
||||
$result = $this->keypair->validateSign($signed_bytes, $signature, null, 'sha256');
|
||||
if ($this->keypair->isError()) {
|
||||
$result = $this->_rsa->validateSign($signed_bytes, $signature, null, 'sha256');
|
||||
if ($this->_rsa->isError()) {
|
||||
$error = $this->keypair->getLastError();
|
||||
//common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||
print $error->getMessage();
|
||||
common_log(LOG_DEBUG, 'RSA Error: '. $error->getMessage());
|
||||
return false;
|
||||
}
|
||||
return $result;
|
||||
}
|
@ -346,6 +346,29 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this remote profile has any active local subscriptions, and
|
||||
* if not drop the PuSH subscription feed.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function garbageCollect()
|
||||
{
|
||||
if ($this->isGroup()) {
|
||||
$members = $this->localGroup()->getMembers(0, 1);
|
||||
$count = $members->N;
|
||||
} else {
|
||||
$count = $this->localProfile()->subscriberCount();
|
||||
}
|
||||
if ($count == 0) {
|
||||
common_log(LOG_INFO, "Unsubscribing from now-unused remote feed $oprofile->feeduri");
|
||||
$this->unsubscribe();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an Activity Streams notification to the remote Salmon endpoint,
|
||||
* if so configured.
|
||||
@ -379,7 +402,8 @@ class Ostatus_profile extends Memcached_DataObject
|
||||
'xmlns:activity' => 'http://activitystrea.ms/spec/1.0/',
|
||||
'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
|
||||
'xmlns:georss' => 'http://www.georss.org/georss',
|
||||
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0');
|
||||
'xmlns:ostatus' => 'http://ostatus.org/schema/1.0',
|
||||
'xmlns:poco' => 'http://portablecontacts.net/spec/1.0');
|
||||
|
||||
$entry = new XMLStringer();
|
||||
$entry->elementStart('entry', $attributes);
|
||||
@ -464,7 +488,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) {
|
||||
@ -474,7 +498,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,15 +508,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");
|
||||
}
|
||||
@ -501,130 +522,176 @@ 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;
|
||||
}
|
||||
} 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);
|
||||
if ($actorUri == $this->uri) {
|
||||
// @fixme check if profile info has changed and update it
|
||||
} else {
|
||||
// @fixme drop or reject the messages once we've got the canonical profile URI recorded sanely
|
||||
common_log(LOG_INFO, "OStatus: Warning: non-group post with unexpected author: $actorUri expected $this->uri");
|
||||
//return;
|
||||
common_log(LOG_WARNING, "OStatus: skipping post with bad author: got $actorUri expected $this->uri");
|
||||
return false;
|
||||
}
|
||||
$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;
|
||||
|
||||
$dupe = Notice::staticGet('uri', $sourceUri);
|
||||
|
||||
if ($dupe) {
|
||||
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;
|
||||
|
||||
if ($activity->object->link) {
|
||||
$sourceUrl = $activity->object->link;
|
||||
} else if ($activity->link) {
|
||||
$sourceUrl = $activity->link;
|
||||
} else if (preg_match('!^https?://!', $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;
|
||||
|
||||
$params = array('is_local' => Notice::REMOTE_OMB,
|
||||
$options = array('is_local' => Notice::REMOTE_OMB,
|
||||
'url' => $sourceUrl,
|
||||
'uri' => $sourceUri);
|
||||
'uri' => $sourceUri,
|
||||
'rendered' => $rendered,
|
||||
'replies' => array(),
|
||||
'groups' => array());
|
||||
|
||||
$location = $activity->context->location;
|
||||
// Check for optional attributes...
|
||||
|
||||
if ($location) {
|
||||
$params['lat'] = $location->lat;
|
||||
$params['lon'] = $location->lon;
|
||||
if ($location->location_id) {
|
||||
$params['location_ns'] = $location->location_ns;
|
||||
$params['location_id'] = $location->location_id;
|
||||
}
|
||||
if (!empty($activity->time)) {
|
||||
$options['created'] = common_sql_date($activity->time);
|
||||
}
|
||||
|
||||
$profile = $oprofile->localProfile();
|
||||
$params['groups'] = array();
|
||||
$params['replies'] = array();
|
||||
if ($activity->context) {
|
||||
foreach ($activity->context->attention as $recipient) {
|
||||
$roprofile = Ostatus_profile::staticGet('uri', $recipient);
|
||||
if ($roprofile) {
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
$user = User::staticGet('uri', $recipient);
|
||||
if ($user) {
|
||||
// An @-reply directed to a local user.
|
||||
// @fixme sender verification, spam etc?
|
||||
$params['replies'][] = $recipient;
|
||||
continue;
|
||||
}
|
||||
|
||||
// @fixme we need a uri on user_group
|
||||
// $group = User_group::staticGet('uri', $recipient);
|
||||
$template = common_local_url('groupbyid', array('id' => '31337'));
|
||||
$template = preg_quote($template, '/');
|
||||
$template = str_replace('31337', '(\d+)', $template);
|
||||
common_log(LOG_DEBUG, $template);
|
||||
if (preg_match("/$template/", $recipient, $matches)) {
|
||||
$id = $matches[1];
|
||||
$group = User_group::staticGet('id', $id);
|
||||
if ($group) {
|
||||
// Deliver to all members of this local group.
|
||||
// @fixme sender verification?
|
||||
if ($profile->isMember($group)) {
|
||||
common_log(LOG_DEBUG, "delivering to group $id $group->nickname");
|
||||
$params['groups'][] = $group->id;
|
||||
} else {
|
||||
common_log(LOG_DEBUG, "not delivering to group $id $group->nickname because sender $profile->nickname is not a member");
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
common_log(LOG_DEBUG, "not delivering to missing group $id");
|
||||
}
|
||||
} else {
|
||||
common_log(LOG_DEBUG, "not delivering to groups for $recipient");
|
||||
}
|
||||
|
||||
$location = $activity->context->location;
|
||||
if ($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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$saved = Notice::saveNew($profile->id,
|
||||
$saved = Notice::saveNew($oprofile->profile_id,
|
||||
$content,
|
||||
'ostatus',
|
||||
$params);
|
||||
$options);
|
||||
if ($saved) {
|
||||
Ostatus_source::saveNew($saved, $this, $method);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, "Failed saving notice entry for $sourceUri: " . $e->getMessage());
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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());
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$groups = array();
|
||||
$replies = array();
|
||||
foreach ($attention_uris as $recipient) {
|
||||
// Is the recipient a local user?
|
||||
$user = User::staticGet('uri', $recipient);
|
||||
if ($user) {
|
||||
// @fixme sender verification, spam etc?
|
||||
$replies[] = $recipient;
|
||||
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;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the recipient a local group?
|
||||
// @fixme we need a uri on user_group
|
||||
// $group = User_group::staticGet('uri', $recipient);
|
||||
$template = common_local_url('groupbyid', array('id' => '31337'));
|
||||
$template = preg_quote($template, '/');
|
||||
$template = str_replace('31337', '(\d+)', $template);
|
||||
if (preg_match("/$template/", $recipient, $matches)) {
|
||||
$id = $matches[1];
|
||||
$group = User_group::staticGet('id', $id);
|
||||
if ($group) {
|
||||
// Deliver to all members of this local group if allowed.
|
||||
if ($sender->localProfile()->isMember($group)) {
|
||||
$groups[] = $group->id;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$attention_uris = $replies;
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,8 +27,6 @@
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
require_once 'magicsig.php';
|
||||
|
||||
class MagicEnvelope
|
||||
{
|
||||
const ENCODING = 'base64url';
|
||||
@ -64,7 +62,7 @@ class MagicEnvelope
|
||||
return false;
|
||||
}
|
||||
|
||||
$signature_alg = new MagicsigRsaSha256($this->getKeyPair($signer_uri));
|
||||
$signature_alg = Magicsig::fromString($this->getKeyPair($signer_uri));
|
||||
$armored_text = base64_encode($text);
|
||||
|
||||
return array(
|
||||
@ -139,7 +137,7 @@ class MagicEnvelope
|
||||
$text = base64_decode($env['data']);
|
||||
$signer_uri = $this->getAuthor($text);
|
||||
|
||||
$verifier = new MagicsigRsaSha256($this->getKeyPair($signer_uri));
|
||||
$verifier = Magicsig::fromString($this->getKeyPair($signer_uri));
|
||||
|
||||
return $verifier->verify($env['data'], $env['sig']);
|
||||
}
|
||||
|
@ -185,54 +185,6 @@ class SalmonAction extends Action
|
||||
function saveNotice()
|
||||
{
|
||||
$oprofile = $this->ensureProfile();
|
||||
|
||||
// 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;
|
||||
return $oprofile->processPost($this->act, 'salmon');
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,10 @@ class Webfinger
|
||||
|
||||
$content = $this->fetchURL($url);
|
||||
|
||||
if (!$content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return XRD::parse($content);
|
||||
}
|
||||
|
||||
|
@ -184,6 +184,7 @@ button.close,
|
||||
.form_user_unsubscribe input.submit,
|
||||
.form_group_join input.submit,
|
||||
.form_user_subscribe input.submit,
|
||||
.form_remote_authorize input.submit,
|
||||
.entity_subscribe a,
|
||||
.entity_moderation p,
|
||||
.entity_sandbox input.submit,
|
||||
@ -291,6 +292,7 @@ background-position:0 1px;
|
||||
.form_group_leave input.submit,
|
||||
.form_user_subscribe input.submit,
|
||||
.form_user_unsubscribe input.submit,
|
||||
.form_remote_authorize input.submit,
|
||||
.entity_subscribe a {
|
||||
background-color:#AAAAAA;
|
||||
color:#FFFFFF;
|
||||
@ -301,6 +303,7 @@ background-position:5px -1246px;
|
||||
}
|
||||
.form_group_join input.submit,
|
||||
.form_user_subscribe input.submit,
|
||||
.form_remote_authorize input.submit,
|
||||
.entity_subscribe a {
|
||||
background-position:5px -1181px;
|
||||
}
|
||||
|
@ -184,6 +184,7 @@ button.close,
|
||||
.form_user_unsubscribe input.submit,
|
||||
.form_group_join input.submit,
|
||||
.form_user_subscribe input.submit,
|
||||
.form_remote_authorize input.submit,
|
||||
.entity_subscribe a,
|
||||
.entity_moderation p,
|
||||
.entity_sandbox input.submit,
|
||||
@ -290,6 +291,7 @@ background-position:0 1px;
|
||||
.form_group_leave input.submit,
|
||||
.form_user_subscribe input.submit,
|
||||
.form_user_unsubscribe input.submit,
|
||||
.form_remote_authorize input.submit,
|
||||
.entity_subscribe a {
|
||||
background-color:#AAAAAA;
|
||||
color:#FFFFFF;
|
||||
@ -300,6 +302,7 @@ background-position:5px -1246px;
|
||||
}
|
||||
.form_group_join input.submit,
|
||||
.form_user_subscribe input.submit,
|
||||
.form_remote_authorize input.submit,
|
||||
.entity_subscribe a {
|
||||
background-position:5px -1181px;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user