OStatus support for people tags

This commit is contained in:
Shashi Gowda 2011-03-07 00:45:34 +05:30
parent b372ed721d
commit c335db4bbc
13 changed files with 1151 additions and 47 deletions

View File

@ -56,14 +56,25 @@ class OStatusPlugin extends Plugin
array('action' => 'ownerxrd'));
$m->connect('main/ostatus',
array('action' => 'ostatusinit'));
$m->connect('main/ostatustag',
array('action' => 'ostatustag'));
$m->connect('main/ostatustag?nickname=:nickname',
array('action' => 'ostatustag'), array('nickname' => '[A-Za-z0-9_-]+'));
$m->connect('main/ostatus?nickname=:nickname',
array('action' => 'ostatusinit'), array('nickname' => '[A-Za-z0-9_-]+'));
$m->connect('main/ostatus?group=:group',
array('action' => 'ostatusinit'), array('group' => '[A-Za-z0-9_-]+'));
$m->connect('main/ostatus?peopletag=:peopletag&tagger=:tagger',
array('action' => 'ostatusinit'), array('tagger' => '[A-Za-z0-9_-]+',
'peopletag' => '[A-Za-z0-9_-]+'));
// Remote subscription actions
$m->connect('main/ostatussub',
array('action' => 'ostatussub'));
$m->connect('main/ostatusgroup',
array('action' => 'ostatusgroup'));
$m->connect('main/ostatuspeopletag',
array('action' => 'ostatuspeopletag'));
// PuSH actions
$m->connect('main/push/hub', array('action' => 'pushhub'));
@ -79,6 +90,9 @@ class OStatusPlugin extends Plugin
$m->connect('main/salmon/group/:id',
array('action' => 'groupsalmon'),
array('id' => '[0-9]+'));
$m->connect('main/salmon/peopletag/:id',
array('action' => 'peopletagsalmon'),
array('id' => '[0-9]+'));
return true;
}
@ -149,6 +163,10 @@ class OStatusPlugin extends Plugin
$salmonAction = 'groupsalmon';
$group = $feed->getGroup();
$id = $group->id;
} else if ($feed instanceof AtomListNoticeFeed) {
$salmonAction = 'peopletagsalmon';
$peopletag = $feed->getList();
$id = $peopletag->id;
} else {
return true;
}
@ -210,21 +228,7 @@ class OStatusPlugin extends Plugin
*/
function onStartProfileRemoteSubscribe($output, $profile)
{
$cur = common_current_user();
if (empty($cur)) {
// Add an OStatus subscribe
$output->elementStart('li', 'entity_subscribe');
$url = common_local_url('ostatusinit',
array('nickname' => $profile->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
// TRANS: Link description for link to subscribe to a remote user.
_m('Subscribe'));
$output->elementEnd('li');
}
$this->onStartProfileListItemActionElements($output, $profile);
return false;
}
@ -238,13 +242,119 @@ class OStatusPlugin extends Plugin
array('group' => $group->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
// TRANS: Link description for link to join a remote group.
_m('Join'));
}
return true;
}
function onStartSubscribePeopletagForm($output, $peopletag)
{
$cur = common_current_user();
if (empty($cur)) {
$output->elementStart('li', 'entity_subscribe');
$profile = $peopletag->getTagger();
$url = common_local_url('ostatusinit',
array('tagger' => $profile->nickname, 'peopletag' => $peopletag->tag));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
_m('Subscribe'));
$output->elementEnd('li');
return false;
}
return true;
}
function onStartShowTagProfileForm($action, $profile)
{
$action->elementStart('form', array('method' => 'post',
'id' => 'form_tag_user',
'class' => 'form_settings',
'name' => 'tagprofile',
'action' => common_local_url('tagprofile', array('id' => @$profile->id))));
$action->elementStart('fieldset');
$action->element('legend', null, _('Tag remote profile'));
$action->hidden('token', common_session_token());
$user = common_current_user();
$action->elementStart('ul', 'form_data');
$action->elementStart('li');
$action->input('uri', _('Remote profile'), $action->trimmed('uri'),
_('OStatus user\'s address, like nickname@example.com or http://example.net/nickname'));
$action->elementEnd('li');
$action->elementEnd('ul');
$action->submit('fetch', _('Fetch'));
$action->elementEnd('fieldset');
$action->elementEnd('form');
}
function onStartTagProfileAction($action, $profile)
{
$err = null;
$uri = $action->trimmed('uri');
if (!$profile && $uri) {
try {
if (Validate::email($uri)) {
$oprofile = Ostatus_profile::ensureWebfinger($uri);
} else if (Validate::uri($uri)) {
$oprofile = Ostatus_profile::ensureProfileURL($uri);
} else {
throw new Exception('Invalid URI');
}
// redirect to the new profile.
common_redirect(common_local_url('tagprofile', array('id' => $oprofile->profile_id)), 303);
return false;
} catch (Exception $e) {
$err = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
}
$action->showForm($err);
return false;
}
return true;
}
/*
* If the field being looked for is URI look for the profile
*/
function onStartProfileCompletionSearch($action, $profile, $search_engine) {
if ($action->field == 'uri') {
$user = new User();
$profile->joinAdd($user);
$profile->whereAdd('uri LIKE "%' . $profile->escape($q) . '%"');
$profile->query();
if ($profile->N == 0) {
try {
if (Validate::email($q)) {
$oprofile = Ostatus_profile::ensureWebfinger($q);
} else if (Validate::uri($q)) {
$oprofile = Ostatus_profile::ensureProfileURL($q);
} else {
throw new Exception('Invalid URI');
}
return $this->filter(array($oprofile->localProfile()));
} catch (Exception $e) {
$this->msg = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
return array();
}
}
return false;
}
return true;
}
/**
* Find any explicit remote mentions. Accepted forms:
* Webfinger: @user@example.com
@ -711,6 +821,95 @@ class OStatusPlugin extends Plugin
}
}
/**
* When one of our local users tries to subscribe to a remote peopletag,
* notify the remote server. If the notification is rejected,
* deny the subscription.
*
* @param Profile_list $peopletag
* @param User $user
*
* @return mixed hook return value
*/
function onStartSubscribePeopletag($peopletag, $user)
{
$oprofile = Ostatus_profile::staticGet('peopletag_id', $peopletag->id);
if ($oprofile) {
if (!$oprofile->subscribe()) {
throw new Exception(_m('Could not set up remote peopletag subscription.'));
}
$sub = $user->getProfile();
$tagger = Profile::staticGet($peopletag->tagger);
$act = new Activity();
$act->id = TagURI::mint('subscribe_peopletag:%d:%d:%s',
$sub->id,
$peopletag->id,
common_date_iso8601(time()));
$act->actor = ActivityObject::fromProfile($sub);
$act->verb = ActivityVerb::FOLLOW;
$act->object = $oprofile->asActivityObject();
$act->time = time();
$act->title = _m("Follow list");
$act->content = sprintf(_m("%s is now following people tagged %s by %s."),
$sub->getBestName(),
$oprofile->getBestName(),
$tagger->getBestName());
if ($oprofile->notifyActivity($act, $sub)) {
return true;
} else {
$oprofile->garbageCollect();
throw new Exception(_m("Failed subscribing to remote peopletag."));
}
}
}
/**
* When one of our local users unsubscribes to a remote peopletag, notify the remote
* server.
*
* @param Profile_list $peopletag
* @param User $user
*
* @return mixed hook return value
*/
function onEndUnsubscribePeopletag($peopletag, $user)
{
$oprofile = Ostatus_profile::staticGet('peopletag_id', $peopletag->id);
if ($oprofile) {
// Drop the PuSH subscription if there are no other subscribers.
$oprofile->garbageCollect();
$sub = Profile::staticGet($user->id);
$tagger = Profile::staticGet($peopletag->tagger);
$act = new Activity();
$act->id = TagURI::mint('unsubscribe_peopletag:%d:%d:%s',
$sub->id,
$peopletag->id,
common_date_iso8601(time()));
$act->actor = ActivityObject::fromProfile($member);
$act->verb = ActivityVerb::UNFOLLOW;
$act->object = $oprofile->asActivityObject();
$act->time = time();
$act->title = _m("Unfollow peopletag");
$act->content = sprintf(_m("%s stopped following the list %s by %s."),
$sub->getBestName(),
$oprofile->getBestName(),
$tagger->getBestName());
$oprofile->notifyActivity($act, $user);
}
}
/**
* Notify remote users when their notices get favorited.
*
@ -747,6 +946,91 @@ class OStatusPlugin extends Plugin
return true;
}
function onEndTagProfile($ptag)
{
$oprofile = Ostatus_profile::staticGet('profile_id', $ptag->tagged);
if (empty($oprofile)) {
return true;
}
$plist = $ptag->getMeta();
if ($plist->private) {
return true;
}
$act = new Activity();
$tagger = $plist->getTagger();
$tagged = Profile::staticGet('id', $ptag->tagged);
$act->verb = ActivityVerb::TAG;
$act->id = TagURI::mint('tag_profile:%d:%d:%s',
$plist->tagger, $plist->id,
common_date_iso8601(time()));
$act->time = time();
$act->title = _("Tag");
$act->content = sprintf(_("%s tagged %s in the list %s"),
$tagger->getBestName(),
$tagged->getBestName(),
$plist->getBestName());
$act->actor = ActivityObject::fromProfile($tagger);
$act->objects = array(ActivityObject::fromProfile($tagged));
$act->target = ActivityObject::fromPeopletag($plist);
$oprofile->notifyActivity($act, $tagger);
// initiate a PuSH subscription for the person being tagged
if (!$oprofile->subscribe()) {
throw new Exception(sprintf(_('Could not complete subscription to remote '.
'profile\'s feed. Tag %s could not be saved.'), $ptag->tag));
return false;
}
return true;
}
function onEndUntagProfile($ptag)
{
$oprofile = Ostatus_profile::staticGet('profile_id', $ptag->tagged);
if (empty($oprofile)) {
return true;
}
$plist = $ptag->getMeta();
if ($plist->private) {
return true;
}
$act = new Activity();
$tagger = $plist->getTagger();
$tagged = Profile::staticGet('id', $ptag->tagged);
$act->verb = ActivityVerb::UNTAG;
$act->id = TagURI::mint('untag_profile:%d:%d:%s',
$plist->tagger, $plist->id,
common_date_iso8601(time()));
$act->time = time();
$act->title = _("Untag");
$act->content = sprintf(_("%s untagged %s from the list %s"),
$tagger->getBestName(),
$tagged->getBestName(),
$plist->getBestName());
$act->actor = ActivityObject::fromProfile($tagger);
$act->objects = array(ActivityObject::fromProfile($tagged));
$act->target = ActivityObject::fromPeopletag($plist);
$oprofile->notifyActivity($act, $tagger);
// unsubscribe to PuSH feed if no more required
$oprofile->garbageCollect();
return true;
}
/**
* Notify remote users when their notices get de-favorited.
*
@ -913,7 +1197,7 @@ class OStatusPlugin extends Plugin
return true;
}
function onStartProfileListItemActionElements($item)
function onStartProfileListItemActionElements($item, $profile=null)
{
if (!common_logged_in()) {
@ -921,7 +1205,12 @@ class OStatusPlugin extends Plugin
if (!empty($profileUser)) {
$output = $item->out;
if ($item instanceof Action) {
$output = $item;
$profile = $item->profile;
} else {
$output = $item->out;
}
// Add an OStatus subscribe
$output->elementStart('li', 'entity_subscribe');
@ -932,6 +1221,14 @@ class OStatusPlugin extends Plugin
// TRANS: Link text for a user to subscribe to an OStatus user.
_m('Subscribe'));
$output->elementEnd('li');
$output->elementStart('li', 'entity_tag');
$url = common_local_url('ostatustag',
array('nickname' => $profileUser->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_tag'),
_m('Tag'));
$output->elementEnd('li');
}
}

View File

@ -29,6 +29,8 @@ if (!defined('STATUSNET')) {
class OStatusInitAction extends Action
{
var $nickname;
var $tagger;
var $peopletag;
var $group;
var $profile;
var $err;
@ -45,6 +47,8 @@ class OStatusInitAction extends Action
// Local user or group the remote wants to subscribe to
$this->nickname = $this->trimmed('nickname');
$this->tagger = $this->trimmed('tagger');
$this->peopletag = $this->trimmed('peopletag');
$this->group = $this->trimmed('group');
// Webfinger or profile URL of the remote user
@ -96,8 +100,12 @@ class OStatusInitAction extends Action
if ($this->group) {
// TRANS: Form legend.
$header = sprintf(_m('Join group %s'), $this->group);
// TRANS: Button text.
$submit = _m('BUTTON','Join');
} else if ($this->peopletag && $this->tagger) {
$header = sprintf(_m('Subscribe to people tagged %s by %s'), $this->peopletag, $this->tagger);
$submit = _m('Subscribe');
$submit = _m('BUTTON','Subscribe');
// TRANS: Button text.
} else {
// TRANS: Form legend.
$header = sprintf(_m('Subscribe to %s'), $this->nickname);
@ -114,6 +122,7 @@ class OStatusInitAction extends Action
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'ostatus_nickname'));
if ($this->group) {
// TRANS: Field label.
$this->input('group', _m('Group nickname'), $this->group,
@ -122,7 +131,10 @@ class OStatusInitAction extends Action
// TRANS: Field label.
$this->input('nickname', _m('User nickname'), $this->nickname,
_m('Nickname of the user you want to follow.'));
$this->hidden('tagger', $this->tagger);
$this->hidden('peopletag', $this->peopletag);
}
$this->elementEnd('li');
$this->elementStart('li', array('id' => 'ostatus_profile'));
// TRANS: Field label.
@ -211,6 +223,18 @@ class OStatusInitAction extends Action
// TRANS: Client error.
$this->clientError("No such group.");
}
} else if ($this->peopletag && $this->tagger) {
$user = User::staticGet('nickname', $this->tagger);
if (empty($user)) {
$this->clientError("No such user.");
}
$peopletag = Profile_list::getByTaggerAndTag($user->id, $this->peopletag);
if ($peopletag) {
return common_local_url('profiletagbyid',
array('tagger_id' => $user->id, 'id' => $peopletag->id));
}
$this->clientError("No such people tag.");
} else {
// TRANS: Client error.
$this->clientError("No local user or group nickname provided.");

View File

@ -0,0 +1,179 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009-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/>.
*/
/**
* @package OStatusPlugin
* @maintainer Brion Vibber <brion@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
require_once INSTALLDIR . '/lib/peopletaglist.php';
/**
* Key UI methods:
*
* showInputForm() - form asking for a remote profile account or URL
* We end up back here on errors
*
* showPreviewForm() - surrounding form for preview-and-confirm
* preview() - display profile for a remote group
*
* success() - redirects to groups page on join
*/
class OStatusPeopletagAction extends OStatusSubAction
{
protected $profile_uri; // provided acct: or URI of remote entity
protected $oprofile; // Ostatus_profile of remote entity, if valid
function validateRemoteProfile()
{
if (!$this->oprofile->isPeopletag()) {
// Send us to the user subscription form for conf
$target = common_local_url('ostatussub', array(), array('profile' => $this->profile_uri));
common_redirect($target, 303);
}
}
/**
* Show the initial form, when we haven't yet been given a valid
* remote profile.
*/
function showInputForm()
{
$this->elementStart('form', array('method' => 'post',
'id' => 'form_ostatus_sub',
'class' => 'form_settings',
'action' => $this->selfLink()));
$this->hidden('token', common_session_token());
$this->elementStart('fieldset', array('id' => 'settings_feeds'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('profile',
_m('Subscribe to people tag'),
$this->profile_uri,
_m("Address of the OStatus people tag, like http://example.net/user/all/tag"));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('validate', _m('Continue'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
/**
* Show a preview for a remote peopletag's profile
* @return boolean true if we're ok to try joining
*/
function preview()
{
$oprofile = $this->oprofile;
$ptag = $oprofile->localPeopletag();
$cur = common_current_user();
if ($ptag->hasSubscriber($cur->id)) {
$this->element('div', array('class' => 'error'),
_m("You are already subscribed to this peopletag."));
$ok = false;
} else {
$ok = true;
}
$this->showEntity($ptag);
return $ok;
}
function showEntity($ptag)
{
$this->elementStart('div', 'peopletag');
$widget = new PeopletagListItem($ptag, common_current_user(), $this);
$widget->showCreator();
$widget->showTag();
$widget->showDescription();
$this->elementEnd('div');
}
/**
* Redirect on successful remote people tag subscription
*/
function success()
{
$cur = common_current_user();
$url = common_local_url('peopletagsubscriptions', array('nickname' => $cur->nickname));
common_redirect($url, 303);
}
/**
* Attempt to finalize subscription.
* validateFeed must have been run first.
*
* Calls showForm on failure or success on success.
*/
function saveFeed()
{
$user = common_current_user();
$ptag = $this->oprofile->localPeopletag();
if ($ptag->hasSubscriber($user->id)) {
// TRANS: OStatus remote group subscription dialog error.
$this->showForm(_m('Already subscribed!'));
return;
}
try {
Profile_tag_subscription::add($ptag, $user);
$this->success();
} catch (Exception $e) {
$this->showForm($e->getMessage());
}
}
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
// TRANS: Page title for OStatus remote people tag subscription form
return _m('Confirm subscription to remote people tag');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return _m('You can subscribe to people tags from other supported sites. Paste the tag\'s profile URI below:');
}
function selfLink()
{
return common_local_url('ostatuspeopletag');
}
}

View File

@ -270,10 +270,13 @@ class OStatusSubAction extends Action
function validateRemoteProfile()
{
// Send us to the respective subscription form for conf
if ($this->oprofile->isGroup()) {
// Send us to the group subscription form for conf
$target = common_local_url('ostatusgroup', array(), array('profile' => $this->profile_uri));
common_redirect($target, 303);
} else if ($this->oprofile->isPeopletag()) {
$target = common_local_url('ostatuspeopletag', array(), array('profile' => $this->profile_uri));
common_redirect($target, 303);
}
}

View File

@ -0,0 +1,117 @@
<?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/>.
*/
/**
* @package OStatusPlugin
* @maintainer James Walker <james@status.net>
*/
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class OStatusTagAction extends OStatusInitAction
{
var $nickname;
var $profile;
var $err;
function prepare($args)
{
parent::prepare($args);
if (common_logged_in()) {
$this->clientError(_m('You can use the local tagging!'));
return false;
}
$this->nickname = $this->trimmed('nickname');
// Webfinger or profile URL of the remote user
$this->profile = $this->trimmed('profile');
return true;
}
function showContent()
{
$header = sprintf(_m('Tag %s'), $this->nickname);
$submit = _m('Go');
$this->elementStart('form', array('id' => 'form_ostatus_connect',
'method' => 'post',
'class' => 'form_settings',
'action' => common_local_url('ostatustag')));
$this->elementStart('fieldset');
$this->element('legend', null, $header);
$this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'ostatus_nickname'));
$this->input('nickname', _m('User nickname'), $this->nickname,
_m('Nickname of the user you want to tag'));
$this->elementEnd('li');
$this->elementStart('li', array('id' => 'ostatus_profile'));
$this->input('profile', _m('Profile Account'), $this->profile,
_m('Your account id (i.e. user@identi.ca)'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('submit', $submit);
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
function connectWebfinger($acct)
{
$target_profile = $this->targetProfile();
$disco = new Discovery;
$result = $disco->lookup($acct);
if (!$result) {
$this->clientError(_m("Couldn't look up OStatus account profile."));
}
foreach ($result->links as $link) {
if ($link['rel'] == 'http://ostatus.org/schema/1.0/tag') {
// We found a URL - let's redirect!
$url = Discovery::applyTemplate($link['template'], $target_profile);
common_log(LOG_INFO, "Sending remote subscriber $acct to $url");
common_redirect($url, 303);
}
}
$this->clientError(_m("Couldn't confirm remote profile address."));
}
function connectProfile($subscriber_profile)
{
$target_profile = $this->targetProfile();
// @fixme hack hack! We should look up the remote sub URL from XRDS
$suburl = preg_replace('!^(.*)/(.*?)$!', '$1/main/tagprofile', $subscriber_profile);
$suburl .= '?uri=' . urlencode($target_profile);
common_log(LOG_INFO, "Sending remote subscriber $subscriber_profile to $suburl");
common_redirect($suburl, 303);
}
function title()
{
return _m('OStatus people tag');
}
}

View File

@ -0,0 +1,141 @@
<?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/>.
*/
/**
* @package OStatusPlugin
*/
if (!defined('STATUSNET')) {
exit(1);
}
class PeopletagsalmonAction extends SalmonAction
{
var $peopletag = null;
function prepare($args)
{
parent::prepare($args);
$id = $this->trimmed('id');
if (!$id) {
$this->clientError(_('No ID.'));
}
$this->peopletag = Profile_list::staticGet('id', $id);
if (empty($this->peopletag)) {
$this->clientError(_('No such peopletag.'));
}
$oprofile = Ostatus_profile::staticGet('peopletag_id', $id);
if (!empty($oprofile)) {
$this->clientError(_m("Can't accept remote posts for a remote peopletag."));
}
return true;
}
/**
* We've gotten a follow/subscribe notification from a remote user.
* Save a subscription relationship for them.
*/
/**
* Postel's law: consider a "follow" notification as a "join".
*/
function handleFollow()
{
$this->handleSubscribe();
}
/**
* Postel's law: consider an "unfollow" notification as a "unsubscribe".
*/
function handleUnfollow()
{
$this->handleUnsubscribe();
}
/**
* A remote user subscribed.
* @fixme move permission checks and event call into common code,
* currently we're doing the main logic in joingroup action
* and so have to repeat it here.
*/
function handleSubscribe()
{
$oprofile = $this->ensureProfile();
if (!$oprofile) {
$this->clientError(_m("Can't read profile to set up profiletag subscription."));
}
if ($oprofile->isGroup()) {
$this->clientError(_m("Groups can't subscribe to peopletags."));
}
common_log(LOG_INFO, "Remote profile {$oprofile->uri} subscribing to local peopletag ".$this->peopletag->getBestName());
$profile = $oprofile->localProfile();
if ($this->peopletag->hasSubscriber($profile)) {
// Already a member; we'll take it silently to aid in resolving
// inconsistencies on the other side.
return true;
}
// should we block those whom the tagger has blocked from listening to
// his own updates?
try {
Profile_tag_subscription::add($this->peopletag, $profile);
} catch (Exception $e) {
$this->serverError(sprintf(_m('Could not subscribe remote user %1$s to peopletag %2$s.'),
$oprofile->uri, $this->peopletag->getBestName()));
}
}
/**
* A remote user unsubscribed from our peopletag.
*/
function handleUnsubscribe()
{
$oprofile = $this->ensureProfile();
if (!$oprofile) {
$this->clientError(_m("Can't read profile to cancel peopletag membership."));
}
if ($oprofile->isGroup()) {
$this->clientError(_m("Groups can't subscribe to peopletags."));
}
common_log(LOG_INFO, "Remote profile {$oprofile->uri} unsubscribing from local peopletag ".$this->peopletag->getBestName());
$profile = $oprofile->localProfile();
try {
Profile_tag_subscription::remove($this->peopletag->tagger, $this->peopletag->tag, $profile->id);
} catch (Exception $e) {
$this->serverError(sprintf(_m('Could not remove remote user %1$s from peopletag %2$s.'),
$oprofile->uri, $this->peopletag->getBestName()));
return;
}
}
}

View File

@ -176,7 +176,22 @@ class PushHubAction extends Action
return true;
}
}
common_log(LOG_DEBUG, "Not a user or group feed? $feed $userFeed $groupFeed");
} else if (preg_match('!/(\d+)/lists/(\d+)/statuses\.atom$!', $feed, $matches)) {
$user = $matches[1];
$id = $matches[2];
$params = array('user' => $user, 'id' => $id, 'format' => 'atom');
$listFeed = common_local_url('ApiTimelineList', $params);
if ($feed == $listFeed) {
$list = Profile_list::staticGet('id', $id);
$user = User::staticGet('id', $user);
if (!$list || !$user || $list->tagger != $user->id) {
throw new ClientException("Invalid hub.topic $feed; people tag doesn't exist.");
} else {
return true;
}
}
common_log(LOG_DEBUG, "Not a user, group or people tag feed? $feed $userFeed $groupFeed $listFeed");
}
common_log(LOG_DEBUG, "LOST $feed");
return false;

View File

@ -185,6 +185,67 @@ class UsersalmonAction extends SalmonAction
$fave->delete();
}
function handleTag()
{
if ($this->activity->target->type == ActivityObject::_LIST) {
if ($this->activity->objects[0]->type != ActivityObject::PERSON) {
throw new ClientException("Not a person object");
return false;
}
// this is a peopletag
$tagged = User::staticGet('uri', $this->activity->objects[0]->id);
if (empty($tagged)) {
throw new ClientException("Unidentified profile being tagged");
}
if ($tagged->id !== $this->user->id) {
throw new ClientException("This user is not the one being tagged");
}
// save the list
$tagger = $this->ensureProfile();
$list = Ostatus_profile::ensureActivityObjectProfile($this->activity->target);
$ptag = $list->localPeopletag();
$result = Profile_tag::setTag($ptag->tagger, $tagged->id, $ptag->tag);
if (!$result) {
throw new ClientException("The tag could not be saved.");
}
}
}
function handleUntag()
{
if ($this->activity->target->type == ActivityObject::_LIST) {
if ($this->activity->objects[0]->type != ActivityObject::PERSON) {
throw new ClientException("Not a person object");
return false;
}
// this is a peopletag
$tagged = User::staticGet('uri', $this->activity->objects[0]->id);
if (empty($tagged)) {
throw new ClientException("Unidentified profile being untagged");
}
if ($tagged->id !== $this->user->id) {
throw new ClientException("This user is not the one being untagged");
}
// save the list
$tagger = $this->ensureProfile();
$list = Ostatus_profile::ensureActivityObjectProfile($this->activity->target);
$ptag = $list->localPeopletag();
$result = Profile_tag::unTag($ptag->tagger, $tagged->id, $ptag->tag);
if (!$result) {
throw new ClientException("The tag could not be deleted.");
}
}
}
/**
* @param ActivityObject $object
* @return Notice

View File

@ -34,6 +34,7 @@ class Ostatus_profile extends Managed_DataObject
public $profile_id;
public $group_id;
public $peopletag_id;
public $feeduri;
public $salmonuri;
@ -60,6 +61,7 @@ class Ostatus_profile extends Managed_DataObject
'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true),
'profile_id' => array('type' => 'integer'),
'group_id' => array('type' => 'integer'),
'peopletag_id' => array('type' => 'integer'),
'feeduri' => array('type' => 'varchar', 'length' => 255),
'salmonuri' => array('type' => 'varchar', 'length' => 255),
'avatar' => array('type' => 'text'),
@ -70,11 +72,13 @@ class Ostatus_profile extends Managed_DataObject
'unique keys' => array(
'ostatus_profile_profile_id_idx' => array('profile_id'),
'ostatus_profile_group_id_idx' => array('group_id'),
'ostatus_profile_peopletag_id_idx' => array('peopletag_id'),
'ostatus_profile_feeduri_idx' => array('feeduri'),
),
'foreign keys' => array(
'ostatus_profile_profile_id_fkey' => array('profile', array('profile_id' => 'id')),
'ostatus_profile_group_id_fkey' => array('user_group', array('group_id' => 'id')),
'ostatus_profile_peopletag_id_fkey' => array('profile_list', array('peopletag_id' => 'id')),
),
);
}
@ -103,6 +107,18 @@ class Ostatus_profile extends Managed_DataObject
return null;
}
/**
* Fetch the StatusNet-side peopletag for this feed
* @return Profile
*/
public function localPeopletag()
{
if ($this->peopletag_id) {
return Profile_list::staticGet('id', $this->peopletag_id);
}
return null;
}
/**
* Returns an ActivityObject describing this remote user or group profile.
* Can then be used to generate Atom chunks.
@ -113,6 +129,8 @@ class Ostatus_profile extends Managed_DataObject
{
if ($this->isGroup()) {
return ActivityObject::fromGroup($this->localGroup());
} else if ($this->isPeopletag()) {
return ActivityObject::fromPeopletag($this->localPeopletag());
} else {
return ActivityObject::fromProfile($this->localProfile());
}
@ -134,6 +152,9 @@ class Ostatus_profile extends Managed_DataObject
if ($this->isGroup()) {
$noun = ActivityObject::fromGroup($this->localGroup());
return $noun->asString('activity:' . $element);
} else if ($this->isPeopletag()) {
$noun = ActivityObject::fromPeopletag($this->localPeopletag());
return $noun->asString('activity:' . $element);
} else {
$noun = ActivityObject::fromProfile($this->localProfile());
return $noun->asString('activity:' . $element);
@ -145,16 +166,34 @@ class Ostatus_profile extends Managed_DataObject
*/
function isGroup()
{
if ($this->profile_id && !$this->group_id) {
if ($this->profile_id || $this->peopletag_id && !$this->group_id) {
return false;
} else if ($this->group_id && !$this->profile_id) {
} else if ($this->group_id && !$this->profile_id && !$this->peopletag_id) {
return true;
} else if ($this->group_id && $this->profile_id) {
// TRANS: Server exception. %s is a URI.
throw new ServerException(sprintf(_m('Invalid ostatus_profile state: both group and profile IDs set for %s.'),$this->uri));
} else if ($this->group_id && ($this->profile_id || $this->peopletag_id)) {
// TRANS: Server exception. %s is a URI
throw new ServerException(_m("Invalid ostatus_profile state: two or more IDs set for %s", $this->uri));
} else {
// TRANS: Server exception. %s is a URI.
throw new ServerException(sprintf(_m('Invalid ostatus_profile state: both group and profile IDs empty for %s.'),$this->uri));
// TRANS: Server exception. %s is a URI
throw new ServerException(_m("Invalid ostatus_profile state: all IDs empty for %s", $this->uri));
}
}
/**
* @return boolean true if this is a remote peopletag
*/
function isPeopletag()
{
if ($this->profile_id || $this->group_id && !$this->peopletag_id) {
return false;
} else if ($this->peopletag_id && !$this->profile_id && !$this->group_id) {
return true;
} else if ($this->peopletag_id && ($this->profile_id || $this->group_id)) {
// TRANS: Server exception. %s is a URI
throw new ServerException(_m("Invalid ostatus_profile state: two or more IDs set for %s", $this->uri));
} else {
// TRANS: Server exception. %s is a URI
throw new ServerException(_m("Invalid ostatus_profile state: all IDs empty for %s", $this->uri));
}
}
@ -214,8 +253,15 @@ class Ostatus_profile extends Managed_DataObject
if ($this->isGroup()) {
$members = $this->localGroup()->getMembers(0, 1);
$count = $members->N;
} else if ($this->isPeopletag()) {
$subscribers = $this->localPeopletag()->getSubscribers(0, 1);
$count = $subscribers->N;
} else {
$count = $this->localProfile()->subscriberCount();
$profile = $this->localProfile();
$count = $profile->subscriberCount();
if ($profile->hasLocalTags()) {
$count = 1;
}
}
common_log(LOG_INFO, __METHOD__ . " SUB COUNT BEFORE: $count");
@ -235,7 +281,7 @@ class Ostatus_profile extends Managed_DataObject
* @param string $verb Activity::SUBSCRIBE or Activity::JOIN
* @param Object $object object of the action; must define asActivityNoun($tag)
*/
public function notify($actor, $verb, $object=null)
public function notify($actor, $verb, $object=null, $target=null)
{
if (!($actor instanceof Profile)) {
$type = gettype($actor);
@ -277,6 +323,9 @@ class Ostatus_profile extends Managed_DataObject
$entry->raw($actor->asAtomAuthor());
$entry->raw($actor->asActivityActor());
$entry->raw($object->asActivityNoun('object'));
if ($target != null) {
$entry->raw($target->asActivityNoun('target'));
}
$entry->elementEnd('entry');
$xml = $entry->getString();
@ -346,6 +395,8 @@ class Ostatus_profile extends Managed_DataObject
{
if ($this->isGroup()) {
return $this->localGroup()->getBestName();
} else if ($this->isPeopletag()) {
return $this->localPeopletag()->getBestName();
} else {
return $this->localProfile()->getBestName();
}
@ -550,6 +601,7 @@ class Ostatus_profile extends Managed_DataObject
'rendered' => $rendered,
'replies' => array(),
'groups' => array(),
'peopletags' => array(),
'tags' => array(),
'urls' => array());
@ -586,6 +638,10 @@ class Ostatus_profile extends Managed_DataObject
}
}
if ($this->isPeopletag()) {
$options['peopletags'][] = $this->localPeopletag();
}
// Atom categories <-> hashtags
foreach ($activity->categories as $cat) {
if ($cat->term) {
@ -1202,6 +1258,14 @@ class Ostatus_profile extends Managed_DataObject
throw new Exception(_m('Local group can\'t be referenced as remote.'));
}
$ptag = Profile_list::staticGet('uri', $homeuri);
if ($ptag) {
$local_user = User::staticGet('id', $ptag->tagger);
if (!empty($local_user)) {
throw new Exception("Local peopletag can't be referenced as remote.");
}
}
if (array_key_exists('feedurl', $hints)) {
$feeduri = $hints['feedurl'];
} else {
@ -1253,7 +1317,7 @@ class Ostatus_profile extends Managed_DataObject
// TRANS: Server exception.
throw new ServerException(_m('Can\'t save local profile.'));
}
} else {
} else if ($object->type == ActivityObject::GROUP) {
$group = new User_group();
$group->uri = $homeuri;
$group->created = common_sql_now();
@ -1264,6 +1328,16 @@ class Ostatus_profile extends Managed_DataObject
// TRANS: Server exception.
throw new ServerException(_m('Can\'t save local profile.'));
}
} else if ($object->type == ActivityObject::_LIST) {
$ptag = new Profile_list();
$ptag->uri = $homeuri;
$ptag->created = common_sql_now();
self::updatePeopletag($ptag, $object, $hints);
$oprofile->peopletag_id = $ptag->insert();
if (!$oprofile->peopletag_id) {
throw new ServerException("Can't save local peopletag");
}
}
$ok = $oprofile->insert();
@ -1298,12 +1372,16 @@ class Ostatus_profile extends Managed_DataObject
if ($this->isGroup()) {
$group = $this->localGroup();
self::updateGroup($group, $object, $hints);
} else if ($this->isPeopletag()) {
$ptag = $this->localPeopletag();
self::updatePeopletag($ptag, $object, $hints);
} else {
$profile = $this->localProfile();
self::updateProfile($profile, $object, $hints);
}
$avatar = self::getActivityObjectAvatar($object, $hints);
if ($avatar) {
if ($avatar && !isset($ptag)) {
try {
$this->updateAvatar($avatar);
} catch (Exception $ex) {
@ -1401,6 +1479,27 @@ class Ostatus_profile extends Managed_DataObject
}
}
protected static function updatePeopletag($tag, $object, $hints=array()) {
$orig = clone($tag);
$tag->tag = $object->title;
if (!empty($object->link)) {
$tag->mainpage = $object->link;
} else if (array_key_exists('profileurl', $hints)) {
$tag->mainpage = $hints['profileurl'];
}
$tag->description = $object->summary;
$tagger = self::ensureActivityObjectProfile($object->owner);
$tag->tagger = $tagger->profile_id;
if ($tag->id) {
common_log(LOG_DEBUG, "Updating OStatus peopletag $tag->id from remote info $object->id: " . var_export($object, true) . var_export($hints, true));
$tag->update($orig);
}
}
protected static function getActivityObjectHomepage($object, $hints=array())
{
$homepage = null;
@ -1781,15 +1880,14 @@ class Ostatus_profile extends Managed_DataObject
function checkAuthorship($activity)
{
if ($this->isGroup()) {
// A group feed will contain posts from multiple authors.
// @fixme validate these profiles in some way!
if ($this->isGroup() || $this->isPeopletag()) {
// A group or propletag feed will contain posts from multiple authors.
$oprofile = self::ensureActorProfile($activity);
if ($oprofile->isGroup()) {
if ($oprofile->isGroup() || $oprofile->isPeopletag()) {
// 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");
common_log(LOG_WARNING,
"OStatus: skipping post with group listed ".
"as author: $oprofile->uri in feed from $this->uri");
return false;
}
} else {

View File

@ -92,7 +92,8 @@ SN.U.DialogBox = {
};
SN.Init.Subscribe = function() {
$('.entity_subscribe .entity_remote_subscribe').live('click', function() { SN.U.DialogBox.Subscribe($(this)); return false; });
$('.entity_subscribe .entity_remote_subscribe, .entity_tag .entity_remote_tag')
.live('click', function() { SN.U.DialogBox.Subscribe($(this)); return false; });
};
$(document).ready(function() {

View File

@ -64,13 +64,6 @@ class OStatusQueueHandler extends QueueHandler
}
}
foreach ($notice->getReplies() as $profile_id) {
$oprofile = Ostatus_profile::staticGet('profile_id', $profile_id);
if ($oprofile) {
$this->pingReply($oprofile);
}
}
if (!empty($this->notice->reply_to)) {
$replyTo = Notice::staticGet('id', $this->notice->reply_to);
if (!empty($replyTo)) {
@ -82,6 +75,14 @@ class OStatusQueueHandler extends QueueHandler
}
}
}
foreach ($notice->getProfileTags() as $ptag) {
$oprofile = Ostatus_profile::staticGet('peopletag_id', $ptag->id);
if (!$oprofile) {
$this->pushPeopletag($ptag);
}
}
return true;
}
@ -107,6 +108,17 @@ class OStatusQueueHandler extends QueueHandler
$this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id);
}
function pushPeopletag($ptag)
{
// For a local people tag, ping the PuSH hub to update its feed.
// Updates may come from either a local or a remote user.
$feed = common_local_url('ApiTimelineList',
array('id' => $ptag->id,
'user' => $ptag->tagger,
'format' => 'atom'));
$this->pushFeed($feed, array($this, 'peopletagFeedForNotice'), $ptag);
}
function pingReply($oprofile)
{
if ($this->user) {
@ -225,4 +237,13 @@ class OStatusQueueHandler extends QueueHandler
return $feed;
}
function peopletagFeedForNotice($ptag)
{
$atom = new AtomListNoticeFeed($ptag);
$atom->addEntryFromNotice($this->notice);
$feed = $atom->getString();
return $feed;
}
}

View File

@ -112,6 +112,12 @@ class SalmonAction extends Action
case ActivityVerb::LEAVE:
$this->handleLeave();
break;
case ActivityVerb::TAG:
$this->handleTag();
break;
case ActivityVerb::UNTAG:
$this->handleUntag();
break;
case ActivityVerb::UPDATE_PROFILE:
$this->handleUpdateProfile();
break;
@ -172,6 +178,16 @@ class SalmonAction extends Action
throw new ClientException(_m("This target doesn't understand leave events."));
}
function handleTag()
{
throw new ClientException(_m("This target doesn't understand tag events."));
}
function handleUntag()
{
throw new ClientException(_m("This target doesn't understand untag events."));
}
/**
* Remote user sent us an update to their profile.
* If we already know them, accept the updates.

View File

@ -0,0 +1,131 @@
<?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/>.
*/
/**
* @package OStatusPlugin
* @maintainer James Walker <james@status.net>
*/
if (!defined('STATUSNET')) {
exit(1);
}
class XrdAction extends Action
{
public $uri;
public $user;
public $xrd;
function handle()
{
$nick = $this->user->nickname;
$profile = $this->user->getProfile();
if (empty($this->xrd)) {
$xrd = new XRD();
} else {
$xrd = $this->xrd;
}
if (empty($xrd->subject)) {
$xrd->subject = Discovery::normalize($this->uri);
}
// Possible aliases for the user
$uris = array($this->user->uri, $profile->profileurl);
// FIXME: Webfinger generation code should live somewhere on its own
$path = common_config('site', 'path');
if (empty($path)) {
$uris[] = sprintf('acct:%s@%s', $nick, common_config('site', 'server'));
}
foreach ($uris as $uri) {
if ($uri != $xrd->subject) {
$xrd->alias[] = $uri;
}
}
$xrd->links[] = array('rel' => Discovery::PROFILEPAGE,
'type' => 'text/html',
'href' => $profile->profileurl);
$xrd->links[] = array('rel' => Discovery::UPDATESFROM,
'href' => common_local_url('ApiTimelineUser',
array('id' => $this->user->id,
'format' => 'atom')),
'type' => 'application/atom+xml');
// hCard
$xrd->links[] = array('rel' => Discovery::HCARD,
'type' => 'text/html',
'href' => common_local_url('hcard', array('nickname' => $nick)));
// XFN
$xrd->links[] = array('rel' => 'http://gmpg.org/xfn/11',
'type' => 'text/html',
'href' => $profile->profileurl);
// FOAF
$xrd->links[] = array('rel' => 'describedby',
'type' => 'application/rdf+xml',
'href' => common_local_url('foaf',
array('nickname' => $nick)));
// Salmon
$salmon_url = common_local_url('usersalmon',
array('id' => $this->user->id));
$xrd->links[] = array('rel' => Salmon::REL_SALMON,
'href' => $salmon_url);
// XXX : Deprecated - to be removed.
$xrd->links[] = array('rel' => Salmon::NS_REPLIES,
'href' => $salmon_url);
$xrd->links[] = array('rel' => Salmon::NS_MENTIONS,
'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($this->user->id);
}
$xrd->links[] = array('rel' => Magicsig::PUBLICKEYREL,
'href' => 'data:application/magic-public-key,'. $magickey->toString(false));
// 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',
'template' => $url );
$url = common_local_url('tagprofile') . '?uri={uri}';
$xrd->links[] = array('rel' => 'http://ostatus.org/schema/1.0/tag',
'template' => $url );
header('Content-type: application/xrd+xml');
print $xrd->toXML();
}
}