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'));
$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;
}
$salmonAction = 'groupsalmon';
$group = $feed->getGroup();
$id = $group->id;
+ } else if ($feed instanceof AtomListNoticeFeed) {
+ $salmonAction = 'peopletagsalmon';
+ $peopletag = $feed->getList();
+ $id = $peopletag->id;
} else {
return true;
}
* Add in an OStatus subscribe button
*/
function onStartProfileRemoteSubscribe($output, $profile)
+ {
+ $this->onStartProfileListItemActionElements($output, $profile);
+ return false;
+ }
+
+ function onStartGroupSubscribe($output, $group)
{
$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));
+ array('group' => $group->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
- // TRANS: Link description for link to subscribe to a remote user.
- _m('Subscribe'));
+ _m('Join'));
- $output->elementEnd('li');
}
- return false;
+ return true;
}
- function onStartGroupSubscribe($output, $group)
+ function onStartSubscribePeopletagForm($output, $peopletag)
{
$cur = common_current_user();
if (empty($cur)) {
- // Add an OStatus subscribe
+ $output->elementStart('li', 'entity_subscribe');
+ $profile = $peopletag->getTagger();
$url = common_local_url('ostatusinit',
- array('group' => $group->nickname));
+ array('tagger' => $profile->nickname, 'peopletag' => $peopletag->tag));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
- // TRANS: Link description for link to join a remote group.
- _m('Join'));
+ _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;
}
}
}
+ /**
+ * 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.
*
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.
*
return true;
}
- function onStartProfileListItemActionElements($item)
+ function onStartProfileListItemActionElements($item, $profile=null)
{
if (!common_logged_in()) {
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');
// 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');
}
}
class OStatusInitAction extends Action
{
var $nickname;
+ var $tagger;
+ var $peopletag;
var $group;
var $profile;
var $err;
// 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
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);
$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,
// 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.
// 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.");
--- /dev/null
+<?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');
+ }
+}
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);
}
}
--- /dev/null
+<?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');
+ }
+}
--- /dev/null
+<?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;
+ }
+ }
+}
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;
$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
public $profile_id;
public $group_id;
+ public $peopletag_id;
public $feeduri;
public $salmonuri;
'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'),
'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')),
),
);
}
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.
{
if ($this->isGroup()) {
return ActivityObject::fromGroup($this->localGroup());
+ } else if ($this->isPeopletag()) {
+ return ActivityObject::fromPeopletag($this->localPeopletag());
} else {
return ActivityObject::fromProfile($this->localProfile());
}
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);
*/
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));
}
}
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");
* @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);
$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();
{
if ($this->isGroup()) {
return $this->localGroup()->getBestName();
+ } else if ($this->isPeopletag()) {
+ return $this->localPeopletag()->getBestName();
} else {
return $this->localProfile()->getBestName();
}
'rendered' => $rendered,
'replies' => array(),
'groups' => array(),
+ 'peopletags' => array(),
'tags' => array(),
'urls' => array());
}
}
+ if ($this->isPeopletag()) {
+ $options['peopletags'][] = $this->localPeopletag();
+ }
+
// Atom categories <-> hashtags
foreach ($activity->categories as $cat) {
if ($cat->term) {
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 {
// 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();
// 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();
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) {
}
}
+ 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;
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 {
};
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() {
}
}
- 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)) {
}
}
}
+
+ foreach ($notice->getProfileTags() as $ptag) {
+ $oprofile = Ostatus_profile::staticGet('peopletag_id', $ptag->id);
+ if (!$oprofile) {
+ $this->pushPeopletag($ptag);
+ }
+ }
+
return true;
}
$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) {
return $feed;
}
+
+ function peopletagFeedForNotice($ptag)
+ {
+ $atom = new AtomListNoticeFeed($ptag);
+ $atom->addEntryFromNotice($this->notice);
+ $feed = $atom->getString();
+
+ return $feed;
+ }
}
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;
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.
--- /dev/null
+<?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();
+ }
+}