]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
OStatus support for people tags
authorShashi Gowda <connect2shashi@gmail.com>
Sun, 6 Mar 2011 19:15:34 +0000 (00:45 +0530)
committerShashi Gowda <connect2shashi@gmail.com>
Sun, 6 Mar 2011 19:15:34 +0000 (00:45 +0530)
13 files changed:
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/actions/ostatusinit.php
plugins/OStatus/actions/ostatuspeopletag.php [new file with mode: 0644]
plugins/OStatus/actions/ostatussub.php
plugins/OStatus/actions/ostatustag.php [new file with mode: 0644]
plugins/OStatus/actions/peopletagsalmon.php [new file with mode: 0644]
plugins/OStatus/actions/pushhub.php
plugins/OStatus/actions/usersalmon.php
plugins/OStatus/classes/Ostatus_profile.php
plugins/OStatus/js/ostatus.js
plugins/OStatus/lib/ostatusqueuehandler.php
plugins/OStatus/lib/salmonaction.php
plugins/OStatus/lib/xrdaction.php [new file with mode: 0644]

index ef9a39a3772b47e878d3ca5793e40a6d6a04699a..666683affc4d8a8389e6e212115d8b2b46f2a181 100644 (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;
         }
@@ -209,39 +227,131 @@ class OStatusPlugin extends Plugin
      * 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;
     }
 
@@ -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');
             }
         }
 
index 9832f33c057889b918d4ab931001683f9af46a31..bcef3eef299c04299feabed227cb78cb108b8c7e 100644 (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.");
diff --git a/plugins/OStatus/actions/ostatuspeopletag.php b/plugins/OStatus/actions/ostatuspeopletag.php
new file mode 100644 (file)
index 0000000..737a7c5
--- /dev/null
@@ -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');
+    }
+}
index 5ca7ce76743a9cfa781b1e742d66549ca9a39c1e..95c6e18c18c63c7c8c86eee29904a7e525a9cca4 100644 (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);
         }
     }
 
diff --git a/plugins/OStatus/actions/ostatustag.php b/plugins/OStatus/actions/ostatustag.php
new file mode 100644 (file)
index 0000000..85ead81
--- /dev/null
@@ -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');  
+    }
+}
diff --git a/plugins/OStatus/actions/peopletagsalmon.php b/plugins/OStatus/actions/peopletagsalmon.php
new file mode 100644 (file)
index 0000000..c5e972e
--- /dev/null
@@ -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;
+        }
+    }
+}
index bfd51ec02f65b1d5ce17dc7d537f3c3528aaf9ad..e7716c8cd03b9c05f6eceb3c6f0f1fd38bae422c 100644 (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;
index 5355aeba03fed5b2eca9c762709d3466aae00f4b..76125553cf6096030963123b1a3ac38c7e72f652 100644 (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
index e71e5c9131268239cc554417786eda3875db3749..821ebef3d5da21c7afc7047918580de9ddd4c8f4 100644 (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 {
index bd29b5c0cf8fa41c1cd5a6efc279d74be43e2153..59d9f23f0510b6027976e087702517cf7db2c77a 100644 (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() {
index d5ee0c50417295a191f4ffa03542a30f8bda273a..d7dc921ad678e08969c9badbe44a13b50247e11a 100644 (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;
+    }
 }
index 8bfd7c8261e81ef3ca021d481854c63468289aa3..774709d6f5fc842fb3bba0fa9347bb554d4001a4 100644 (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.
diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php
new file mode 100644 (file)
index 0000000..1ac4d40
--- /dev/null
@@ -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();
+    }
+}